Decision. These three don’t compete for the same slot. ntfy [1]
⭐ 30.7kis the feature-rich default — pick it for ACLs, priorities, attachments, action buttons, and the only credible self-hosted iOS path. Gotify [4]⭐ 15.1kis the minimalist — pick it for a 5-minute Docker install and < 50 MB RAM if Android/web-only is fine. Apprise [6]⭐ 16.7kis not a server at all — it’s the routing layer that sends to ntfy, Gotify, and ~140 other services. The common homelab build is Apprise as the dispatch fan-out + ntfy as the delivery endpoint.
The category split (read this first)
The single biggest misconception is that these are three interchangeable apps. They are two categories:
- Push servers (ntfy, Gotify): they own the full chain — a REST endpoint to publish, a message store, and their own mobile apps that receive. ntfy is “a simple HTTP-based pub-sub notification service” [2]; Gotify is “a simple server for sending and receiving messages in real-time per WebSocket” [4]. Both are single Go binaries, no cloud dependency.
- Notification router (Apprise): a send-only library/CLI that normalizes one message out to ~143 upstream services via a common
service://creds@host/targetURL syntax [18][19]. It has no app and never receives anything. Its companion Apprise-API [7]⭐ 1.2kwraps it as a REST microservice.
Crucially, ntfy and Gotify are targets Apprise dispatches to [6] — ntfy://topic/ and gotify://host/token are valid Apprise URLs. So “Apprise vs ntfy” is the wrong frame; it sits a layer above.
app / alert source ──► Apprise (route + fan-out) ──┬──► ntfy ──► phone app
├──► Gotify ──► phone app
├──► email / SMS
└──► Discord / Telegram / ...
At a glance
| Axis | ntfy | Gotify | Apprise |
|---|---|---|---|
| What it is | Pub-sub push server 1 | Minimal push server 4 | Send-only router/library 18 |
| GitHub stars | ⭐ 30.7k 1 | ⭐ 15.1k 4 | ⭐ 16.7k 6 |
| Language / footprint | Go, ~20–50 MB (est.) | Go, < 50 MB RAM 14 | Python (heavier) |
| Own mobile apps | ✓ Android + iOS 2 | ✓ Android only 5 | ✗ none (it’s a relay) 18 |
| iOS support | ✓ via APNs relay 20 | ✗ no official app 38 | n/a (sends to others) |
| Priorities | ✓ 1–5 15 | ✓ integer 17 | maps to target’s |
| Tags / emoji | ✓ 15 | ✗ | passthrough |
| Attachments | ✓ ≤15 MB or URL 15 | ✗ | ✓ file or URL 18 |
| Action buttons | ✓ up to 3 15 | ✗ | ✗ |
| Scheduled delivery | ✓ X-Delay 15 |
✗ | ✗ |
| Auth model | Users + per-topic ACLs 11 | App/client tokens 12 | Config keys (stateful) 7 |
| Targets reachable | itself | itself | ~143 services 19 |
| Setup weight | Medium (CLI users/ACL) 13 | Easy (~5 env vars) 13 | Low (stateless) or keyed 10 |
Docker / self-hosting experience
All three are first-class Docker citizens; the gap is setup weight.
-
Gotify — easiest. One container, one
/app/datavolume holding the SQLite DB, images and certs; seed the admin password withGOTIFY_DEFAULTUSER_PASSand you’re done [9]. A homelab head-to-head calls it an “easy installation process” against ntfy’s “bit more setup” [13]. As a Go binary it sits under 50 MB RAM — runs on a Pi [14].services: gotify: image: gotify/server ports: ["80:80"] volumes: ["./gotify-data:/app/data"] environment: GOTIFY_DEFAULTUSER_PASS: change-me -
ntfy — more knobs. The image deliberately ships no
server.yml, so you mount your own/etc/ntfy/server.ymlplus a/var/cache/ntfyvolume forcache.db[8]. Auth is the richest of the three: a SQLiteauth-file,auth-default-access: deny-allfor a private instance, admin/user roles, per-topic ACLs with wildcards, andtk_-prefixed tokens — all managed viantfy user add/ntfy access/ntfy token addor declarative YAML [11]. Behind a reverse proxy, setbehind-proxy: true+base-url; omitting it collapses every client into one rate-limit bucket [16]. -
Apprise — stateless or keyed. Run
caronc/apprise:lateston port 8000;APPRISE_STATEFUL_MODEdecides whether config is persisted (hash/simple/disabled) with/configand/attachvolumes [7]. LinuxServer also ships a rootless PUID/PGID image [10]. Because it’s Python, its footprint is heavier than the two Go binaries — but it’s a stateless relay, so you can run it ephemerally.
Delivery mechanisms & the iOS problem
This is where “self-hosted” gets complicated, and it’s the most common real-world surprise.
- ntfy publishes over plain HTTP PUT/POST: five priorities via
X-Priority, emoji tags, attachments to 15 MB, up to three action buttons, andX-Delayscheduled delivery [15]. It also doubles as a UnifiedPush distributor (push without Google FCM, for apps like Element/FluffyChat) [3] and supports browser Web Push. - Gotify is deliberately leaner:
POST /message?token=<apptoken>, optional title/priority, anextrasfield — no attachments, tags, or action buttons [12]. It splitsappToken(senders) fromclientToken(receivers/management, e.g. its Android app) over a persistent WebSocket [17].
iOS is the sharpest discriminator. Apple forbids background WebSocket connections and routes all push through APNs — so instant iOS push is impossible without a central relay [20]. The consequences:
- ntfy solves it, but not purely locally: a self-hosted server sets
upstream-base-url: "https://ntfy.sh"and forwards poll requests through ntfy.sh’s APNs relay; an iOS Notification Service Extension then fetches the message body back from your server [20]. So even self-hosted ntfy leans on ntfy.sh for the iOS leg. ⚠ Note a current regression: on iOS 26.2+ ntfy notifications have no sound and can mute other apps until you make a phone call [21]. - Gotify has no official iOS app at all — shipping one means accepting Apple’s TOS and paying Apple [38]. iOS users bridge through ntfy or an unofficial workaround like mschirrmeister/gotify-ios
⭐ 2, which one reviewer flags as “insecure by default” [26][23].
If iPhones are in scope, ntfy is effectively the only self-hosted option — and even it routes the iOS leg through ntfy.sh.
Integration ecosystem (where Apprise earns its place)
The homelab story splits into two roles: Apprise is the dispatch/abstraction layer; ntfy and Gotify are the delivery + app endpoints — and Apprise sends to both, so they’re complementary, not rivals [34].
| Source tool | ntfy | Gotify | Apprise |
|---|---|---|---|
| Uptime Kuma | ✓ native 35 | ✓ native 35 | ✓ native 35 |
| Home Assistant | ✓ core integ. 28 | via Apprise/REST | ✓ notify.NAME 140+ svc 27 |
| Watchtower | via shoutrrr 30 | ✓ shorthand 29 | (uses shoutrrr, not Apprise) |
| Grafana alerting | webhook/bridge 32 | webhook | webhook |
- Uptime Kuma
⭐ 87.8kexposes ntfy, Gotify, and Apprise as native notification providers in its UI [35]. - Home Assistant ships a native core ntfy integration (since 2025.05, with usage sensors) [28] and an Apprise integration that binds
notify.NAMEto 140+ services [27]. - Watchtower
⭐ 24.8kdispatches via shoutrrr (note: not Apprise) — a Gotify shorthand backend [29] andntfy://ntfy.sh/topicfor ntfy [30]. - Grafana alerting reaches ntfy through its generic webhook contact point [32], optionally via a small reformatting bridge [33]
⭐ 34.
ntfy’s own integrations page catalogs the whole stack — Home Assistant, Uptime Kuma, Watchtower, Healthchecks, Gatus — plus Apprise as a universal bridge [31].
The takeaway: embed Apprise (or Apprise-API) as your one notification backend, point it at ntfy/Gotify, and every source tool only has to learn one URL syntax [22].
Project health & bus-factor
| Project | Stars | Latest release | Open issues | Maintainer |
|---|---|---|---|---|
| ntfy | ⭐ 30.7k | v2.24.0 (Jun 4 2026) | ~311 | Philipp Heckel (solo) |
| Apprise | ⭐ 16.7k | v1.11.0 (May 29 2026) | ~23 | Chris Caron |
| Gotify | ⭐ 15.1k | v2.9.1 (Feb 28 2026) | ~73 | Jannis Mattheis (solo) |
ntfy is the clear momentum leader on stars and release cadence [1]. Both push servers are effectively single-maintainer side projects: Philipp Heckel runs ntfy alone on one DigitalOcean droplet and explicitly chose not to depend on it financially [24]; Jannis Mattheis maintains Gotify in his free time around a full-time job, which shows in the spaced-out cadence [25]. Plan your bus-factor accordingly — though all three are open-source Go/Python you can fork.
Operator consensus (homelab blogs/reviews): ntfy is the rising favorite for cross-server topics, ACLs and emoji, dinged for CLI-only user management and no built-in web admin [13][23]; Gotify is praised as simple and stable but criticized for one-destination delivery and the iOS gap [23]; Apprise is repeatedly framed as the universal glue you stack on top, not a competitor [22].
When to reach for a cloud gateway instead
Self-hosting isn’t always the right answer. Three axes rarely point the same way:
- iOS reach → cloud often wins. Because of the APNs constraint above, a fully local push to iPhone is impossible; you’re relaying through someone’s server regardless [20]. Pushover is a one-time $4.99 per platform, no subscription, rock-solid iOS/Android — effectively a one-time cost for a homelab and zero ops [36].
- Cost → cloud is cheap at small scale. ntfy.sh’s free no-signup tier (60-request burst, then 1 req/10 s) covers most alerting [37]; self-hosting is “free” only if you ignore ops time.
- Privacy → self-host wins. Message content stays local, no rate limits or data mining [39]. By contrast, Telegram’s default Cloud Chats are stored on Telegram’s servers and are not end-to-end encrypted [40].
- ⚠ The reliability paradox. If you self-host your monitoring and your push gateway on the same box, the “your box is down” alert dies with the box. Keep the alert path on different infrastructure than the thing it watches — a cloud relay (Pushover) or ntfy’s hosted upstream is the hedge worth paying for [39].
Bottom line
- Android/web homelab, want power: self-host ntfy → priorities, ACLs, attachments, action buttons, UnifiedPush.
- Want the simplest possible thing, Android-only: Gotify → 5-minute install, < 50 MB RAM.
- Many source apps, many destinations: put Apprise in front as the router and let it fan out to ntfy/Gotify/email/chat.
- iPhones matter or you want zero ops: Pushover (cloud, $4.99 once) — or ntfy, accepting it relays the iOS leg through ntfy.sh.
- Most homelabs end up with two: Apprise (or Apprise-API) as the dispatch layer + ntfy as the delivery endpoint.