Tech Digest

Communication

ntfy

Push notifications by HTTP POST to a topic, with no account and no client registration

CommunicationApache-2.0 (dual licensed with GPL-2.0)Stablearm64 builds
Last reviewed Profile maintained against the project's own documentation
Operational load
1 / 5
Set and forget
Idle memory
30 MB
typical use ~80 MB
Moving parts
1
containers in a normal deployment
Backup shape
SQLite backup
needs a dump or a stop

ntfy is a publish-subscribe notification server: you POST to a topic URL and every subscribed phone, desktop or browser gets a push. It replaces Pushover, Pushbullet and the ad hoc emails your scripts and monitoring send you.

Our verdict on ntfy#

ntfy beats Gotify for most households, for three checkable reasons. First, curl -d "backup done" ntfy.sh/mytopic works with no account, no token and no client registration: the topic name is the credential, which is both the appeal and a security model you have to think about before you use it for anything sensitive. Second, ntfy.sh exists as a free hosted instance, so you can start there and move to your own server later without changing a single publish call. Third, iOS. There is an official iOS app, which Gotify has never had, but the self-hosted catch is real: for timely delivery you must set upstream-base-url: "https://ntfy.sh" so your server sends a poll_request through ntfy.sh to APNs, and the phone then fetches the body from your server. Without it, delivery lags 20 to 30 minutes or much longer. The maintainer describes the iOS app as bare bones. Current release 2.28.0, August 2026.

Who ntfy is for#

Choose it if

  • Cron jobs, backup scripts and CI that need to tell you one thing in one line of curl.
  • Homelabbers wiring alerts out of Uptime Kuma, Home Assistant or Grafana without signing up for a notification SaaS.
  • iPhone households: of the two obvious self-hosted push servers, this is the one with an official iOS client.
  • Anyone who wants to start on hosted ntfy.sh and migrate to self-hosted later without rewriting any senders.

Look elsewhere if

  • You need per-sender isolation by default. Out of the box anyone who guesses your topic name can both read and publish to it.
  • You want iOS push with nothing at all touching a third party. The poll_request still transits ntfy.sh and Apple's push network, even though the message body does not.
  • You need SSO on the web app. There is no OIDC and no LDAP, only local accounts and an auth proxy.

What running it actually looks like#

One container, one config file, one directory. Mount /var/cache/ntfy for the SQLite message cache and attachments, /var/lib/ntfy for the user database, and /etc/ntfy/server.yml for configuration. listen-http defaults to :80, so behind a reverse proxy publish it elsewhere and set behind-proxy: true, or every visitor shares one rate limit under the proxy's IP. base-url must match the URL clients use exactly, scheme included, because the iOS app compares it against its Default Server setting. Out of the box there is no authentication and auth-default-access is read-write, meaning every topic is world-readable and world-writable. If that is not what you want, set it to deny-all and grant with ntfy access before publishing anything real. Upgrades are a tag bump; the schema migrates itself on start.

Resource profile#

Memory
30 MB idle, around 80 MB in ordinary use. Estimated from small installs, not a documented minimum. One Go binary with a SQLite cache; memory tracks the number of open subscriber connections rather than the message rate.
CPU and acceleration
Effectively zero. Every subscriber holds a long-lived HTTP or WebSocket connection, so the constraint is file descriptors and open sockets, not cycles.
Storage growth
Messages are cached for 12 hours by default and then deleted, so the SQLite file stays small. Attachments are the growth, bounded by `attachment-total-size-limit`, which defaults to 5 GB.
Operational load
1 of 5, Set and forget. One container, one SQLite file, no external database, all configuration in a single YAML file, and upgrades are a tag bump with self-applying schema migrations. The only recurring decision is whether you keep `upstream-base-url` pointed at ntfy.sh for iOS delivery.

Figures describe a small single-household install and are the working assumptions behind our stack planner. Your numbers will differ with library size, user count and hardware. See methodology.

Data and backup surface#

There is very little to back up and that is the point. /etc/ntfy/server.yml is the configuration and belongs in version control. /var/lib/ntfy/user.db holds accounts, tokens and ACLs and is the one file you would actually miss. /var/cache/ntfy/cache.db is the message cache, which expires after 12 hours by default and is genuinely disposable, as are the attachments stored beside it. If you enabled Web Push, webpush.db holds browser subscription endpoints; losing it just means browsers resubscribe. A live copy of the SQLite files is usually fine in WAL mode, but sqlite3 user.db ".backup out.db" costs nothing and is definitely consistent.

