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:

ActionCommand
Send a messagecurl -d "Hello" ntfy.example.com/mytopic
Send with titlecurl -H "Title: Alert" -d "Server down" ntfy.example.com/mytopic
Send with prioritycurl -H "Priority: urgent" -d "Disk full" ntfy.example.com/mytopic
Send with tags/emojicurl -H "Tags: warning" -d "CPU at 90%" ntfy.example.com/mytopic
Attach a filecurl -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

PriorityNameUse Case
1minBackground info, debug logs
2lowRoutine notifications
3defaultStandard notifications
4highImportant alerts — overrides Do Not Disturb on Android
5max/urgentCritical 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

PlatformSourceNotes
AndroidGoogle Play, F-DroidFull push support, background service
iOSApp StoreRequires 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.