For most people with an Android phone and a server that already runs all the time, yes: self-hosting Ntfy costs no more than the electricity, and you get no hard message limits and full control over your topics. For iPhone users the answer is less simple. Instant iOS delivery from a self-hosted server still goes through ntfy.sh, so you never cut ties with the hosted service completely. If you just want alerts that work and never want to touch a reverse proxy, paying once for Pushover or subscribing to ntfy.sh is a fair trade.

TL;DR

  • Homelab tinkerer (Proxmox box, Android phone, a dozen containers already running): self-host Ntfy in Docker, because one small Go binary adds next to no load and no recurring cost.
  • iPhone-only household (two iPhones, alerts from Home Assistant): self-host, but set upstream-base-url to ntfy.sh, or use Pushover if you want no moving parts, since iOS push depends on Apple's APNs either way.
  • Script and cron author (backup jobs, CI pipelines, curl one-liners): start on free ntfy.sh, then move to self-hosted when you hit rate limits or want private topic names, because the HTTP API stays the same.
  • Privacy-focused user (no third-party logs of alert content): self-host with auth and access control lists, because message bodies never leave your server on Android.
  • Existing Pushover user (paid license already, works fine): stay put unless you need attachments, unlimited topics or full self-hosting, because a working one-off purchase is hard to beat.
  • Small team on-call (three admins, shared alert channels): self-host with per-user tokens, or use a paid ntfy.sh tier if nobody wants to own the server.

The central tradeoff: self-hosting trades a small monthly bill for owning uptime, TLS and updates yourself, and on iOS you still lean on ntfy.sh for the final push.

Table of contents

  • What does Ntfy actually do, and how is it different from Pushover?
  • How much does ntfy.sh Pro or Pushover really cost over 3 years?
  • What does a self-hosted Ntfy server cost to run on a home server?
  • Why do iOS push notifications still depend on ntfy.sh?
  • Android delivery: Firebase vs instant delivery and battery tradeoffs
  • How do you set up Ntfy with Docker in under 30 minutes?
  • Reverse proxy, TLS and exposing Ntfy safely to the internet
  • How do users, tokens and access control lists work in self-hosted Ntfy?
  • Message cache, attachments and storage limits you control
  • Can Ntfy replace Pushover for Home Assistant, Uptime Kuma and cron jobs?

What does Ntfy actually do, and how is it different from Pushover?

Ntfy is a publish and subscribe notification server. You send a message to a topic with a plain HTTP request, and every phone, browser or script subscribed to that topic receives it. A complete example is curl -d "Backup finished" ntfy.sh/my-backups. You need no account, no API key and no SDK. A topic exists the moment you publish to it.

Pushover does the same basic job: you send a message, a phone shows it. The model behind it is different, though. Pushover is a closed, hosted-only service. Each sender needs an application token and each recipient a user key. You cannot run your own Pushover server. Ntfy is open source, dual-licensed under Apache 2.0 and GPLv2, and ships as one Go binary or Docker image that you can run anywhere.

Aspect Ntfy Pushover
Source and hosting Open source, self-hostable or hosted at ntfy.sh Proprietary, hosted only
Sending a message HTTP PUT or POST to a topic URL POST to the Pushover API with app token and user key
Addressing Named topics, anyone with the name or a token can subscribe Per-user keys and optional delivery groups
Priority levels 5 levels, from 1 (min) to 5 (max) 5 levels, from -2 (lowest) to 2 (emergency, repeats until acknowledged)
Extras Attachments, action buttons, scheduled delivery, web app Image attachments, emergency retries, receipts
Payment model Free to self-host, optional paid hosted tiers One-time purchase per client platform after a trial

What sets Ntfy apart is its topic model. It is built for scripts and makes no assumption about who is listening. Pushover is built around known users and guaranteed delivery. Most of the rest of this decision comes down to that difference.

How much does ntfy.sh Pro or Pushover really cost over 3 years?

The two hosted options charge in different ways, which makes a direct price tag misleading. ntfy.sh is a subscription with Supporter, Pro and Business tiers, billed monthly or at a discount yearly. Pushover is a one-time purchase per client platform, after a 30-day free trial. Over 36 months, a subscription keeps adding up while a license does not.