Derived backup shape

SQLite backup. sqlite3 <db> ".backup out.db" while running, or stop the container and copy. A live cp of a WAL-mode SQLite file can restore corrupt.

Traps and surprises#

Every topic is public until you say otherwise

A default install has no authentication and auth-default-access set to read-write, so any topic on your server can be read and written by anyone who knows or guesses its name. Topic names are the only secret. Before you point your backup script or your door sensor at it, set auth-default-access: deny-all, create users with ntfy user add, and grant explicit topic access. On a public instance also consider enable-signup: false.

iOS needs upstream-base-url pointed at ntfy.sh

Apple only delivers push through APNs, and your self-hosted server has no APNs certificate. The documented workaround is upstream-base-url: "https://ntfy.sh": your server publishes a poll_request containing only a message ID to ntfy.sh, which wakes the app through APNs, and the app then fetches the actual body from you. Set it to https://ntfy.sh, not your own hostname. Without it, notifications can arrive 20 to 30 minutes late or worse.

Behind a proxy, everyone shares one rate limit

Default rate limiting is 60 requests as a burst then one per 10 seconds, per visitor IP. Behind nginx, Traefik or Cloudflare, ntfy sees only the proxy's address unless you set behind-proxy: true and the proxy forwards the real client IP. The symptom is one noisy sender throttling everyone else. Set behind-proxy: true and make sure the proxy actually sends X-Forwarded-For.

A base-url mismatch breaks things silently

base-url feeds attachment download URLs, email footers, the Matrix push gateway check and the iOS poll_request topic calculation. If it does not match what clients use, character for character including https://, attachments return unreachable links and iOS push stops working while everything looks configured. Set it once to the public URL and change every client at the same time if you ever move.

Messages are deleted after 12 hours

cache-duration defaults to 12h, which exists so that clients reconnecting after a restart can catch up with since=, not so you can browse history. Anything older is gone. If you want notifications you can go back and read, raise cache-duration deliberately and watch the SQLite file grow, or forward the important ones somewhere with real retention. Setting it to 0 disables caching entirely and breaks since= and poll=1.

Specifications#

Category
Chat, mail and notifications
Licence
Apache-2.0 (dual licensed with GPL-2.0) (Permissive)
Written in
Go
First release
2021
Maturity
Stable
Datastore
SQLite (PostgreSQL supported since 2.x)
Default ports
80
Ships as
docker, deb/rpm package, native binary
arm64 builds
Yes
Identity
Local accounts only. Local users and per-topic ACLs managed with `ntfy access`; there is no OIDC or LDAP, so put an auth proxy in front of the web app if you need single sign-on.
Replaces
Pushover, Pushbullet, Prowl, Slack incoming webhooks
Project site
ntfy.sh
Source
github.com/binwiederhier/ntfy
Documentation
docs.ntfy.sh

Alternatives to ntfy#

Everything else in chat, mail and notifications, closest in operational weight first.

ToolOps loadIdle RAMLicence
Gotify1, Set and forget20 MBMIT
Mattermost3, Moderate500 MBAGPL-3.0 core, Mattermost Source Available License for `server/enterprise/`, Apache-2.0 for webapp and admin tools, MIT for official binaries
Stalwart3, Moderate150 MBAGPL-3.0 for the Community edition, dual licensed with the proprietary Stalwart Enterprise License for gated features
Synapse4, Heavy700 MBAGPL-3.0-only (or a commercial license from Element)
mailcow: dockerized4, Heavy3500 MBGPL-3.0 for mailcow's own code; bundled components keep their own licenses

Where ntfy comes up elsewhere#

Head to head

Sources#

Facts on this page were checked against the project's own material. Where a number is our own estimate rather than a published figure, the text says so.

  1. ntfy FAQ, hosted instance, rate limits, iOS app status
  2. ntfy default server.yml with documented defaults
  3. ntfy configuration documentation
  4. ntfy releases
  5. ntfy repository and dual licensing

Published . Last reviewed . Found something out of date? Tell us and we will fix it and log the change.