NTFY, not a typo, awesome tool for push notifications.
Short post today. NTFY — yes, that's genuinely the name, not a keyboard having a small seizure — is a tool for sending push notifications, especially to mobile.
Why would you want this? Off the top of my head, in no particular order of urgency:
- Checking if a site is down before your users politely inform you
- A long-running task finally finishing, after making you sweat
- A task getting assigned to you, so you can pretend you didn't see it for a while
- A warning during a test run, whispering that something's wrong
- Unit tests failing or completing, the two emotional poles of software development
- And plenty more, this list is not exhaustive, mostly because my imagination for “things that should buzz my phone” is apparently limitless
It's free and it's convenient — you actually already saw it in action in an earlier post, quietly pinging me the moment a NuGet package finished publishing (or didn't), instead of me refreshing a GitHub Actions tab like it owes me money and might pay up if I stare hard enough.
We'll install it in Docker, sitting behind an NGINX reverse proxy, because at this point every tool on this blog lives behind NGINX, it's basically a house rule.
docker run -v /var/cache/ntfy:/var/cache/ntfy -v /etc/ntfy:/etc/ntfy -p 8070:80 --network=host -itd binwiederhier/ntfy serve --cache-file /var/cache/ntfy/cache.dbThat command:
- Mounts a local volume for caching
- Uses
cache.dbfor local message caching - Mounts
/etc/ntfyfor configuration - Exposes port 8070
- Runs the
binwiederhier/ntfyimage, detached (-itd), quietly, like a good roommate
Next, the configuration. Since this is sitting behind a proxy and we actually want real-time notifications on iOS (not a small ask, Apple has opinions, more on that in a second), create server.yml in /etc/ntfy:
base-url: "https://yourserver/ntfytool"
listen-http: "127.0.0.1:8070"
behind-proxy: true
keepalive-interval: "45s"
upstream-base-url: "https://ntfy.sh"base-url— your server plus the virtual directory it's living under.listen-http— localhost plus whatever port the tool is actually running on (8070, here).behind-proxy— exactly what it says on the tin, no twist ending.upstream-base-url— the one line standing between you and iOS notifications actually working.
That last line matters more than it looks, because Apple, bless them, decided background processing on iOS should be treated like a national security concern. Straight from NTFY's own documentation:
“Unlike Android, iOS heavily restricts background processing, which sadly makes it impossible to implement instant push notifications without a central server. To still support instant notifications on iOS through your self-hosted ntfy server, you have to forward so called poll_request messages to the main ntfy.sh server (or any upstream server that's APNS/Firebase connected, if you build your own iOS app), which will then forward it to Firebase/APNS.”
Translation: your beautifully self-hosted instance still has to phone a friend (ntfy.sh) for the final iOS hop, because Apple simply will not allow anything else, no exceptions, no appeals. Android, refreshingly, doesn't have main-character syndrome about this — self-hosted alone is perfectly enough over there.
Last setup step, wire the virtual directory into NGINX:
location /ntfytool/ {
proxy_pass http://127.0.0.1:8070/;
proxy_http_version 1.1;
proxy_buffering off;
proxy_read_timeout 90;
proxy_connect_timeout 90;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header Proxy "";
}That's it — your very own push notification server, up, running, and entirely too easy to set up given how satisfying it is to use. Sending a notification is just a POST to your URL plus a topic name of your choosing:
https://yourserver/ntfytool/mytestI actually spun up a real NTFY instance to confirm this while writing the post, because “trust me, it works” has no business appearing twice in the same blog:
$ curl -X POST http://localhost:8079/mytest -d "NuGet packages published successfully"
{"id":"iSs4fEf5igM7","time":1784588473,"expires":1784631673,"event":"message","topic":"mytest","message":"NuGet packages published successfully"}One POST, one JSON response confirming the message landed safely on the mytest topic. No auth, no SDK, no ceremony, no fourteen-step onboarding wizard.
Now for the fun part — grab the NTFY app from the App Store, point it at your self-hosted server, and subscribe to a topic (mytest, in our case). Send that same POST again, and the notification shows up on your phone almost instantly, banner and all, sitting right there on your lock screen like a text from a very reliable, very robotic friend who never leaves you on read.
Antes de elegir entre camiseta de fútbol de la selección española, merece la pena comprobar la versión local, visitante o alternativa. Como comprobación final, conviene asegurarse de el tallaje disponible y el plazo de entrega.
Simple, quick, deeply satisfying in a way that's honestly a little disproportionate to how little effort it took. And since this is just a POST request underneath everything, you can fire notifications from curl, GitHub Actions, C#, Node, a shell script duct-taped together at 1am — whatever's already in your stack. No SDK to install, no library to import, just an HTTP request to a URL you already own and a phone that finally has something useful to say to you.
Happy coding!!!