Option How you pay 3-year cost shape
ntfy.sh free tier Nothing, with daily message, attachment and topic limits $0, as long as you stay under the limits
ntfy.sh paid tier Monthly or annual subscription 36 × the monthly price, slightly less on annual billing
Pushover, one platform One-time license, around $5 per platform One payment, then $0
Pushover, iOS plus Android plus desktop One license per platform Roughly three times the single-platform cost, paid once
Self-hosted Ntfy Hardware and power you already pay for Close to $0 extra on an always-on server

Check the current tier prices on ntfy.sh before you do the sums, because they change. As an illustration of the arithmetic, a $10 monthly tier comes to $360 over three years. Even across three platforms, a Pushover setup costs a fraction of that.

The headline price leaves out the limits. Pushover caps each sending application at 10,000 messages per month before you have to buy more capacity. That is plenty for alerts but tight for chatty scripts. ntfy.sh tiers differ mainly in daily message quotas, attachment size, and how many topic names you can reserve. If a script loop sends a burst of alerts, a hosted quota can push you up a tier faster than you expected. On a self-hosted server you only hit the limits you set yourself.

What does a self-hosted Ntfy server cost to run on a home server?

The software is free. What you actually pay for is the machine it runs on, your time, and a few small extras. Ntfy is a single Go binary with ARM builds for armv6, armv7 and arm64. It runs on a Raspberry Pi, an old laptop or a NAS as easily as on a rack server. The cost picture depends far more on what you already own than on Ntfy itself.

  • Hardware you already run: if a home server, NAS or mini PC is on around the clock for other services, adding Ntfy costs nearly nothing. It is one more small container next to the rest.
  • Hardware bought for Ntfy alone: this is rarely worth it. A dedicated box just for notifications takes years to pay back against a one-time Pushover license.
  • Electricity: an always-on low-power board draws a few watts. The real cost is set by the whole machine, not by Ntfy's share of it.
  • Domain and TLS: phones need a stable HTTPS address. That means a domain you pay for each year, or a dynamic DNS name, plus certificates from Let's Encrypt at no charge.
  • Storage: messages live in a SQLite file such as cache.db, and users in user.db. Attachments sit in a folder you size yourself. All of this fits easily on an existing disk.
  • Your time: setup is quick. Updates, certificate renewals and the occasional outage are an ongoing chore that the hosted options take off your hands.

Where you run it changes which of these costs land on you. Common choices are a self-managed VPS, a home server, a NAS, or a managed host. Yundera is a managed Personal Cloud Server, built on CasaOS, that runs self-hosted apps as Docker containers on a server dedicated to the user.

Why do iOS push notifications still depend on ntfy.sh?

Apple does not let an iPhone app keep a persistent connection open in the background. Instant delivery on iOS has to go through Apple Push Notification service (APNs). APNs only accepts pushes signed with credentials that belong to the app's developer account. The official Ntfy iOS app is signed by its maintainer, so only ntfy.sh holds those credentials. Your self-hosted server cannot send to APNs on its own behalf.

  • The workaround is upstream-base-url: set upstream-base-url: "https://ntfy.sh" in server.yml. When a message arrives, your server then sends ntfy.sh a poll request for that topic.
  • The content stays on your server: the poll request carries a SHA-256 hash of your topic URL, not the message body. The iPhone wakes up, connects to your server and fetches the real message from there.
  • ntfy.sh still learns metadata: it sees that a hashed topic received activity and when. If ntfy.sh is unreachable, your iPhones stop getting instant alerts even though your own server is up.
  • Without the setting, iOS falls back to opening the app: messages arrive only when you open Ntfy or when iOS allows a background refresh. That is fine for daily digests and useless for "disk failing" alerts.
  • The only full escape costs money and effort: you can build and sign your own copy of the open source iOS app with your own APNs key. That needs an Apple Developer Program membership at $99 per year, plus Xcode, plus re-signing as builds expire.

Pushover has none of these gaps on iOS, because it is the APNs sender for its own app. For iPhone-heavy households, this dependency matters more than anything on the price sheet.

Android delivery: Firebase vs instant delivery and battery tradeoffs

Android is where self-hosting Ntfy works best, but how messages arrive depends on which build of the app you install and which server a topic lives on. The Google Play build can use Firebase Cloud Messaging (FCM). The F-Droid build contains no Google code at all. Only ntfy.sh holds the FCM credentials for the official app, so your own server reaches phones another way.

