Self-Host ntfy: Push Notifications via curl for Your Homelab

Self-Host ntfy: Push Notifications via curl for Your Homelab

Self-host ntfy to send push notifications to your phone from any script or cron job using nothing but curl. A quick Docker setup for your homelab.

đź’ˇ Disclosure: This article contains affiliate links. If you make a purchase through these links, we may earn a small commission at no extra cost to you. This helps support the site and keeps the content free.

I run a lot of cron jobs. Backups, monitoring checks, certificate renewal reminders, “is my server still alive?” pings. For years, I checked them by SSH-ing in and grepping logs. That got old fast.

I tried Gotify first. It works — I wrote about it before. But there’s another option that takes simplicity to an absurd level: ntfy. You send a POST request with curl, and a notification appears on your phone. That’s it.

No SDK. No config file. No auth (unless you want it). Just curl -d "message" ntfy.sh/topic and your phone buzzes.

Why ntfy over Gotify?

I’ll be honest — both are great. Here’s why I ended up running both.

Gotify is better if you want a full push notification server with user management, plugins, and an app that feels “finished.” It’s the right choice if you’re setting this up for a family or a team.

ntfy is better if you want zero friction. The client app subscribes to topics. That’s the whole setup. You don’t log in, you don’t create users, you just pick a topic name and start publishing.

The killer feature for ntfy, though, is that publishing works over plain HTTP. Gotify requires you to send a token in every request. ntfy works with curl, HTTPie, wget, or even a browser URL. I have a one-liner in my backup script that took ten seconds to write:

curl -d "Backup complete on $(hostname)" ntfy.sh/my-backup-topic

That’s not an exaggeration. That’s the whole integration.

Self-hosting ntfy with Docker

You can use ntfy’s public server for testing. I did for about a day before I decided I wanted my notifications staying on my own hardware.

Here’s the Compose setup I run:

# ~/apps/ntfy/docker-compose.yml
services:
  ntfy:
    image: binwiederhier/ntfy
    container_name: ntfy
    restart: unless-stopped
    command:
      - serve
    environment:
      - TZ=UTC
    volumes:
      - ./data:/etc/ntfy
      - ./cache:/var/cache/ntfy
    ports:
      - "2586:80"
    healthcheck:
      test: ["CMD-SHELL", "wget -q --tries=1 http://localhost:80/v1/health -O /dev/null || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3

Create a config file in ./data/server.yml:

# ./data/server.yml
base-url: "https://ntfy.yourdomain.com"
cache-file: "/var/cache/ntfy/cache.db"
auth-file: "/var/cache/ntfy/auth.db"
upstream-base-url: ""

# Optional: attach to public topic for discovery
# enable-public-topic: true

Bring it up:

docker compose up -d

Put it behind your reverse proxy (Caddy, Traefik, or nginx) with a subdomain like ntfy.yourdomain.com, and you’re live.

Setting up the mobile app

This is the part that still surprises me every time. Install the ntfy app from F-Droid, Play Store, or the App Store. Open it. Tap the ”+” button. Enter your topic name and your server URL (like https://ntfy.yourdomain.com).

That’s the whole setup. No login. No API key. No QR code scanning. The app just subscribes to that topic on your server.

Security note: Without auth, anyone who guesses your topic name can subscribe. If you’re sending anything sensitive, add authentication. ntfy supports token-based and user/password auth — check the official docs.

What I use it for

Here are the notifications I actually send via ntfy:

Backup completion

#!/bin/bash
restic backup /data && \
  curl -d "Restic backup finished: $(date)" ntfy.sh/my-backups

Dead simple. If the backup fails, I don’t get a notification — and I’ve trained myself to notice the silence.

Certificate expiry warnings

days_left=$(openssl s_client -connect mydomain.com:443 </dev/null 2>/dev/null \
  | openssl x509 -noout -enddate \
  | cut -d= -f2 \
  | xargs -I{} date -d "{}" +%s)
now=$(date +%s)
remaining=$(( (days_left - now) / 86400 ))

if [ "$remaining" -lt 30 ]; then
  curl -d "SSL cert for mydomain.com expires in $remaining days" ntfy.sh/cert-alerts
fi

Set this as a weekly cron job and you’ll never get surprised by an expired certificate on a Sunday afternoon.

Server reboot alerts

# /etc/cron.d/reboot-notify
@reboot curl -d "$(hostname) rebooted at $(date)" ntfy.sh/server-events

I cannot tell you how many times this has saved me. A server reboots silently in the middle of the night, and you find out when someone asks why the service is down. Not anymore.

High disk usage

#!/bin/bash
usage=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$usage" -gt 85 ]; then
  curl -d "Disk usage at ${usage}% on $(hostname)" ntfy.sh/alerts
fi

The things I wish I knew sooner

A few gotchas I bumped into:

Topic names are case-sensitive. I used MyBackups once and my-backups another time and spent twenty minutes wondering why my phone wasn’t buzzing. Pick a convention and stick to it.

Don’t use the public server for anything real. The public ntfy.sh server is fine for testing. But topic names are essentially public URLs — anyone who guesses yours can read your notifications. Self-host. It’s one Compose file.

ntfy supports priorities and click actions. You can add tags, set a priority (1-5), and even define a click action that opens a URL when you tap the notification. Here’s a richer example:

curl -H "Title: Backup done" \
     -H "Tags: white_check_mark" \
     -H "Priority: 5" \
     -H "Click: https://ntfy.yourdomain.com/backups" \
     -d "All files synced successfully to Backblaze B2" \
     ntfy.sh/my-backups

That sends a notification with a green checkmark emoji, high priority (emergency-level vibrate), and tapping it opens the backup status page. Handy.

Where ntfy falls short

I’ve been running ntfy for about six months. Here’s what I’d love to see improve:

No built-in delivery receipts. With Gotify, you know if the client received the message. ntfy is fire-and-forget. For most of my use cases this is fine, but for critical alerts (like “server on fire”) I’d like confirmation.

No multi-user UI. If you want to let your family subscribe to different topics, ntfy works — but there’s no web dashboard for managing users or viewing message history. Gotify has a nice web UI for browsing past notifications. ntfy expects you to check the mobile app.

Authentication setup is manual. Token-based auth works, but you have to create tokens via the CLI inside the container. Not a big deal, but worth knowing going in.

The bottom line

ntfy is the simplest push notification tool I’ve ever used. It occupies a weirdly specific niche: notifications that require zero setup and work from a shell one-liner.

🚀NordVPN

Running ntfy on a VPS? Secure your server with a reliable VPN.

Get NordVPN →

Affiliate link — we may earn a commission at no extra cost to you.

If you’re already running Gotify, stick with it. But if you want to add push notifications to a bash script without writing eleven lines of boilerplate, or if you’ve been putting off setting up notifications because it seems like a project — give ntfy a try. It’s one Compose file and a mobile app install away.

Try this: Set up the server, subscribe from your phone, then run curl -d "Hello from curl" ntfy.yourdomain.com/hello in your terminal. If your phone buzzes within two seconds (it will), you’ll understand why I wrote this post.

Stay in the loop 📬

Get self-hosting tutorials, tool reviews, and infrastructure tips delivered to your inbox. No spam, unsubscribe anytime.

Join 0 self-hosters. Free forever.