ntfy: Simple Push Notifications for Your Homelab
Send notifications to your phone from scripts and services. Simple HTTP/pub-sub interface.
Introduction
Every homelab administrator faces the same challenge: how do you get notified when something important happens? Your server reboots, a backup fails, disk space runs low, or your home automation system detects unusual activity. You need instant, reliable notifications without depending on commercial services or complex setups.
ntfy (pronounced “notify”) is the elegant solution: a simple HTTP-based pub-sub notification service that sends push notifications to your phone, desktop, or browser in seconds. No API keys, no complicated authentication, and no monthly fees. Just pure, straightforward notifications.
What you’ll learn:
- Deploy ntfy server with Docker Compose
- Configure topics and subscriptions
- Set up authentication and access control
- Install mobile apps (iOS/Android) and web client
- Integrate with monitoring tools (Uptime Kuma, Grafana, scripts)
- Use advanced features (priorities, attachments, actions)
- Implement rate limiting and security
- Troubleshoot common notification issues
Prerequisites:
- Docker and Docker Compose installed (v24.0+ and v2.0+)
- Reverse proxy with HTTPS (optional but recommended)
- Domain name (optional, can use IP address)
- Basic understanding of HTTP requests
- 256+ MB RAM, 1+ GB storage (minimal requirements)
Why ntfy?
The Notification Problem
Traditional notification solutions present challenges:
- Cloud Services (Pushover, Pushbullet): Monthly fees, API limits, privacy concerns
- Email: Slow, unreliable, often filtered to spam
- SMS: Expensive, carrier-dependent, no rich media
- Custom Apps: Development overhead, platform-specific
- IFTTT/Zapier: Requires internet, adds complexity
How ntfy Solves This
ntfy provides dead-simple notifications with zero configuration:
| Feature | ntfy | Pushover | |
|---|---|---|---|
| Cost | Free | $5 one-time | Free |
| Setup Time | 5 minutes | 10 minutes | 30+ minutes |
| Delivery Speed | Instant | Instant | Seconds to minutes |
| Privacy | Self-hosted | Cloud service | Email provider |
| API Complexity | HTTP POST | API key required | SMTP configuration |
| File Attachments | Yes | No | Yes |
| Rate Limits | Your choice | 10,000/month | Provider-dependent |
Key Features
- Zero Configuration: Use public ntfy.sh or self-host in 2 minutes
- Simple API: Send notifications with
curlor any HTTP client - Topics: Subscribe to specific notification channels
- Priority Levels: Urgent, high, default, low, min
- Attachments: Send images, files up to 150 MB
- Actions: Add clickable buttons to notifications
- Scheduled Delivery: Delay notifications or send at specific times
- Email Publishing: Send notifications via email
- Authentication: Protect topics with passwords or access tokens
- Web Push: Browser notifications without mobile app
Architecture Overview
Your Scripts/Services → ntfy Server → Mobile Apps
↓ ↓ ↓
HTTP POST Topics Push Notifications
↓
Subscribers How it works:
- Publisher sends HTTP POST to ntfy server:
curl -d "Backup completed" ntfy.sh/backups - ntfy server stores message in topic:
backups - All subscribers to
backupstopic receive instant push notification - Delivery via WebSocket, Server-Sent Events (SSE), or polling
Resource Requirements:
| Users | Messages/Day | RAM | Storage |
|---|---|---|---|
| 1-5 | Under 1,000 | 256 MB | 1 GB |
| 5-20 | 1,000-10,000 | 512 MB | 5 GB |
| 20-100 | 10,000+ | 1 GB | 10+ GB |
Installation: Docker Compose Method
Step 1: Create Directory Structure
mkdir -p /opt/ntfy/{cache,etc}
cd /opt/ntfy Step 2: Create server.yml Configuration
Create /opt/ntfy/etc/server.yml:
# Base configuration
base-url: "https://ntfy.example.com"
# Listen address (internal to container)
listen-http: ":80"
# Cache configuration
cache-file: "/var/cache/ntfy/cache.db"
cache-duration: "12h"
cache-startup-queries: |
pragma journal_mode = WAL;
pragma synchronous = normal;
pragma temp_store = memory;
# Attachment storage
attachment-cache-dir: "/var/cache/ntfy/attachments"
attachment-total-size-limit: "5G"
attachment-file-size-limit: "150M"
attachment-expiry-duration: "3h"
# Keepalive settings
keepalive-interval: "45s"
# Authentication and access control
auth-default-access: "deny-all" # Require authentication for all topics
auth-file: "/var/cache/ntfy/user.db"
# Rate limiting (prevent abuse)
visitor-subscription-limit: 30
visitor-request-limit-burst: 60
visitor-request-limit-replenish: "5s"
visitor-email-limit-burst: 16
visitor-email-limit-replenish: "1h"
visitor-attachment-total-size-limit: "100M"
visitor-attachment-daily-bandwidth-limit: "500M"
# Message limits
message-size-limit: "4096" # 4 KB max message size
# Enable web app
enable-web-ui: true
# Metrics (Prometheus)
enable-metrics: true
metrics-listen-http: ":9090"
# SMTP for email notifications (optional)
# smtp-server-listen: ":25"
# smtp-server-domain: "ntfy.example.com"
# smtp-server-addr-prefix: "ntfy-"
# Logging
log-level: "info"
log-format: "text" Key settings explained:
auth-default-access: "deny-all"- Require authentication (change to"read-write"for open server)cache-duration: "12h"- Keep messages for 12 hoursattachment-file-size-limit: "150M"- Max 150 MB per attachmentvisitor-request-limit-burst: 60- Allow 60 requests per burst
Step 3: Create docker-compose.yml
version: '3.8'
services:
ntfy:
image: binwiederhier/ntfy:latest
container_name: ntfy
restart: always
command:
- serve
environment:
- TZ=America/New_York
volumes:
- ./cache:/var/cache/ntfy
- ./etc/server.yml:/etc/ntfy/server.yml:ro
ports:
- "8080:80" # HTTP (client API)
- "9090:9090" # Metrics (Prometheus)
networks:
- ntfy
healthcheck:
test: ["CMD-SHELL", "wget -q --tries=1 http://localhost:80/v1/health -O - | grep -Eo '"healthy"\s*:\s*true' || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
ntfy:
driver: bridge Step 4: Configure Reverse Proxy (Optional but Recommended)
Caddy Configuration:
ntfy.example.com {
reverse_proxy ntfy:80
# WebSocket support
header {
X-Forwarded-Proto {scheme}
}
# Security headers
header {
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Strict-Transport-Security "max-age=31536000;"
}
} Nginx Configuration:
server {
server_name ntfy.example.com;
location / {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
# WebSocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Standard proxy headers
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Buffering settings
proxy_buffering off;
proxy_read_timeout 86400s;
}
listen 443 ssl http2;
ssl_certificate /etc/letsencrypt/live/ntfy.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ntfy.example.com/privkey.pem;
} Step 5: Start ntfy
# Start service
docker compose up -d
# Monitor logs
docker compose logs -f ntfy
# Check health
docker compose ps
curl http://localhost:8080/v1/health Expected output:
{"healthy":true} Step 6: Access Web Interface
Navigate to https://ntfy.example.com (or http://YOUR-SERVER-IP:8080)
You should see the ntfy web interface with:
- Subscribe to topic input
- Example commands
- Recent notifications (if any)
User Management and Authentication
Creating Users
If you enabled auth-default-access: "deny-all", create users:
# Create admin user
docker exec -it ntfy ntfy user add --role=admin admin
# Create regular user
docker exec -it ntfy ntfy user add alice
# Follow prompts to set passwords User roles:
admin- Can manage all topics and usersuser- Standard access to allowed topics- Anonymous - No authentication (if enabled)
Managing User Access
Grant user access to specific topic:
# Allow alice to read and write to "alerts" topic
docker exec -it ntfy ntfy access alice alerts rw
# Allow bob to read-only on "status" topic
docker exec -it ntfy ntfy access bob status ro
# Grant alice wildcard access to all "backup-*" topics
docker exec -it ntfy ntfy access alice 'backup-*' rw List user permissions:
docker exec -it ntfy ntfy user list Access Tokens
Generate tokens for scripts and automation:
# Create access token for alice
docker exec -it ntfy ntfy token add alice
# Output example:
# tk_AgQdq7mVBoFD37zQVN29RhuMzNIz Use token in requests:
curl -H "Authorization: Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz" -d "Backup completed successfully" https://ntfy.example.com/backups Revoke token:
docker exec -it ntfy ntfy token remove tk_AgQdq7mVBoFD37zQVN29RhuMzNIz Using ntfy: Publishing Notifications
Basic Publishing
Simplest notification:
curl -d "Hello, World!" ntfy.example.com/test With title:
curl -H "Title: Server Alert" -d "CPU usage exceeded 90%" ntfy.example.com/alerts From stdin:
echo "Backup completed" | curl -T- ntfy.example.com/backups Priority Levels
Control notification urgency:
# Max priority (bypass Do Not Disturb)
curl -H "Priority: max" -d "CRITICAL: Database offline!" ntfy.example.com/alerts
# High priority (default sound)
curl -H "Priority: high" -d "Disk space low: 95%" ntfy.example.com/monitoring
# Default priority (normal)
curl -d "Deployment successful" ntfy.example.com/ci-cd
# Low priority (no sound)
curl -H "Priority: low" -d "Daily backup completed" ntfy.example.com/backups
# Min priority (no notification, stored only)
curl -H "Priority: min" -d "Debug log entry" ntfy.example.com/logs Priority levels:
max(5) - Critical alerts, bypass DNDhigh(4) - Important notificationsdefault(3) - Normal prioritylow(2) - Low priority, quietmin(1) - Lowest, often silent
Tags and Emojis
Add visual indicators:
# Warning tag
curl -H "Tags: warning" -d "Unusual login detected" ntfy.example.com/security
# Multiple tags with emojis
curl -H "Tags: rotating_light,skull" -d "System critical error" ntfy.example.com/alerts
# Custom emoji
curl -H "Tags: +1,partying_face" -d "Deployment successful!" ntfy.example.com/ci-cd Common tags: warning, skull, rotating_light, tada, rocket, +1, checkmark
Clickable Actions
Add buttons to notifications:
# View action (opens URL)
curl -H "Actions: view, Open Dashboard, https://dashboard.example.com" -d "Server CPU high" ntfy.example.com/alerts
# HTTP action (trigger webhook)
curl -H "Actions: http, Restart Service, https://api.example.com/restart, method=POST, headers.Authorization=Bearer token123" -d "Service crashed" ntfy.example.com/monitoring
# Broadcast action (Android intent)
curl -H "Actions: broadcast, Trigger Automation, com.example.ACTION_TRIGGER" -d "Home automation event" ntfy.example.com/home File Attachments
Send images, logs, or documents:
# Attach file from URL
curl -H "Attach: https://example.com/screenshot.png" -d "Check this error screenshot" ntfy.example.com/support
# Upload file directly
curl -T backup.log -H "Filename: backup.log" https://ntfy.example.com/backups
# Attach with custom filename
curl -H "Attach: https://example.com/report.pdf" -H "Filename: daily-report.pdf" -d "Daily report attached" ntfy.example.com/reports Attachment limits: 150 MB per file (configurable in server.yml)
Delayed and Scheduled Delivery
Send notification in 30 minutes:
curl -H "Delay: 30m" -d "Meeting reminder" ntfy.example.com/reminders Send at specific time:
# Send at 9 AM tomorrow
curl -H "At: tomorrow, 9am" -d "Daily standup meeting" ntfy.example.com/team
# Send at specific timestamp
curl -H "At: 2025-12-25T08:00:00" -d "Merry Christmas!" ntfy.example.com/personal Email to ntfy
Send notifications via email:
# Configure in server.yml
smtp-server-listen: ":25"
smtp-server-domain: "ntfy.example.com"
smtp-server-addr-prefix: "ntfy-" Send email to: [email protected]
Subject becomes title, body becomes message.
Mobile App Setup
Android App
- Install “ntfy” from Google Play Store
- Open app → Add subscription
- Configure:
- Topic:
alerts - Server:
https://ntfy.example.com - Username:
alice(if auth enabled) - Password: [your password]
- Topic:
- Tap Subscribe
Features:
- Background service keeps connection alive
- Instant push notifications
- Share to ntfy from other apps
- Backup/restore subscriptions
- Custom notification sounds per topic
Notification Settings:
Per-topic configuration:
- Priority: Max, High, Default, Low, Min
- Sound: Custom ringtone
- Vibrate: On/Off
- LED color: Custom
iOS App
- Install “ntfy” from App Store
- Open app → Add subscription
- Configure:
- Topic URL:
https://ntfy.example.com/alerts - Username:
alice - Password: [your password]
- Topic URL:
- Tap Add
iOS Limitations:
- Background refresh limited by iOS
- Notifications may be slightly delayed (30-60s)
- No custom sounds (iOS restriction)
Workaround for instant notifications: Enable polling or use Apple Push Notification service (requires ntfy.sh or custom setup).
Desktop App
Linux (AppImage):
wget https://github.com/binwiederhier/ntfy/releases/latest/download/ntfy_linux_x86_64.AppImage
chmod +x ntfy_linux_x86_64.AppImage
./ntfy_linux_x86_64.AppImage macOS (Homebrew):
brew install ntfy
ntfy subscribe alerts Windows:
Download from GitHub Releases
Web Client
Access web interface directly:
- Navigate to
https://ntfy.example.com - Click Subscribe to topic
- Enter topic name (e.g.,
alerts) - Allow browser notifications when prompted
Browser notifications work in:
- Chrome/Chromium (all platforms)
- Firefox (all platforms)
- Safari (macOS/iOS)
- Edge (Windows/macOS)
Integration with Monitoring Tools
Uptime Kuma Integration
Send ntfy notifications from Uptime Kuma:
- Uptime Kuma → Settings → Notifications
- Click “Add Notification”
- Notification Type: Webhook
- Configure:
- Friendly Name:
ntfy Alerts - URL:
https://ntfy.example.com/monitoring?priority=high&tags=warning - Method:
POST - Body:
{"message": "[UP/DOWN]"}
- Friendly Name:
- Test and Save
Custom payload for better formatting:
{
"topic": "monitoring",
"message": "[status]: [msg]",
"title": "Uptime Kuma Alert",
"priority": "high",
"tags": ["warning"]
} Grafana Integration
Send alerts from Grafana:
Grafana → Alerting → Contact points
Add contact point:
- Name:
ntfy - Integration:
Webhook - URL:
https://ntfy.example.com/grafana - HTTP Method:
POST
- Name:
Optional: Add template for better formatting
Alert template:
{
"title": "{{ .CommonLabels.alertname }}",
"message": "{{ .CommonAnnotations.summary }}\n\n{{ .CommonAnnotations.description }}",
"priority": "{{ if eq .Status "firing" }}high{{ else }}default{{ end }}",
"tags": ["grafana", "{{ .Status }}"]
} Bash Script Monitoring
Example: Disk space monitor
#!/bin/bash
# disk-monitor.sh
NTFY_URL="https://ntfy.example.com/monitoring"
THRESHOLD=90
USAGE=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$USAGE" -gt "$THRESHOLD" ]; then
curl -H "Title: Disk Space Alert" -H "Priority: high" -H "Tags: warning,cd" -d "Disk usage at ${USAGE}% (threshold: ${THRESHOLD}%)" "$NTFY_URL"
fi Schedule with cron:
*/15 * * * * /opt/scripts/disk-monitor.sh Docker Container Health Checks
Monitor Docker container health:
#!/bin/bash
# docker-health-monitor.sh
NTFY_URL="https://ntfy.example.com/docker"
CONTAINERS=("nginx" "postgres" "redis")
for container in "${CONTAINERS[@]}"; do
if ! docker ps | grep -q "$container"; then
curl -H "Title: Container Down" -H "Priority: max" -H "Tags: rotating_light,skull" -d "Container $container is not running!" "$NTFY_URL"
fi
done Home Assistant Integration
Add to configuration.yaml:
notify:
- name: ntfy
platform: rest
resource: https://ntfy.example.com/homeassistant
method: POST_JSON
data:
priority: "{{ priority }}"
title: "{{ title }}"
message_param_name: message Use in automations:
automation:
- alias: "Front door opened"
trigger:
platform: state
entity_id: binary_sensor.front_door
to: 'on'
action:
service: notify.ntfy
data:
message: "Front door opened"
data:
priority: high
tags: ["door", "warning"] n8n Workflow
Create n8n workflow node:
- Add “HTTP Request” node
- Configure:
- Method: POST
- URL:
https://ntfy.example.com/n8n - Body: JSON
- JSON body:
{ "message": "{{ $json.message }}", "title": "n8n Workflow", "priority": "default" }
Advanced Features
Email Publishing
Enable SMTP to send notifications via email:
Update server.yml:
smtp-server-listen: ":25"
smtp-server-domain: "ntfy.example.com"
smtp-server-addr-prefix: "ntfy-" Send email to create notification:
To: [email protected]
Subject: Server Alert
Body: CPU usage critical Phone Calls (via ntfy.sh)
Use ntfy.sh cloud service for phone call notifications:
curl -H "Call: yes" -d "Critical server failure - call immediately" https://ntfy.sh/your-topic Note: Phone calls only available on ntfy.sh hosted service.
WebSocket Subscriptions
Subscribe via WebSocket for real-time updates:
// JavaScript example
const ws = new WebSocket('wss://ntfy.example.com/alerts/ws');
ws.onmessage = (event) => {
const notification = JSON.parse(event.data);
console.log('Received:', notification.message);
// Display notification in your app
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
}; Server-Sent Events (SSE)
Subscribe using Server-Sent Events:
# Command-line subscription
curl -s https://ntfy.example.com/alerts/sse
# Or with ntfy CLI
ntfy subscribe alerts Security Best Practices
Enable Authentication
Require authentication for all topics:
# server.yml
auth-default-access: "deny-all" Create users and grant specific access:
docker exec -it ntfy ntfy user add alice
docker exec -it ntfy ntfy access alice alerts rw Rate Limiting
Prevent abuse with rate limits:
# server.yml
visitor-subscription-limit: 30
visitor-request-limit-burst: 60
visitor-request-limit-replenish: "5s"
visitor-attachment-total-size-limit: "100M"
visitor-attachment-daily-bandwidth-limit: "500M" What these limits do:
- Max 30 active subscriptions per IP
- 60 requests per burst, then 1 per 5 seconds
- 100 MB total attachment storage per visitor
- 500 MB attachment bandwidth per day per visitor
HTTPS Only
Always use HTTPS in production:
- Prevents message interception
- Protects authentication credentials
- Required for web push notifications
Configure reverse proxy with valid SSL certificate.
Topic Naming Best Practices
Use private, unpredictable topic names:
Good:
alerts-dk83jf92kd
backup-monitoring-x7k2p
server-status-private-8xm2 Bad (easily guessable):
alerts
backups
my-topic Access Control Lists
Fine-grained permissions:
# Alice can read and write to all backup topics
docker exec -it ntfy ntfy access alice 'backup-*' rw
# Bob can only read alerts
docker exec -it ntfy ntfy access bob alerts ro
# Charlie has admin access
docker exec -it ntfy ntfy user change-role charlie admin Monitoring and Troubleshooting
Health Checks
# Check service health
curl https://ntfy.example.com/v1/health
# Check server stats
curl https://ntfy.example.com/v1/stats
# Prometheus metrics
curl https://ntfy.example.com:9090/metrics Common Issues
Issue 1: Notifications Not Received
Symptoms: Published messages don’t appear on mobile app
Solutions:
# Check topic subscription
# Ensure exact topic name match (case-sensitive)
# Test with curl
curl -d "Test notification" https://ntfy.example.com/test
# Check mobile app settings:
# - Background refresh enabled (iOS)
# - Battery optimization disabled (Android)
# - Notifications enabled in OS settings
# Verify authentication
curl -u alice:password -d "Auth test" https://ntfy.example.com/protected-topic Issue 2: High Memory Usage
Symptoms: Container consumes excessive RAM
Solutions:
# Add resource limits to docker-compose.yml
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
# Reduce cache duration in server.yml
cache-duration: "6h" # Reduce from 12h Issue 3: Attachments Not Working
Symptoms: Cannot upload or download files
Solutions:
# Check attachment limits
docker exec -it ntfy cat /etc/ntfy/server.yml | grep attachment
# Verify disk space
df -h /opt/ntfy/cache/attachments
# Check file permissions
docker exec -it ntfy ls -la /var/cache/ntfy/attachments Issue 4: WebSocket Connection Fails
Symptoms: Real-time notifications delayed or not working
Solutions:
# Ensure reverse proxy has WebSocket support
# Nginx:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Caddy (automatic WebSocket support):
reverse_proxy ntfy:80 Logging
Enable debug logging:
# server.yml
log-level: "debug"
log-format: "json" View logs:
docker compose logs -f ntfy
# Filter errors
docker compose logs ntfy | grep -i error
# Export logs
docker compose logs ntfy > /opt/ntfy/debug.log Backup and Maintenance
Backup Script
#!/bin/bash
# ntfy-backup.sh
BACKUP_DIR="/opt/backups/ntfy"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p "$BACKUP_DIR"
# Backup cache database and user database
tar czf "$BACKUP_DIR/ntfy-${DATE}.tar.gz" /opt/ntfy/cache/cache.db /opt/ntfy/cache/user.db /opt/ntfy/etc/server.yml
# Optional: Backup attachments (can be large)
# tar czf "$BACKUP_DIR/ntfy-attachments-${DATE}.tar.gz" # /opt/ntfy/cache/attachments
# Cleanup old backups (keep 30 days)
find "$BACKUP_DIR" -name "ntfy-*.tar.gz" -mtime +30 -delete
echo "Backup completed: $DATE" Schedule:
0 3 * * * /opt/ntfy/ntfy-backup.sh Restore
# Stop service
docker compose down
# Restore backup
tar xzf /opt/backups/ntfy/ntfy-YYYYMMDD.tar.gz -C /
# Restart service
docker compose up -d Database Maintenance
# Check cache database size
docker exec -it ntfy sqlite3 /var/cache/ntfy/cache.db "SELECT pg_size_pretty(pg_database_size('cache'));"
# Vacuum database (reclaim space)
docker exec -it ntfy sqlite3 /var/cache/ntfy/cache.db "VACUUM;"
# Purge old messages
# Automatically handled by cache-duration setting Conclusion
You now have a fully functional ntfy notification server with:
- Docker-based deployment for easy management
- Topic-based subscriptions for organized notifications
- Authentication and access control for security
- Mobile app support for iOS and Android
- Integration with monitoring tools and automation
- Advanced features like attachments and actions
- Rate limiting and security hardening
- Automated backups
Next steps:
- Subscribe to topics from mobile apps
- Integrate with existing monitoring tools
- Create custom scripts for homelab automation
- Set up Prometheus metrics monitoring
- Explore advanced features (scheduled delivery, email publishing)
ntfy provides the simplest path to reliable push notifications for your homelab. No complexity, no monthly fees, just instant notifications when you need them.
Additional Resources
Official:
Community:
Examples:
Last Updated: December 2025 Version Covered: ntfy 2.11.0+ Word Count: 3,287 words