Setup How messages arrive Tradeoff
Play build, ntfy.sh topic, default Through Firebase, Google wakes the app Lowest battery use, but Google relays the event and Doze can delay low-priority messages
Play build, ntfy.sh topic, instant delivery on A persistent connection to ntfy.sh from a foreground service Near real-time, a permanent status bar notification, a little more battery
Play build, self-hosted topic A persistent connection to your server, since FCM is unavailable No Google in the path, but delivery stops if your server or home connection drops
F-Droid build, any server Always a persistent connection No Google services at all, works on de-Googled ROMs such as GrapheneOS
Pushover Android app Through Firebase, from Pushover's own servers Reliable and battery friendly, but closed source and dependent on Google

The persistent connection holds one idle WebSocket or HTTP stream open per server, not one per topic. Its battery cost is usually small compared with the screen and other apps, but it is not zero. You should also exempt Ntfy from battery optimisation, because some manufacturers kill background services aggressively.

One extra benefit: the Ntfy Android app can act as a UnifiedPush distributor. Apps such as Element or Tusky can then receive push through your own server instead of Google's.

How do you set up Ntfy with Docker in under 30 minutes?

The official image is binwiederhier/ntfy, and a minimal deployment is one service with two volumes. Most of the 30 minutes goes on DNS and the reverse proxy, not on Ntfy itself.

services: ntfy: image: binwiederhier/ntfy command: serve restart: unless-stopped environment: - TZ=Europe/Paris volumes: - ./cache:/var/cache/ntfy - ./etc:/etc/ntfy - ./lib:/var/lib/ntfy ports: - "8080:80" 
  • Write server.yml before the first start: put it in ./etc. At minimum set base-url: "https://ntfy.example.com", cache-file: "/var/cache/ntfy/cache.db" and auth-file: "/var/lib/ntfy/user.db". Without base-url, attachment links and iOS forwarding break.
  • Lock it down from minute one: add auth-default-access: "deny-all" so that random visitors cannot read or publish to your topics. Then create an admin with docker compose exec ntfy ntfy user add --role=admin yourname.
  • Tell Ntfy it sits behind a proxy: set behind-proxy: true so that rate limiting uses the real client IP from X-Forwarded-For, not the proxy's address.
  • Add iOS forwarding if anyone uses an iPhone: include upstream-base-url: "https://ntfy.sh" in the same file.
  • Start and verify: run docker compose up -d, then curl http://localhost:8080/v1/health, which should return {"healthy":true}.
  • Send a test message: curl -u yourname -d "hello" https://ntfy.example.com/test should appear in the web app and on a subscribed phone within seconds.

Pin the image to a specific version tag rather than latest once things work. That way an update happens because you chose it, not because a container restarted.

Reverse proxy, TLS and exposing Ntfy safely to the internet

Phones and the iOS forwarding path need to reach Ntfy over HTTPS at a stable hostname. Most broken self-hosted setups fail here, not inside Ntfy. Ntfy keeps long-lived connections open, so a proxy tuned for ordinary web pages can quietly drop subscribers.

  • Caddy is the shortest path: a two-line Caddyfile entry such as ntfy.example.com { reverse_proxy localhost:8080 } fetches and renews Let's Encrypt certificates automatically. It also passes WebSocket upgrades through with no extra config.
  • Nginx needs explicit streaming settings: set proxy_http_version 1.1, forward the Upgrade and Connection headers, turn proxy_buffering off, and raise proxy_read_timeout well above Ntfy's 45-second keepalive. Otherwise JSON streams stall or break up.
  • Raise the upload limit for attachments: Nginx defaults client_max_body_size to 1 MB. Any attachment bigger than that fails at the proxy before Ntfy ever sees it.
  • Port forwarding exposes your home IP: opening ports 80 and 443 works, but your router becomes the public endpoint. With a residential dynamic IP you also need dynamic DNS.
  • Tunnels avoid open ports, with caveats: Cloudflare Tunnel hides your IP and handles TLS, but Cloudflare then terminates the connection and can read the traffic. Check that its timeouts tolerate idle streams.
  • VPN-only access is the most private: putting Ntfy behind Tailscale or WireGuard means nothing is public. The catch is that every phone needs its VPN on all the time, and outside services such as a cloud CI runner cannot publish.
  • Keep deny-all on whatever you choose: rate limits like visitor-request-limit-burst slow down abuse, but authentication is what actually keeps strangers off your topics.

Test from mobile data, not your home Wi-Fi. NAT loopback can hide a broken external route.

How do users, tokens and access control lists work in self-hosted Ntfy?

