DEV Community

Cover image for One Open Source Project a Day (No. 165): ntfy — Push Notifications with a Single curl
WonderLab
WonderLab

Posted on

One Open Source Project a Day (No. 165): ntfy — Push Notifications with a Single curl

Introduction

"Push notifications without complexity."

This is the 165th article in the "One Open Source Project a Day" series. Today's project is ntfy (pronounced "notify").

You have probably run into this situation before: a long-running script finishes and you want to know the result. A backup job completes successfully and you want a quick tap on your shoulder. A server's disk is nearly full and you need to know immediately.

There are plenty of solutions — Email, Slack, a Telegram bot — but every single one requires creating an account, configuring an API key, and installing an SDK. By the time you are done, the notification plumbing has become more work than the original task.

ntfy strips the whole thing down to its essence:

curl -d "Backup complete!" ntfy.sh/my-channel
Enter fullscreen mode Exit fullscreen mode

That is it. One line, and the message lands on your phone. No account, no API key, no configuration whatsoever. 33.8k Stars, fully self-hostable, and a staple tool for ops engineers, developers, and automation enthusiasts.

What You Will Learn

  • How ntfy's publish-subscribe model works
  • Advanced message features: priority, tags, action buttons, delayed delivery, and attachments
  • How to run a private ntfy server with Docker
  • Authentication and access control configuration
  • Integrating ntfy with Alertmanager, GitHub Actions, Home Assistant, and more

Prerequisites

  • Basic familiarity with HTTP concepts (GET/POST requests)
  • Comfortable with Linux command-line basics (curl)
  • Docker fundamentals for the self-hosting section

Project Background

What It Is

ntfy is an HTTP-based publish-subscribe (pub-sub) push notification service. Its design is deliberately minimal:

  • Publisher: send an HTTP request to a "topic"
  • Subscriber: subscribe to that topic in the phone app or browser

Topics require no pre-registration. The message body is the notification content. There are no extra layers of abstraction.

The official ntfy.sh public service is free to use (with rate limits). You can also self-host it entirely — both options run the same open-source code and expose identical features.

Author

  • Author: Philipp C. Heckel (website: heckel.io)
  • Background: German software engineer and independent open-source author; also created the ZFS backup tool znapzend among others
  • Origin: Started in 2021 from a personal itch — "I just want a notification when my script finishes"

Project Stats

  • ⭐ GitHub Stars: 33,800+
  • 🍴 Forks: 1,600+
  • 📄 License: Apache 2.0 + GPLv2 (dual license)
  • 🌐 Public service: ntfy.sh
  • 📚 Docs: docs.ntfy.sh
  • 📱 Android: Google Play + F-Droid (both free)
  • 🍎 iOS: App Store

Core Features

What Problem It Solves

ntfy is a minimal message broker that reduces "send a notification" to the simplest possible HTTP request:

Publisher (anything that can make an HTTP request)
    curl / shell script / GitHub Actions / Prometheus Alertmanager ...
         ↓  POST/PUT  ntfy.sh/my-topic
ntfy server (public ntfy.sh or self-hosted instance)
         ↓  push delivery
Subscriber (any client subscribed to that topic)
    mobile app / browser / CLI ...
Enter fullscreen mode Exit fullscreen mode

The topic name doubles as both the "channel address" and the first layer of access control — there is no registration flow, no API key to manage. An unpredictable topic name is itself the first barrier to unauthorized access (formal authentication is available too).

Usage Scenarios

  1. Script and job completion notifications

    • Long-running backup jobs, builds, or data pipelines push their result to your phone the instant they finish. No more staring at a terminal.
  2. Server monitoring alerts

    • Pair ntfy with Uptime Kuma, Prometheus Alertmanager, or Healthchecks.io to receive alerts the moment a service goes down.
  3. CI/CD pipeline notifications

    • GitHub Actions or GitLab CI pushes success or failure to your phone on completion. No polling the web UI.
  4. Smart home events

    • Home Assistant automation rules fire ntfy notifications: "Front door unlocked," "Washing machine done."
  5. Personal automation scripts

    • Any shell script scenario — scheduled tasks, file downloads finishing, scrapers completing — one curl line handles the notification.

Quick Start

Option 1: Use the public service — zero configuration

# Step 1: Install the app on your phone (iOS / Android)
# Open app → Add subscription → enter a topic name, e.g. "my-alerts-abc123"

# Step 2: Send a notification (from any device)
curl -d "Hello from ntfy!" ntfy.sh/my-alerts-abc123

# With a title
curl \
  -H "Title: Backup complete" \
  -d "All files successfully backed up to S3" \
  ntfy.sh/my-alerts-abc123
Enter fullscreen mode Exit fullscreen mode

Option 2: Self-hosted server (Docker)

# Minimal start
docker run -p 80:80 -it binwiederhier/ntfy serve

