The 31 October release makes this a timely moment to look at ntfy: version 2.36.0 keeps daily usage limits across restarts and includes changes to how those limits are shared with PostgreSQL. For a first deployment, a single server with persistent SQLite storage is a simpler starting point.
Below, a script account can publish to one topic, a reader account can receive it, and anonymous clients are denied. You will also see where self-hosting ends: mobile push delivery, recovery and independent availability checks still need deliberate setup.
What is ntfy, and where does it fit?
ntfy is an open-source notification service. An application publishes an HTTP request to a named topic, and subscribers receive the message. That makes it useful when a tool can call an HTTP endpoint but does not have a dedicated mobile notification system.
Backup results
Send a short success or failure message after a backup job finishes. Include a job name and the result, with a link to protected logs when useful.
Uptime alerts
Have a separate monitor publish when an endpoint fails or recovers. Put the checker somewhere that can still run if the application server is unavailable.
Automation updates
Receive deployment, scheduled-task or bot-job results without repeatedly opening a terminal to inspect routine output.
Keep notification payloads short. “Nightly backup failed on worker-2” is more useful on a lock screen than a raw log containing customer data, credentials or internal connection strings.
1. Prepare a VPS, a domain and Docker
Use a Linux VPS with SSH access, Docker Engine and the Compose plugin. Our Ubuntu Docker installation guide covers that foundation. Check that the host has spare memory and disk for its existing workloads as well as the notification service; monitor actual use after deployment.
- Choose a hostname such as
alerts.example.comand point its DNS A record to the VPS. Add an AAAA record only when IPv6 reaches the same service correctly. - Allow inbound TCP ports 80 and 443 for the Caddy setup below. UDP 443 is optional for HTTP/3. Keep your existing SSH access available.
- Use a fresh deployment or adapt an existing reverse proxy. Another service already listening on 80 or 443 will conflict with this example.
- Run the commands from one project directory using an account authorised to manage Docker.
HYEHOST Cloud VPS provides root access in Wolverhampton, Ashburn and Auckland, so you can install and maintain this stack. Choose a region around your users and the systems sending alerts.
mkdir -p ~/ntfy-alerts
cd ~/ntfy-alerts
docker compose version
Create the following three files in that directory. Replace alerts.example.com in both configuration files with your real hostname.
2. Create the Docker Compose stack
Save this as compose.yaml. It uses the official ntfy image with Caddy as the HTTPS entry point:
services:
ntfy:
image: binwiederhier/ntfy:v2.36.0
command: ["serve"]
restart: unless-stopped
volumes:
- ./server.yml:/etc/ntfy/server.yml:ro
- ntfy_state:/var/lib/ntfy
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- ntfy
volumes:
ntfy_state:
caddy_data:
caddy_config:
Only Caddy publishes ports on the host. It reaches ntfy using the service name ntfy on the project's Docker network. Do not add a public ntfy port that bypasses this HTTPS entry point.
The ntfy image is pinned to the release checked for this article. The Caddy example follows its 2.x Alpine tag; pin tested image digests as well if your deployment requires reproducible builds. Review newer releases before reusing the configuration later.
3. Enable persistent storage and private access
Save this as server.yml:
base-url: "https://alerts.example.com"
listen-http: ":80"
behind-proxy: true
auth-file: "/var/lib/ntfy/user.db"
auth-default-access: "deny-all"
enable-login: true
enable-signup: false
cache-file: "/var/lib/ntfy/cache.db"
cache-duration: "24h"
The authentication database and message cache live in the persistent ntfy_state volume. Anonymous access is explicitly denied. Login is enabled, and public self-service registration is disabled. The versioned configuration reference explains those settings.
The 24-hour cache is a delivery buffer for clients that miss a message. It is not a permanent audit trail or a backup of the system that sent the alert. Use your normal logging and recovery tools for those jobs.
behind-proxy: true lets ntfy use proxy-supplied client information for rate limiting. This topology keeps ntfy behind Caddy on the project network; review that trust boundary if you add another proxy or attach other containers to it.
4. Configure HTTPS with Caddy
Save this as Caddyfile:
alerts.example.com {
reverse_proxy ntfy:80 {
flush_interval -1
}
}
Caddy can obtain and renew a public certificate when the hostname and reachable ports meet its HTTPS requirements. Its data volume preserves certificate state between container replacements.
The streaming setting avoids delaying streamed responses through buffering. Keep the hostname consistent with ntfy's base-url.
Validate the Compose model, then start the services:
docker compose config --quiet
docker compose up -d
docker compose ps
docker compose logs --tail=60 ntfy caddy
Open https://alerts.example.com and check the certificate. If certificate issuance fails, fix DNS and reachability before creating an exception in your browser or sending credentials.
5. Separate the publisher from the reader
Create two regular users from the server. Each user-add command prompts for a password and confirmation, so the password does not have to appear in the command itself:
docker compose exec ntfy ntfy user add alert-reader
docker compose exec ntfy ntfy access alert-reader backups read-only
docker compose exec ntfy ntfy user add backup-agent
docker compose exec ntfy ntfy access backup-agent backups write-only
The first account can read the backups topic. The second can publish to it. Neither is an all-topic administrator. Local management commands operate on the configured user database; they do not require an HTTP admin login. The tagged user-command implementation and access-command implementation document the relevant behaviour.
Check the resulting account permissions:
docker compose exec ntfy ntfy access
Give each application its own publisher identity when you add more integrations. That lets you change or revoke one application's credentials without changing every phone and script.
6. Send a message and test the access rules
Publish a first notification from a machine with curl:
curl --fail-with-body --user backup-agent \
-H 'Title: Backup test' \
-H 'Tags: white_check_mark' \
--data-binary 'The notification path is working.' \
https://alerts.example.com/backups
curl prompts for the publisher's password. A successful response contains the accepted message. These fields follow the publishing API.
Retrieve the latest cached message using the reader account:
curl --fail-with-body --user alert-reader \
'https://alerts.example.com/backups/json?poll=1&since=latest'
The poll option makes this a one-shot request. A normal streaming subscription stays open to receive new messages.
Now repeat a publish request without credentials:
curl --silent --show-error --write-out '\nHTTP %{http_code}\n' \
--data-binary 'Anonymous access probe' \
https://alerts.example.com/backups
It must be rejected. Also check that the reader cannot publish, the publisher cannot read, and neither account can access an unrelated topic. A working notification alone does not prove that permissions are correct.
7. Subscribe on a phone or desktop
In the ntfy mobile app, add your server's full HTTPS URL and the backups topic, then provide the alert-reader credentials in the server's account settings. Send another test while the app is open and another with the phone locked.
Android
Allow the app to receive notifications and check its background/battery settings. Test the self-hosted server while the screen is off.
iPhone
Prompt delivery through the standard iOS app requires an upstream push relay. Configure it if timely background alerts matter to your setup.
Web browser
The web UI is useful for reading topics. Background Web Push needs separate server configuration and browser permission.
For iOS push delivery, add the following to server.yml and restart ntfy:
upstream-base-url: "https://ntfy.sh"
docker compose restart ntfy
The documented iOS flow sends a poll-request message ID and a hash of the topic URL upstream; the phone then retrieves the content from your server. Metadata still passes through an external service. Without the relay, background delivery can be delayed.
HTTPS protects the connection to your server. Do not treat this configuration as end-to-end encryption that hides messages from the server administrator. Choose alert content accordingly and review the project's privacy explanation for the mobile services you use.
8. Connect backups and uptime checks
For a backup job, send the success notification only after the backup command has succeeded. Handle backup failure and notification failure separately: a failed HTTP request does not prove the backup failed, and silence does not prove it worked.
The interactive password examples above are for setup. An unattended job needs credentials from your secret-management system or a permission-restricted client configuration. Keep the publisher account limited to its topic and avoid putting passwords into a cron command, public repository or job log.
ntfy's integration examples include Uptime Kuma. Create a separate topic and publisher for uptime events, grant a reader access, then use the notification provider settings supported by your installed monitor. Our Uptime Kuma guide covers the monitor itself.
Run the checker independently of the server being monitored. For important alerts, consider a second delivery channel too: if the ntfy VPS or the phone's connection is down, this notification path is unavailable.
Keep the notification service recoverable
Back up compose.yaml, server.yml, Caddyfile and the persistent volumes needed for recovery. The user database contains sensitive authentication material, and cached notifications may contain private operational information, so protect the copies.
For a straightforward SQLite backup, stop ntfy, copy the complete ntfy_state volume using a volume-backup procedure, then start it again. Include any database sidecar files. Copying a live database as if it were an ordinary text file can produce an inconsistent result; SQLite describes the backup risks.
Keep an encrypted off-server copy and test a restoration on a separate instance. HYEHOST Storage Box can hold those recovery files; our rclone and SFTP guide covers encrypted backup storage.
Before updates, save a working configuration and backup, review the release notes and test account permissions again afterwards. Our Compose update guide explains the pull, recreate and recovery process.
Common ntfy self-hosting problems
| Symptom | Check first | Useful next step |
|---|---|---|
| Certificate failure | DNS records and public TCP 80/443 reachability | Check Caddy logs and remove an incorrect AAAA record |
| Unauthorised request | Account credentials, exact topic and read/write permission | Inspect ntfy access output; do not enable anonymous access to hide the error |
| Messages arrive late | Phone background settings, iOS relay and proxy buffering | Compare an open-app test with a locked-phone test |
| Users disappear after recreation | The mounted state volume and auth-file path | Check the Compose project name and recover the correct volume |
A server health check confirms only part of the path. Include a periodic end-to-end test that reaches a real subscriber, and investigate missed alerts rather than relying solely on container status.
Self-hosted ntfy FAQ
Is ntfy free to self-host?
The ntfy server is open-source software that you can run on your own VPS. You still pay for the server, domain and any optional external services you choose.
Are ntfy topics private by default?
Do not assume so. This guide explicitly enables authentication, denies anonymous topic access and grants separate read-only and write-only permissions for the backups topic. Test that an anonymous publish request is rejected.
Which ports does this Docker setup expose?
Only Caddy publishes host ports: 80 and 443 over TCP, plus 443 over UDP for HTTP/3. ntfy has no published host port and is reached by Caddy over the project's Docker network.
Does self-hosted ntfy work with iPhone notifications?
Yes, but prompt notifications through the standard iOS app need the optional upstream push relay. The upstream receives poll-request metadata, while the phone retrieves message content from your server. Without that configuration, background delivery may be delayed.
Is opening the web UI enough for browser background notifications?
No. The web interface can display topics, but Web Push needs additional server configuration and browser permission. This guide's basic configuration does not enable Web Push.
Can ntfy replace an uptime monitor or a backup?
No. ntfy delivers notifications. Use a separate checker to detect outages and a backup system to preserve data. An alert server cannot send an outage message while it is itself unavailable.

