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
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
znapzendamong 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 ...
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
-
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.
-
Server monitoring alerts
- Pair ntfy with Uptime Kuma, Prometheus Alertmanager, or Healthchecks.io to receive alerts the moment a service goes down.
-
CI/CD pipeline notifications
- GitHub Actions or GitLab CI pushes success or failure to your phone on completion. No polling the web UI.
-
Smart home events
- Home Assistant automation rules fire ntfy notifications: "Front door unlocked," "Washing machine done."
-
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
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
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
| 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%
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
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
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
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
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) │
└──────────────────────────────────────────────┘
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.shrelay
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"
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"
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"
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 }}
Prometheus Alertmanager:
# alertmanager.yml
receivers:
- name: "ntfy"
webhook_configs:
- url: "https://ntfy.sh/my-alerts?template=alertmanager"
send_resolved: true
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
Project Links & Resources
Official Resources
- 🌟 GitHub: https://github.com/binwiederhier/ntfy
- 📚 Docs: https://docs.ntfy.sh
- 🌐 Public service: https://ntfy.sh
- 📱 Android App (F-Droid): f-droid.org/packages/io.heckel.ntfy
- 🐛 Issues: github.com/binwiederhier/ntfy/issues
Related Resources
- Uptime Kuma — self-hosted monitoring that pairs naturally with ntfy alerts
- Healthchecks.io — cron job heartbeat monitoring with ntfy support
- ntfy Android source — Android client in its own repository
Summary
Key Takeaways
- Minimal pub-sub model: topic = address, HTTP request = publish, no account, no SDK
- Rich message features: priority, tags, action buttons, delayed delivery, attachments — covers virtually every notification use case
-
Complete self-hosting: single Go binary,
docker runin one line, SQLite or PostgreSQL your choice - Strong integration ecosystem: Alertmanager, GitHub Actions, Home Assistant, Uptime Kuma all work out of the box
- 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)