# With persistent storage
docker run \
  -v /var/cache/ntfy:/var/cache/ntfy \
  -v /etc/ntfy:/etc/ntfy \
  -p 80:80 \
  -it binwiederhier/ntfy serve
Enter fullscreen mode Exit fullscreen mode

Replace ntfy.sh with your server's address and everything else stays identical.

Core Features

1. Message Priority (levels 1–5)

# Urgent: heavy vibration + full-screen alert
curl -H "Priority: urgent" -d "Disk almost full!" ntfy.sh/alerts

# Low: silent delivery
curl -H "Priority: low" -d "Scheduled backup complete" ntfy.sh/alerts
Enter fullscreen mode Exit fullscreen mode
Level Name Android Behavior
5 urgent Heavy vibration + full-screen popup
4 high Long vibration + default sound
3 default Default behavior
2 low No vibration or sound
1 min Collapsed under "other notifications"

2. Tags and Emoji

Tag names that match emoji short codes are automatically converted:

curl \
  -H "Tags: warning,computer" \
  -d "CPU usage exceeded 90%" \
  ntfy.sh/alerts
# Phone shows: ⚠️ 💻 CPU usage exceeded 90%
Enter fullscreen mode Exit fullscreen mode

3. Action Buttons (up to 3)

Add tappable actions directly to the notification — no need to open the app:

# Button that triggers an HTTP request
curl \
  -H "Actions: http, Silence alert, https://myserver.com/api/silence, method=POST" \
  -d "CPU sustained high load. Silence alert?" \
  ntfy.sh/alerts

# Action types:
# view      → open a URL
# http      → send an HTTP request
# broadcast → Android broadcast intent
# copy      → copy text to clipboard
Enter fullscreen mode Exit fullscreen mode

4. Delayed Delivery

# Send in 30 minutes
curl -H "In: 30min" -d "Time to drink some water" ntfy.sh/reminders

# Send tomorrow at 9am
curl -H "At: tomorrow, 9am" -d "Weekly standup reminder" ntfy.sh/reminders

# Dead man's switch: if the script doesn't ping again within 1 hour, fire an alert
curl -H "In: 1h" \
  -H "Title: Script heartbeat lost" \
  -d "No heartbeat in over 1 hour — please investigate!" \
  ntfy.sh/watchdog
Enter fullscreen mode Exit fullscreen mode

5. Attachments

# Upload a local file (max 15 MB, expires after 3 hours)
curl -T screenshot.png \
  -H "Filename: screenshot.png" \
  ntfy.sh/alerts

# Attach an external URL (no server storage used)
curl \
  -H "Attach: https://example.com/report.pdf" \
  -H "Filename: monthly-report.pdf" \
  -d "Monthly report is ready" \
  ntfy.sh/reports
Enter fullscreen mode Exit fullscreen mode

6. Message Templates

ntfy ships with built-in templates for Alertmanager and Grafana. Configuring the webhook URL is enough:

https://ntfy.sh/my-alerts?template=alertmanager
Enter fullscreen mode Exit fullscreen mode

Custom Go templates can process arbitrary JSON payloads.

Competitive Comparison

Dimension ntfy Telegram Bot Slack Webhook Pushover
No account needed ❌ Requires Telegram ❌ Requires Slack ❌ Requires signup
Self-hostable ✅ Full support
Free self-hosted ✅ Completely free ❌ Paid
Free Android app ❌ Paid app
Action buttons ✅ Limited
Delayed delivery
Publish method Plain HTTP Bot API Webhook API
Open source

Deep Dive

Architecture: Minimal Pub-Sub

ntfy's architecture is deliberately simple:

┌──────────────────────────────────────────────┐
│                ntfy server                    │
│                                              │
│  HTTP API ──→ Message routing ──→ Topic      │
│                                    ↓         │
│                               Message cache  │
│                            (SQLite / PgSQL)  │
│                                    ↓         │
│              ┌─────────────────────┤         │
│              ↓                     ↓         │
│          WebSocket              FCM/APNs     │
│        (Web / CLI)           (Mobile push)  │
└──────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Message routing: The topic name is the routing key. Incoming messages are broadcast to all active subscribers.

Message cache: The server caches messages for 12 hours by default. Clients that reconnect can pull any messages they missed. This is a key advantage over pure webhook solutions — the recipient does not need to be online at the moment of delivery.

Push channels:

  • Web / desktop: Server-Sent Events (SSE) or WebSocket
  • Android: Firebase Cloud Messaging (FCM), or direct long-polling in the F-Droid build (no FCM dependency)
  • iOS: APNs via the official ntfy.sh relay

Technology Stack

The backend is written in Go — a well-suited choice:

  • Produces a single binary with zero external dependencies
  • Handles many concurrent WebSocket connections efficiently
  • Cross-platform compilation covers Linux, macOS, and Windows

The database defaults to SQLite; switching to PostgreSQL is a single line in the config file:

