If Gotify is the neat self-hosted notification dashboard, ntfy is the thing you reach for when you want notifications to be as boring as curl.
Publish to a topic URL. Subscribe from a phone, browser, desktop, CLI, or another script. That is the core idea.
That makes ntfy especially useful for backups, cron jobs, monitoring checks, Home Assistant automations, CI scripts, Docker update alerts, and small homelab tasks where adding a heavier workflow tool would be overkill.
What is ntfy?
ntfy is an open-source HTTP-based pub-sub notification service.
The project description is direct:
Send push notifications to your phone or desktop using PUT/POST
The public service runs at ntfy.sh , but the interesting part for self-hosters is that the same server can run locally with Docker.
The repository includes:
- a Go server
- a CLI for publishing and subscribing
- a React web app
- Android and iOS clients
- SQLite and PostgreSQL-backed persistence options
- Web Push, SMTP, Matrix Push Gateway, Twilio, Firebase/APNS, and Prometheus support
For a first homelab deployment, you do not need most of that. A single container with a persistent cache is enough to start.
Why Self-Host ntfy?
The biggest reason is control over your notification path.
With the public ntfy.sh service, topic names are effectively shared secrets. That is convenient, but you are still sending messages through someone else’s server.
With self-hosted ntfy, you decide where the messages are stored, how long they are cached, whether accounts are enabled, whether topics require auth, and how the instance is exposed.
It also makes local-only alerting easy. A NAS, Raspberry Pi, mini PC, or monitoring box can publish to http://ntfy.local/topic-name without needing a SaaS notification account.
Self-Hosting ntfy with Docker
This compose file pins ntfy to v2.28.0, publishes the web UI and API on port 8099, and keeps the message cache in a Docker volume.
name: ntfy
services:
ntfy:
image: binwiederhier/ntfy:v2.28.0
container_name: ntfy
command:
- serve
- --cache-file
- /var/cache/ntfy/cache.db
environment:
TZ: ${TZ:-Europe/Madrid}
ports:
- "${NTFY_PORT:-8099}:80"
volumes:
- ntfy_cache:/var/cache/ntfy
healthcheck:
test: ["CMD-SHELL", "wget -q --tries=1 http://localhost:80/v1/health -O - | grep -Eo '\"healthy\"[[:space:]]*:[[:space:]]*true' || exit 1"]
interval: 60s
timeout: 10s
retries: 3
start_period: 20s
restart: unless-stopped
volumes:
ntfy_cache:
The matching Home-Lab config is here:
Home-Lab ntfy composeStart it with:
cd /home/jalcocert/Desktop/Home-Lab/ntfy
cp .env.sample .env
docker compose --env-file .env up -d
Then check the server:
curl -s http://localhost:8099/v1/health
Expected response:
{"healthy":true}
The web UI is available at:
http://localhost:8099
Sending a Test Notification
The simplest ntfy publish command is just a POST body to a topic:
curl -d "Backup finished" http://localhost:8099/my-homelab-topic
For a slightly richer message:
curl \
-H "Title: Backup completed" \
-H "Priority: default" \
-H "Tags: white_check_mark" \
-d "The nightly archive finished successfully." \
http://localhost:8099/my-homelab-topic
To verify from the command line without a phone app, poll the topic:
curl -s "http://localhost:8099/my-homelab-topic/json?poll=1"
Field Note: Local Docker Test
I tested the Home-Lab compose locally on port 8099.
The container started with binwiederhier/ntfy:v2.28.0, /v1/health returned {"healthy":true}, and this publish-and-poll flow worked:
topic="foss-ntfy-test-$(date +%s)"
curl -s \
-H "Title: Foss Engineer ntfy test" \
-H "Priority: default" \
-d "local ntfy compose works" \
"http://localhost:8099/$topic"
curl -s "http://localhost:8099/$topic/json?poll=1"
The poll endpoint returned the same message, title, topic, and priority.
Reverse Proxy Notes
For a private LAN-only notifier, localhost:8099 or an internal DNS name may be enough.
For a public instance, do not just throw it onto the internet and hope topic names are enough.
At minimum:
- put ntfy behind HTTPS
- set a public
base-url - set
behind-proxy: trueif a reverse proxy is in front - use long, unguessable topic names
- consider enabling auth and access control
- protect metrics if you enable them
If iOS devices need timely push behavior from a self-hosted ntfy server, read the upstream push notes carefully. The config usually needs upstream-base-url: "https://ntfy.sh" so the iOS app can be nudged to poll your server.
ntfy vs Gotify
Use ntfy when the integration should be a URL.
It shines for shell scripts, health checks, Docker update alerts, cron jobs, CI, and monitoring systems that already know how to make HTTP requests.
Use Gotify when you want a more account-centered notification server with application tokens, a clear dashboard, and persistent app identities.
The one-line version:
ntfy is topic-first and curl-first. Gotify is app-token-first and dashboard-first.
For a homelab, both are valid. I would pick ntfy for simple script alerts and Gotify when I want a more structured notification admin UI.
Conclusion
ntfy is one of those tools that stays useful because it does not ask for much.
You can run it as a single container, send messages with curl, subscribe from a phone or browser, and add auth, Web Push, e-mail, Matrix, or PostgreSQL only when the deployment actually needs them.
For self-hosters already using Uptime Kuma, Gotify, Home Assistant, or cron-based maintenance scripts, ntfy is worth having in the toolbox.
The ntfy Project
- The official ntfy site
- The ntfy source code on GitHub
- ntfy documentation
- License: Apache-2.0 and GPL-2.0
FAQ
Is ntfy open source?
Yes. The ntfy repository is dual licensed under Apache-2.0 and GPL-2.0.
The public hosted service also has paid plans, but the server code can be self-hosted.
Does ntfy need PostgreSQL?
No. ntfy can run with no database, with a local SQLite cache file, or with PostgreSQL.
The compose in this post uses a persistent SQLite message cache at /var/cache/ntfy/cache.db. PostgreSQL becomes more interesting if you want a larger multi-user instance with database-backed message cache, auth, web push subscriptions, and account data.
Is a topic name really a password?
In the simplest public ntfy mode, yes, effectively.
Anyone who knows the topic URL can publish or subscribe if you have not enabled access control. For private or exposed deployments, use long random topic names, HTTPS, auth, ACLs, or all of those.
Can I use ntfy with Uptime Kuma?
Yes. Uptime Kuma supports ntfy as a notification channel.
That gives you a compact monitoring path: Uptime Kuma detects downtime, ntfy delivers the push notification, and your phone subscribes to the chosen topic.
Which should I pick: ntfy or Gotify?
Pick ntfy if you want the simplest possible HTTP publish flow and topic-based subscriptions.
Pick Gotify if you prefer a more structured server with users, app tokens, a dashboard-first workflow, and a focused Android/WebSocket client model.
Comments