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-urlto 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,
curlone-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 inuser.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: setupstream-base-url: "https://ntfy.sh"inserver.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.ymlbefore the first start: put it in./etc. At minimum setbase-url: "https://ntfy.example.com",cache-file: "/var/cache/ntfy/cache.db"andauth-file: "/var/lib/ntfy/user.db". Withoutbase-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 withdocker compose exec ntfy ntfy user add --role=admin yourname. -
Tell Ntfy it sits behind a proxy: set
behind-proxy: trueso that rate limiting uses the real client IP fromX-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, thencurl http://localhost:8080/v1/health, which should return{"healthy":true}. -
Send a test message:
curl -u yourname -d "hello" https://ntfy.example.com/testshould 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
Caddyfileentry such asntfy.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 theUpgradeandConnectionheaders, turnproxy_buffering off, and raiseproxy_read_timeoutwell above Ntfy's 45-second keepalive. Otherwise JSON streams stall or break up. -
Raise the upload limit for attachments: Nginx defaults
client_max_body_sizeto 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-allon whatever you choose: rate limits likevisitor-request-limit-burstslow 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
admincan read and write every topic. Ausergets only what you grant explicitly. Create one withntfy user add aliceand change a password later withntfy user change-pass alice. -
Four permission levels per topic:
read-write,read-only,write-onlyanddeny. For example,ntfy access alice "backups" read-onlylets Alice watch backup alerts without being able to post fake ones. -
Wildcards cover topic families:
ntfy access alice "homelab_*" rwgrants every topic that starts with that prefix. Keep topic names consistent from day one. -
The
everyoneuser handles public topics:ntfy access everyone "status_public" read-onlyopens a single topic for anonymous reading while everything else stays private. -
Access tokens are for scripts:
ntfy token add --expires=30d --label="nas cron" alicegenerates a token starting withtk_. Send it asAuthorization: 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 accesswith 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 inconfiguration.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/backupsis 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://andntfys://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.
Top comments (0)