Skip to content

ntfy — Personal Push Notification Service

Self-hosted push notification service for personal apps, homelab services, and scripts to send alerts to iOS via HTTP POST, without requiring native APNs.

📌 Quick Reference

Component Details
Service URL https://ntfy.parthjain.tech
Docker Path ~/docker/ntfy
CLI Path ~/bin/notify
Config File ~/docker/ntfy/config/server.yml
Primary Topic parth-apps
Network proxy (Docker external network)

🏗️ Architecture & Networking

  • Flow: Internet ➔ Cloudflare ➔ Cloudflare Tunnel (cloudflared) ➔ Docker proxy network ➔ http://ntfy:80 ➔ ntfy container.
  • Ports: ntfy does not expose host ports. It is only accessible via the proxy network.
  • Verification: Both cloudflared and ntfy must appear when running docker network inspect proxy.
  • Tunnel Target: http://ntfy:80
  • Cloudflare Access: Do NOT put CF Access authentication in front of ntfy; it breaks the iOS app's direct communication.

📂 Docker Configuration

Directory Structure (~/docker/ntfy/):

├── docker-compose.yml
├── cache/
│   ├── auth.db
│   └── cache.db
└── config/
    └── server.yml

docker-compose.yml:

services:
  ntfy:
    image: binwiederhier/ntfy:latest
    container_name: ntfy
    command:
      - serve
    environment:
      TZ: Asia/Kolkata
    volumes:
      - ./cache:/var/cache/ntfy
      - ./config:/etc/ntfy
    restart: unless-stopped
    networks:
      - proxy

networks:
  proxy:
    external: true

⚙️ Server & iOS Configuration

server.yml key settings:

base-url: "https://ntfy.parthjain.tech"
upstream-base-url: "https://ntfy.sh"
cache-file: "/var/cache/ntfy/cache.db"
auth-file: "/var/cache/ntfy/auth.db"
auth-default-access: "deny-all"

💡 Note: Both base-url and upstream-base-url are required for instant iOS push delivery.

🔐 Authentication & Tokens

Anonymous access is disabled. Authentication is required.

  • Primary User: parth (has read/write access to topic *)
  • Check Users: docker exec -it ntfy ntfy user list
  • Check ACLs: docker exec -it ntfy ntfy access
  • List Tokens: docker exec -it ntfy ntfy token list parth
  • Create Token: docker exec -it ntfy ntfy token add parth

⚠️ Security Warning: Never commit access tokens (tk_...) to Git or place them directly in source code.

🗂️ Topics & Priorities

Topic Strategy:

  • parth-apps (Current): Reader, finance, scrapers, background jobs.
  • parth-homelab (Planned): Backups, docker, disk usage, updates.
  • parth-urgent (Planned): Security alerts, disk/backup failures (Immediate attention).

Supported Priorities: min, low, default, high, max (Reserve max for critical failures).

💻 notify CLI Adapter

A lightweight Python adapter (~/bin/notify) is used to abstract the ntfy HTTP API. It requires no virtual environments or pip dependencies.

Required Environment Variables (e.g., in ~/.zshrc):

export NTFY_URL="https://ntfy.parthjain.tech"
export NTFY_TOKEN="tk_REDACTED"
export NTFY_TOPIC="parth-apps"
export PATH="$HOME/bin:$PATH"

Basic Usage:

# Standard notification
notify "Hello from the server"

# With source and title
notify --source homelab --title "Backup complete" "Nightly backup completed successfully"

# With priority and URL
notify --source reader --title "Article ready" --priority high --url "https://reader.parthjain.tech/articles/123" "Processing finished"

Cron & Script Usage:

Cron jobs must use the absolute path (/home/<user>/bin/notify) as interactive shell paths and env vars may not load. Keep the source naming short and stable (e.g., backup, reader, docker). Titles render as [source] Title.

Direct API Testing (Without CLI):

curl -i -H "Authorization: Bearer $NTFY_TOKEN" -d "curl auth test" "$NTFY_URL/$NTFY_TOPIC"

🛠️ Troubleshooting

  • HTTP 403 Forbidden: Verify the user's ACLs and token validity. If curl works but the Python script fails, check the User-Agent. Cloudflare blocks default Python user agents. The CLI explicitly uses User-Agent: parth-notify/1.0 to bypass this.
  • Docker Compose Errors (not found): Ensure you are executing commands from ~/docker/ntfy. Alternatively use docker compose -f ~/docker/ntfy/docker-compose.yml logs.
  • Checking Logs & Status:
  • Logs: docker compose logs --tail=50 -f ntfy (Run from ~/docker/ntfy)
  • Status: docker ps --filter name=ntfy

📐 Design Rules

  • Notifications are side-effects: A failed notification must not cause the primary application/script to fail.
  • Keep it simple: Do not implement databases, delivery tracking, message brokers (Redis/RabbitMQ), or retry queues unless an application explicitly demands it. ntfy is the service.
  • Do not preemptively over-engineer: Features like separate app tokens, tags, actions, or Apprise routing should only be added when actively needed.