Skip to main content

Self-Host ntfy: Push Notifications for Everything 2026

·OSSAlt Team
ntfynotificationspushself-hostingdockerhome-assistant2026

TL;DR

ntfy (Apache 2.0, ~16K GitHub stars, Go) is the simplest self-hosted push notification service. Subscribe to a topic, then send notifications to it with a single curl command — no API keys, no SDKs, no setup on the sender side. Send alerts from cron jobs, shell scripts, CI pipelines, Home Assistant, Uptime Kuma, Grafana, or any tool that can make an HTTP request. PagerDuty charges $19/user/month; ntfy self-hosted is free with no per-notification limits.

Key Takeaways

  • ntfy: Apache 2.0, ~16K stars, Go — HTTP pub/sub push notifications
  • Zero sender setup: Send with a single curl to any topic URL — no SDK or API key required
  • iOS and Android apps: Native apps for receiving notifications with rich features
  • Priority levels: Min/low/default/high/urgent — control notification sound and delivery
  • Actions: Add clickable action buttons to notifications (URLs, intents, HTTP callbacks)
  • Access control: User/password protection for private topics

How ntfy Works

Sender (curl/script/app)  →  ntfy server  →  ntfy iOS/Android app
                             (pub/sub)        (subscriber)

Topics are just URL paths — anyone who knows the topic URL can publish or subscribe to it (unless protected with auth).


Part 1: Docker Setup

# docker-compose.yml
services:
  ntfy:
    image: binwiederhier/ntfy:latest
    container_name: ntfy
    restart: unless-stopped
    command: serve
    ports:
      - "8080:80"
    volumes:
      - ntfy_cache:/var/cache/ntfy
      - ntfy_data:/etc/ntfy
    environment:
      TZ: America/Los_Angeles
    # For config file (see below), mount it:
    # volumes:
    #   - ./server.yml:/etc/ntfy/server.yml:ro

volumes:
  ntfy_cache:
  ntfy_data:
docker compose up -d

Part 2: HTTPS with Caddy

ntfy.yourdomain.com {
    reverse_proxy localhost:8080
}

Part 3: Configuration File

For production use, create a config file:

# /etc/ntfy/server.yml (or mount ./server.yml)

# Public base URL (required for iOS notifications):
base-url: "https://ntfy.yourdomain.com"

# Cache for offline message delivery:
cache-file: "/var/cache/ntfy/cache.db"
cache-duration: "12h"

# Auth (enable to protect topics):
auth-file: "/var/lib/ntfy/user.db"
auth-default-access: "deny-all"   # Deny anonymous by default

# Rate limiting:
visitor-request-limit-burst: 60
visitor-request-limit-replenish: "1m"
visitor-subscription-limit: 30
visitor-message-daily-limit: 250

# Attachment support:
attachment-cache-dir: "/var/cache/ntfy/attachments"
attachment-total-size-limit: "5G"
attachment-file-size-limit: "15M"
attachment-expiry-duration: "3h"

# Optional: upstream Firebase for iOS background delivery
# (uses ntfy.sh as relay — set up-stream-base-url to avoid this)
# upstream-base-url: "https://ntfy.sh"
# docker-compose.yml with config file:
services:
  ntfy:
    image: binwiederhier/ntfy:latest
    command: serve --config /etc/ntfy/server.yml
    volumes:
      - ./server.yml:/etc/ntfy/server.yml:ro
      - ntfy_cache:/var/cache/ntfy
      - ntfy_data:/var/lib/ntfy

Part 4: Sending Notifications

Simplest possible notification

curl -d "Backup completed" ntfy.yourdomain.com/my-alerts

That's it. Any subscriber to my-alerts receives the notification immediately.

With title, priority, and tags

curl \
  -H "Title: Server Alert" \
  -H "Priority: high" \
  -H "Tags: warning,computer" \
  -d "Disk usage on prod-1 is at 95%" \
  ntfy.yourdomain.com/server-alerts

Priority levels

PriorityValueBehavior
Min1No notification sound, no badge
Low2No notification sound
Default3Default notification
High4Loud notification, stays in tray
Urgent5Max volume, bypasses DND

Notification with action buttons

# Add a "View Logs" button that opens a URL:
curl \
  -H "Title: Deploy Failed" \
  -H "Priority: high" \
  -H "Actions: view, View Logs, https://logs.yourdomain.com/deploy-42" \
  -d "Production deployment #42 failed at build step" \
  ntfy.yourdomain.com/deployments

Notification with image attachment

# Send a screenshot or image with the notification:
curl \
  -H "Title: Security Camera Alert" \
  -H "Filename: camera-snapshot.jpg" \
  -T /tmp/snapshot.jpg \
  ntfy.yourdomain.com/cameras

