How to Install ntfy (Push Notifications to Your Phone from Any Script)

ntfy: a phone buzz from one curl — Self-hosted push notifications for every script.

Email (tutorial 49) is fine for reports. For "the backup finished", "someone's at the front door" or "disk at 91%" you want a buzz in your pocket, now. ntfy (pronounced notify) is a tiny server where sending a notification is one curl to a URL, and the phone app shows it instantly. Self-hosted, no account with anyone, and every tool in this series can talk to it.

Quick reference

TaskWhat
Folder~/docker/ntfy/compose.yaml
Web / APIhttp://server-ip:8090
Sendcurl -d "Backup done" https://ntfy.example.com/server
Subscribentfy app (iOS, Android) → + → topic server, your server URL
Priority, title, tags-H "Priority: high" -H "Title: Disk" -H "Tags: warning"
Lock it downNTFY_AUTH_DEFAULT_ACCESS: deny-all + users

1. The compose file

mkdir -p ~/docker/ntfy && cd ~/docker/ntfy
nano compose.yaml

services:
ntfy:
image: binwiederhier/ntfy:latest
container_name: ntfy
command: serve
ports:
- "8090:80"
environment:
TZ: Europe/London
NTFY_BASE_URL: https://ntfy.example.com
NTFY_CACHE_FILE: /var/cache/ntfy/cache.db
NTFY_AUTH_FILE: /var/lib/ntfy/user.db
NTFY_AUTH_DEFAULT_ACCESS: deny-all
NTFY_BEHIND_PROXY: "true"
NTFY_ATTACHMENT_CACHE_DIR: /var/cache/ntfy/attachments
NTFY_ENABLE_LOGIN: "true"
volumes:
- ./cache:/var/cache/ntfy
- ./data:/var/lib/ntfy
restart: unless-stopped

deny-all means nobody can send or read without a user — the right default once it's reachable from outside.

docker compose up -d

2. Users and topics

docker compose exec ntfy ntfy user add --role=admin john
docker compose exec ntfy ntfy user add scripts
docker compose exec ntfy ntfy access scripts "server" write-only
docker compose exec ntfy ntfy token add scripts

That's you as admin (read everything), a scripts user that can only write to the server topic, and a token for it to use. Save the token in .env on the server (tutorial 59).

3. The phone app

Install ntfy (App Store / Play Store). Settings → Default server: https://ntfy.example.com (through the reverse proxy with HTTPS — tutorial 28 — or the LAN address if you'll only use it at home over the VPN). Log in as john. + → Subscribe to topic: server.

On iOS, push notifications need the proxy to be reachable from the internet (Apple relays them); on Android the app keeps its own connection and works over the VPN.

4. Send something

curl -H "Authorization: Bearer tk_your_token" -d "Hello from the server" https://ntfy.example.com/server

Phone buzzes. Now the useful forms:

# title, priority, emoji tags
curl -H "Authorization: Bearer $NTFY_TOKEN" -H "Title: Backup" -H "Priority: low" -H "Tags: white_check_mark" \
-d "restic finished in 4m12s" https://ntfy.example.com/server

# urgent, with a button
curl -H "Authorization: Bearer $NTFY_TOKEN" -H "Priority: urgent" -H "Tags: rotating_light" \
-H "Actions: view, Open Grafana, https://grafana.example.com" \
-d "Disk at 91%" https://ntfy.example.com/server

# a picture (Frigate snapshot, say)
curl -H "Authorization: Bearer $NTFY_TOKEN" -T /tmp/front_door.jpg -H "Filename: door.jpg" https://ntfy.example.com/server

Put a notify() function at the top of your scripts and call it at the end. In cron, && notify "ok" || notify "FAILED".

5. Connect the rest

Troubleshooting

"unauthorized". Topic access isn't granted to that user, or the token is wrong. ntfy access lists the rules.

iPhone gets nothing when the app is closed. The server must be reachable over HTTPS from the internet for Apple's push relay; LAN-only works for Android.

Notifications delayed on Android. Battery optimisation killed the app's connection. Exempt ntfy in Android's battery settings, or enable the app's instant delivery option.

Through the proxy, "Bad Request" on the web UI. Missing NTFY_BEHIND_PROXY and the proxy's X-Forwarded-* headers.

Wrapping up

One container, a user and a write-only token, the app subscribed to a topic. From then on any script, cron job, monitor or automation reaches your pocket with a single curl — and the "did the backup run?" question answers itself every morning.

Call us