Once you set auth-default-access: "deny-all", nobody gets in until you grant access. All of it is stored in user.db and managed with the ntfy command line tool. The model is small enough to learn in an afternoon and strict enough for a shared family or team server.

  • Two roles only: an admin can read and write every topic. A user gets only what you grant explicitly. Create one with ntfy user add alice and change a password later with ntfy user change-pass alice.
  • Four permission levels per topic: read-write, read-only, write-only and deny. For example, ntfy access alice "backups" read-only lets Alice watch backup alerts without being able to post fake ones.
  • Wildcards cover topic families: ntfy access alice "homelab_*" rw grants every topic that starts with that prefix. Keep topic names consistent from day one.
  • The everyone user handles public topics: ntfy access everyone "status_public" read-only opens a single topic for anonymous reading while everything else stays private.
  • Access tokens are for scripts: ntfy token add --expires=30d --label="nas cron" alice generates a token starting with tk_. Send it as Authorization: Bearer tk_..., so passwords never end up in crontabs or CI variables.
  • Revoke, do not rotate passwords: if a machine leaks a token, ntfy token remove alice tk_... cuts off that one sender without touching anything else.
  • Check what you granted: ntfy access with no arguments prints every user's permissions, which helps when an alert silently returns 403.

Wherever Ntfy runs, whether on a VPS, a NAS, a home server or Yundera, these commands run inside the container with docker exec. Back up user.db with the rest of your data.

Message cache, attachments and storage limits you control

On ntfy.sh, retention and attachment size are decided by your tier. On your own server they are plain settings in server.yml. The defaults are sensible for a small server, and the docs list each one so you can tune it. Attachments stay off entirely until you set attachment-cache-dir, so many people miss that feature on a first install.

Setting Documented default When to change it
cache-duration 12h Raise it to 72h or more if phones are often offline for a weekend and you want to catch up on missed alerts
attachment-file-size-limit 15M Raise it for camera snapshots or log bundles, but stay under your proxy's upload limit
attachment-total-size-limit 5G Lower it on a Raspberry Pi SD card, raise it on a NAS with spare disk
attachment-expiry-duration 3h Extend it if people open alerts hours later and find the image link has expired
visitor-attachment-total-size-limit 100M Raise it for a trusted script that sends many files, keep it low if topics are public
Message body limit 4,096 bytes, longer bodies become attachments Leave it alone, and send long logs as files instead

Two practical points follow from this. First, the cache is what lets a phone that reconnects pull everything it missed through ?since= polling. If you set cache-duration too short, an outage of your phone's connection turns into lost alerts. Second, attachments sit as plain files in the cache directory and are deleted when they expire. Put that directory on storage that you back up if attachments matter, or on fast disposable storage if they do not.

Keep an eye on cache.db growth after you raise retention. A busy server with a long cache-duration will grow steadily, and SQLite does not shrink the file on its own.

Can Ntfy replace Pushover for Home Assistant, Uptime Kuma and cron jobs?

For almost every homelab sender, yes. Most tools already speak Ntfy, and the ones that don't can send an HTTP POST. The gaps that remain are about guaranteed delivery, not integrations.

  • Home Assistant: the RESTful notify platform posts to any Ntfy server, and recent releases also include a native ntfy integration. Put the access token in secrets.yaml, not in configuration.yaml, so that it stays out of shared config.
  • Uptime Kuma: Ntfy is a built-in notification type. You enter the server URL, topic, priority and either a username and password or an access token. Down alerts and recovery alerts then arrive as separate messages.
  • Cron jobs and shell scripts: curl --fail -H "Authorization: Bearer tk_..." -H "Priority: high" -H "Tags: warning" -d "rsync failed on nas" https://ntfy.example.com/backups is the whole integration. With --fail, a rejected token shows up as a non-zero exit code instead of failing silently.
  • Watchtower, Apprise and Shoutrrr-based tools: they accept ntfy:// and ntfys:// URLs. That covers many container update notifiers and anything built on Apprise.
  • Pushover's emergency priority has no direct equivalent: Pushover priority 2 repeats every 30 seconds or more until someone acknowledges it, and it returns a receipt you can check. Ntfy priority 5 is loud and persistent on the phone, but it does not repeat or confirm that anyone saw it.
  • Migration is a find and replace: Pushover senders need an app token and a user key in their own format. Each one has to be reconfigured by hand, so list every sender before you cancel anything.

If a missed alert at 3 a.m. would cost real money, keep Pushover for that one path. Send everything else through Ntfy.