# /etc/ntfy/server.yml
database-url: "postgres://user:pass@localhost/ntfy"
Enter fullscreen mode Exit fullscreen mode

SQLite's single-writer constraint means it cannot scale horizontally; use PostgreSQL for high-concurrency production deployments.

Self-Hosting Configuration

A production-ready server configuration:

# /etc/ntfy/server.yml

# Base settings
base-url: "https://ntfy.example.com"
listen-http: ":80"
listen-https: ":443"
key-file: "/etc/letsencrypt/live/ntfy.example.com/privkey.pem"
cert-file: "/etc/letsencrypt/live/ntfy.example.com/fullchain.pem"

# Storage
cache-file: "/var/cache/ntfy/cache.db"
cache-duration: "12h"
attachment-cache-dir: "/var/cache/ntfy/attachments"
attachment-total-size-limit: "5G"
attachment-file-size-limit: "15M"
attachment-expiry-duration: "3h"

# Access control (close anonymous access — login required)
auth-file: "/var/lib/ntfy/user.db"
auth-default-access: "deny-all"

# Declarative user provisioning (auto-created on startup)
auth-users:
  - "alice:$2a$10$hashhere:admin"
auth-access:
  - "alice:*:rw"        # alice can read/write all topics
  - "*:public-*:ro"     # everyone can subscribe to public-* topics

# Web push (browser notifications)
web-push-public-key: "BNbxxx..."
web-push-private-key: "xxx..."
web-push-file: "/var/lib/ntfy/webpush.db"

# Email forwarding
smtp-sender-addr: "mail.example.com:587"
smtp-sender-user: "ntfy@example.com"
smtp-sender-pass: "your-smtp-password"
smtp-sender-from: "ntfy@example.com"
Enter fullscreen mode Exit fullscreen mode

Authentication

Three authentication options are supported:

# Username + password
curl -u alice:password -d "Private message" ntfy.example.com/private-topic

# Bearer token (recommended — more secure than a raw password)
curl -H "Authorization: Bearer tk_AbcDefGhi..." \
  -d "Private message" ntfy.example.com/private-topic

# URL parameter (for environments where setting headers is inconvenient)
curl -d "Private message" \
  "ntfy.example.com/private-topic?auth=dXNlcjpwYXNz"
Enter fullscreen mode Exit fullscreen mode

Tokens support expiry and can be revoked at any time — safer than embedding a plain password in scripts.

Integrations

GitHub Actions:

- name: Send build notification
  run: |
    curl \
      -H "Title: ${{ github.repository }} build ${{ job.status }}" \
      -H "Priority: ${{ job.status == 'success' && 'default' || 'high' }}" \
      -H "Tags: ${{ job.status == 'success' && 'white_check_mark' || 'x' }}" \
      -d "Branch: ${{ github.ref_name }}, commit: ${{ github.sha }}" \
      ${{ secrets.NTFY_URL }}/${{ secrets.NTFY_TOPIC }}
Enter fullscreen mode Exit fullscreen mode

Prometheus Alertmanager:

# alertmanager.yml
receivers:
  - name: "ntfy"
    webhook_configs:
      - url: "https://ntfy.sh/my-alerts?template=alertmanager"
        send_resolved: true
Enter fullscreen mode Exit fullscreen mode

Home Assistant:

# configuration.yaml
notify:
  - platform: rest
    name: ntfy
    resource: https://ntfy.sh/my-home-alerts
    method: POST_JSON
    title_param_name: title
    message_param_name: message
Enter fullscreen mode Exit fullscreen mode

Project Links & Resources

Official Resources

Related Resources


Summary

Key Takeaways

  1. Minimal pub-sub model: topic = address, HTTP request = publish, no account, no SDK
  2. Rich message features: priority, tags, action buttons, delayed delivery, attachments — covers virtually every notification use case
  3. Complete self-hosting: single Go binary, docker run in one line, SQLite or PostgreSQL your choice
  4. Strong integration ecosystem: Alertmanager, GitHub Actions, Home Assistant, Uptime Kuma all work out of the box
  5. Message cache: 12-hour cache ensures offline subscribers don't miss messages — more reliable than pure webhooks

Who This Is For

  • Ops and DevOps engineers: server alerts, CI/CD notifications, cron job monitoring
  • Indie developers and solo founders: notification needs for automation scripts — cheap, zero-dependency, no lock-in
  • Self-hosting enthusiasts: full ownership of notification infrastructure, no third-party service dependency
  • Smart home builders: event push from Home Assistant, Node-RED, and similar platforms

One-Line Verdict

ntfy is the best argument for "simple is best" — it reduces push notifications to a single curl command, yet has enough depth to handle serious production requirements when you need it.


Check out PrimeSkills — a curated marketplace of AI agents and skills that have been validated in real-world, enterprise-grade workflows. No fluff, just what actually works.

Find more useful knowledge and interesting products on my Homepage

Top comments (0)