Part 5: iOS and Android Apps

iOS

  1. Install ntfy from App Store
  2. Add subscription:
    • Server: https://ntfy.yourdomain.com
    • Topic: my-alerts
  3. Enable notifications

For background push delivery on iOS, ntfy uses Apple Push Notifications (APNs). Self-hosted servers require proxying through ntfy.sh's Firebase relay unless you configure your own Firebase project.

Android

  1. Install ntfy from F-Droid or Play Store
  2. Add subscription:
    • Server: https://ntfy.yourdomain.com
    • Topic: my-alerts
  3. Notifications work without Firebase (UnifiedPush)

Web

Subscribe in a browser:

const es = new EventSource("https://ntfy.yourdomain.com/my-alerts/sse");
es.onmessage = (e) => {
  const notification = JSON.parse(e.data);
  console.log(notification.message);
};

Part 6: Access Control

Create users

# Add a user with publish-only access to a specific topic:
docker exec ntfy ntfy user add --role=user alice
docker exec ntfy ntfy access alice server-alerts rw   # read+write
docker exec ntfy ntfy access alice public-feed ro      # read-only

# Add an admin user:
docker exec ntfy ntfy user add --role=admin admin

# List users:
docker exec ntfy ntfy user list

Sending with authentication

# Basic auth:
curl -u alice:password \
  -d "Authorized alert" \
  ntfy.yourdomain.com/server-alerts

# Or Bearer token:
curl \
  -H "Authorization: Bearer tk_yourtoken" \
  -d "Authorized alert" \
  ntfy.yourdomain.com/server-alerts

Part 7: Integrations

Home Assistant

# configuration.yaml:
notify:
  - name: ntfy
    platform: rest
    resource: "https://ntfy.yourdomain.com/home-assistant"
    method: POST_JSON
    headers:
      Authorization: "Bearer tk_yourtoken"
    message_param_name: message
    title_param_name: title
    data:
      priority: high
# Automation example:
automation:
  - alias: Notify on motion
    trigger:
      - platform: state
        entity_id: binary_sensor.front_door_motion
        to: "on"
    action:
      - service: notify.ntfy
        data:
          title: "Motion Detected"
          message: "Front door motion sensor triggered"

Uptime Kuma

  1. Uptime Kuma → Notifications → Add → ntfy
  2. ntfy Server URL: https://ntfy.yourdomain.com
  3. Topic: uptime-alerts
  4. Priority: High

Cron job alerts

#!/bin/bash
# Wrap any command to get notified on failure:

COMMAND="$@"
OUTPUT=$($COMMAND 2>&1)
EXIT_CODE=$?

if [ $EXIT_CODE -ne 0 ]; then
  curl \
    -H "Title: Cron Job Failed" \
    -H "Priority: high" \
    -H "Tags: x,computer" \
    -d "Command: $COMMAND
Exit code: $EXIT_CODE
Output: $(echo "$OUTPUT" | tail -20)" \
    ntfy.yourdomain.com/cron-alerts
fi

Grafana alerts

# In Grafana → Contact Points → New → Webhook
URL: https://ntfy.yourdomain.com/grafana-alerts
Method: POST
Headers:
  Authorization: Bearer tk_yourtoken
  Title: Grafana Alert
  Priority: high

Watchtower (Docker update notifications)

# docker-compose.yml:
services:
  watchtower:
    image: containrrr/watchtower
    environment:
      WATCHTOWER_NOTIFICATION_URL: "ntfy://ntfy.yourdomain.com/watchtower?auth=Basic&password=tk_yourtoken"

Part 8: ntfy CLI

# Install ntfy CLI:
brew install ntfy  # macOS
# or download from: https://github.com/binwiederhier/ntfy/releases

# Subscribe and watch for notifications:
ntfy subscribe --from-config

# Subscribe to a topic and run a command on notification:
ntfy subscribe ntfy.yourdomain.com/my-alerts \
  'notify-send "$m" "$t"'   # Linux desktop notification

# Publish:
ntfy publish ntfy.yourdomain.com/my-alerts "Hello from CLI"

Maintenance

# Update:
docker compose pull
docker compose up -d

# Backup:
tar -czf ntfy-backup-$(date +%Y%m%d).tar.gz \
  $(docker volume inspect ntfy_ntfy_data --format '{{.Mountpoint}}')

# Check cache size:
du -sh $(docker volume inspect ntfy_ntfy_cache --format '{{.Mountpoint}}')

# View logs:
docker compose logs -f ntfy

# Test from server:
curl -d "Server is healthy" ntfy.yourdomain.com/health-check

See also: Gotify — alternative push notification service with persistent message history

See all open source productivity tools at OSSAlt.com/categories/productivity.

Comments