Self-Hosting ntfy with Docker Compose
What Is ntfy?
ntfy (pronounced “notify”) is a self-hosted push notification service that lets you send notifications to your phone and desktop using simple HTTP requests. curl -d "Server is down" ntfy.example.com/alerts — that’s it. No signup, no API keys for sending, no complex SDKs. It replaces services like Pushover ($5 one-time), Pushbullet, and Firebase Cloud Messaging. Official site.
Prerequisites
- A Linux server (Ubuntu 22.04+ recommended)
- Docker and Docker Compose installed (guide)
- 512 MB of free RAM (ntfy uses ~30-50 MB idle)
- A domain name (optional but recommended for mobile push)
Docker Compose Configuration
Create a project directory and configuration file:
mkdir -p ntfy/config ntfy/cache ntfy/auth
cd ntfy
Create a server.yml configuration file at ntfy/config/server.yml:
# ntfy server configuration
base-url: "https://ntfy.example.com" # Your public URL — required for iOS push and web push
cache-file: "/var/cache/ntfy/cache.db" # Message cache — persists notifications across restarts
cache-duration: "12h" # How long messages are kept (default: 12h)
# Authentication
auth-file: "/var/lib/ntfy/user.db" # User database — enables access control
auth-default-access: "deny-all" # Require authentication for all topics
# Reverse proxy support
behind-proxy: true # Trust X-Forwarded-For headers for rate limiting
# iOS push support — required for self-hosted instances
upstream-base-url: "https://ntfy.sh"
# Attachments
attachment-cache-dir: "/var/cache/ntfy/attachments"
attachment-total-size-limit: "5G" # Total attachment storage limit
attachment-file-size-limit: "15M" # Max single file size
attachment-expiry-duration: "3h" # Attachment retention
Create docker-compose.yml:
services:
ntfy:
image: binwiederhier/ntfy:v2.17.0
container_name: ntfy
command:
- serve
environment:
- TZ=UTC
volumes:
- ./cache:/var/cache/ntfy # Message cache, attachments, web push DB
- ./config:/etc/ntfy # server.yml configuration
- ./auth:/var/lib/ntfy # User authentication database
ports:
- "8080:80"
healthcheck:
test: ["CMD-SHELL", "wget -q --tries=1 http://localhost:80/v1/health -O - | grep -Eo '\"healthy\"\\s*:\\s*true' || exit 1"]
interval: 60s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
init: true
Start the stack:
docker compose up -d
Initial Setup
ntfy is accessible at http://your-server:8080 immediately after starting.
Create an admin user:
docker exec -it ntfy ntfy user add --role=admin admin
You’ll be prompted for a password. This admin user has full access to all topics.
Set up topic-level access control:
# Give everyone read access to a public announcements topic
docker exec -it ntfy ntfy access everyone "announcements" read-only
# Give a specific user full access to a private topic
docker exec -it ntfy ntfy access admin "server-alerts" read-write
Test by sending a notification:
curl -u admin:YOUR_PASSWORD -d "ntfy is working!" http://localhost:8080/server-alerts
How ntfy Works
ntfy uses a pub/sub model built on HTTP. Every topic is just a URL path:
| Action | Command |
|---|---|
| Send a message | curl -d "Hello" ntfy.example.com/mytopic |
| Send with title | curl -H "Title: Alert" -d "Server down" ntfy.example.com/mytopic |
| Send with priority | curl -H "Priority: urgent" -d "Disk full" ntfy.example.com/mytopic |
| Send with tags/emoji | curl -H "Tags: warning" -d "CPU at 90%" ntfy.example.com/mytopic |
| Attach a file | curl -T backup.log ntfy.example.com/mytopic |
| Subscribe (terminal) | curl -s ntfy.example.com/mytopic/sse |
No topic creation required — topics are created on first use.
Configuration
Message Priority Levels
| Priority | Name | Use Case |
|---|---|---|
| 1 | min | Background info, debug logs |
| 2 | low | Routine notifications |
| 3 | default | Standard notifications |
| 4 | high | Important alerts — overrides Do Not Disturb on Android |
| 5 | max/urgent | Critical alerts — persistent notification with sound |
Web Push Notifications
Generate VAPID keys for browser push support:
docker exec -it ntfy ntfy webpush keys
Add the output to your server.yml:
web-push-public-key: "BGxy..."
web-push-private-key: "AAxy..."
web-push-file: "/var/cache/ntfy/webpush.db"
web-push-email-address: "[email protected]"
Restart the container to apply:
docker compose restart ntfy
Email Notifications
Configure outgoing SMTP to receive ntfy messages as emails:
smtp-sender-addr: "smtp.example.com:587"
smtp-sender-user: "[email protected]"
smtp-sender-pass: "your-smtp-password"
smtp-sender-from: "[email protected]"
Rate Limiting
Default rate limits are generous for personal use. Adjust for multi-user instances:
visitor-subscription-limit: 30 # Max topic subscriptions per IP
visitor-request-limit-burst: 60 # Burst request limit
visitor-request-limit-replenish: "5s" # Refill rate
visitor-message-daily-limit: 500 # Daily message cap per visitor
Prometheus Metrics
enable-metrics: true
metrics-listen-http: ":9090"
Metrics are available at http://your-server:9090/metrics.
Mobile Apps
| Platform | Source | Notes |
|---|---|---|
| Android | Google Play, F-Droid | Full push support, background service |
| iOS | App Store | Requires upstream-base-url set to https://ntfy.sh |
In the app settings, set your server URL to https://ntfy.example.com and log in with your credentials.
Reverse Proxy
Your reverse proxy must forward WebSocket headers. Nginx example:
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
client_max_body_size 0; # Required for attachments
}
See Reverse Proxy Setup for full configuration with Nginx Proxy Manager, Traefik, or Caddy.
Backup
Back up the three data directories:
# Stop the container for consistent backup
docker compose stop ntfy
# Back up all data
tar -czf ntfy-backup-$(date +%Y%m%d).tar.gz cache/ auth/ config/
# Restart
docker compose start ntfy
The cache/ directory contains the message database and attachments. The auth/ directory contains user accounts. Both are SQLite databases. See Backup Strategy for automated approaches.
Troubleshooting
iOS notifications show “New message” without content
Symptom: Push notifications arrive but display generic text instead of the actual message.
Fix: Ensure your self-hosted instance is reachable from the internet. iOS notifications are relayed through Apple’s push service — if Apple’s servers can’t reach your ntfy instance to fetch the message, they show a generic placeholder. Verify base-url in server.yml matches your public URL exactly.
All users hit rate limits simultaneously
Symptom: Multiple users report rate limiting at the same time, even with low individual usage.
Fix: Set behind-proxy: true in server.yml. Without this, ntfy sees all requests coming from the reverse proxy’s IP and rate-limits everyone as a single visitor.
Container exits immediately after starting
Symptom: Container starts and stops within seconds. Logs show help text.
Fix: The command: serve directive is required. Without it, ntfy prints its help menu and exits. Verify your docker-compose.yml includes command: - serve.
WebSocket connections fail with 403
Symptom: Real-time subscriptions don’t work; browser console shows 403 errors.
Fix: Your reverse proxy must forward Upgrade and Connection headers. Add proxy_set_header Upgrade $http_upgrade and proxy_set_header Connection "upgrade" to your Nginx config.
Cannot create users — “auth-file not configured”
Symptom: ntfy user add fails with an auth-file error.
Fix: Ensure auth-file: "/var/lib/ntfy/user.db" is set in your server.yml and the /var/lib/ntfy volume is mounted.
Resource Requirements
- RAM: ~30-50 MB idle, ~100-200 MB under moderate load
- CPU: Minimal — runs comfortably on a Raspberry Pi
- Disk: 15 MB for the application. Cache database grows based on message volume and retention. Attachments use up to
attachment-total-size-limit(default 5 GB).
Verdict
ntfy is the best self-hosted notification service for most people. The HTTP-based API is dead simple — any script, cron job, or monitoring tool that can make an HTTP request can send you push notifications. Zero external dependencies (no database, no Redis), minimal resource usage, and excellent mobile apps make it the obvious choice. The only downside is CLI-only user management — there’s no web interface for creating users or managing ACLs.
If you need a simpler setup with a web UI for managing applications and clients, look at Gotify. If you want notifications as part of a larger monitoring stack, consider Uptime Kuma which has ntfy integration built in.
Related
Get self-hosting tips in your inbox
New guides, comparisons, and setup tutorials — delivered weekly. No spam.