Run your own ntfy server
Keep your messages on your own hardware, add logins, set your own limits, and still get instant notifications on your iPhone with NTFY me.
The short version
Run the standard, unmodified ntfy server and add one line to its config:
upstream-base-url: "https://ntfy-me.com"
That's the only NTFY me-specific part. Everything else is a normal ntfy install. Already running ntfy? Jump to Switch an existing server.
How instant notifications work
iPhones only accept push notifications from Apple, and Apple only delivers them for the app's own push service. Your server can't reach your phone directly, so it sends a tiny "wake up" signal through ntfy-me.com:
The message itself never leaves your server except to go straight to your phone. This is the same mechanism the official ntfy app uses with ntfy.sh, pointed at ntfy-me.com because NTFY me has its own push channel.
Without the upstream-base-url line, NTFY me still works with your server, but messages show up only when you open the app.
What you need
- A computer that stays on: a home server, NAS, Raspberry Pi or a small VPS, with Docker installed.
- An address your phone can reach, ideally a domain with HTTPS, such as
https://ntfy.example.com. A VPN address (Tailscale, WireGuard) works too, as long as your phone is connected to it when messages arrive. - Outgoing internet access from the server to
https://ntfy-me.com.
Install with Docker
Replace ntfy.example.com with your own domain everywhere below.
-
Create a folder and a
compose.ymlfile.mkdir ntfy && cd ntfy
Put this in
compose.yml:services: ntfy: image: binwiederhier/ntfy container_name: ntfy command: serve environment: NTFY_BASE_URL: https://ntfy.example.com NTFY_UPSTREAM_BASE_URL: https://ntfy-me.com NTFY_CACHE_FILE: /var/lib/ntfy/cache.db NTFY_AUTH_FILE: /var/lib/ntfy/auth.db NTFY_ATTACHMENT_CACHE_DIR: /var/lib/ntfy/attachments NTFY_BEHIND_PROXY: "true" volumes: - ./ntfy-data:/var/lib/ntfy ports: - 127.0.0.1:8080:80 restart: unless-stoppedNTFY_BASE_URLmust be exactly the address you'll type into the app, with the samehttps://, the same host name and no trailing slash. The push signal is matched on it, so a mismatch is the most common reason notifications don't arrive instantly.Prefer a config file? Every variable has a
server.ymlequivalent:base-url,upstream-base-url,cache-fileand so on. See the ntfy config reference. -
Start it.
docker compose up -d
Messages, logins and attachments are stored in the
ntfy-datafolder next to the file. -
Put HTTPS in front of it. The server listens on
127.0.0.1:8080. Any reverse proxy works. With Caddy, which gets a certificate for you, the wholeCaddyfileis:ntfy.example.com { reverse_proxy localhost:8080 }Using nginx, Traefik or Cloudflare Tunnel instead? Point it at
http://localhost:8080. The ntfy config reference has nginx and Apache examples. -
Check it from outside.
curl https://ntfy.example.com/v1/health
You should see
{"healthy":true}. -
Subscribe in NTFY me. Tap +, turn on Use another server, enter
https://ntfy.example.comand a topic name. To make your server the default for every new topic, set it in Settings → Default server instead. -
Send a test.
curl -d "Hello from my own server" https://ntfy.example.com/your-topic
A banner should appear within a second or two, even with the app closed.
Updating later: docker compose pull && docker compose up -d.
Optional: require a login
By default anyone who knows a topic name can use your server. To lock it down:
-
Deny anonymous access. Add this line under
environment:incompose.yml, then rundocker compose up -dagain:NTFY_AUTH_DEFAULT_ACCESS: deny-all
-
Create a user. It asks for a password.
docker exec -it ntfy ntfy user add --role=admin alice
Admins can use every topic. For per-topic permissions, create a normal user and grant access with
ntfy access; see access control. -
Sign in from the app. In NTFY me, open Settings → Users → Add user and enter your server address, username and password. The app uses that login for every topic on that server, including when it fetches a message after a push.
-
Send with credentials. Either a password:
curl -u alice:your-password -d "Backup finished" https://ntfy.example.com/backups
or, better for scripts, an access token you can revoke:
docker exec ntfy ntfy token add alice curl -H "Authorization: Bearer tk_..." -d "Backup finished" https://ntfy.example.com/backups
Behind Cloudflare Access or another login proxy? Add its headers in Settings → Server headers. They are stored in the Keychain and only sent to that server.
Switch an existing server
If your server already has upstream-base-url: "https://ntfy.sh" for the official ntfy iOS app, change it to ntfy-me.com:
# server.yml upstream-base-url: "https://ntfy-me.com" # or, with Docker environment variables NTFY_UPSTREAM_BASE_URL: https://ntfy-me.com
Restart the server. Nothing else changes: topics, users, tokens, URLs and your scripts all stay the same.
One server, one upstream. ntfy forwards to a single upstream. After the switch, NTFY me gets instant notifications from your server and the official ntfy iOS app no longer does: it then shows messages only when opened. Android, desktop and web clients aren't affected; they connect to your server directly.
Then add your topics in NTFY me (+ → Use another server) and your login (Settings → Users). The migration guide has a checklist.
Troubleshooting
Messages only show up when I open the app
The push signal isn't reaching your phone. Check, in this order:
- The upstream line is active. It's set and the server was restarted. With Docker:
docker compose config | grep UPSTREAM. - The base URL matches the app.
base-urlon the server and the server address in the app must be identical (httpsvshttp,www., a port number all count). If they differ, change one and re-add the topic. - The server can reach ntfy-me.com.
docker exec ntfy wget -qO- https://ntfy-me.com/v1/healthshould print{"healthy":true}. - Notifications are allowed in iPhone Settings → Notifications → NTFY me.
- You're under the relay limit. ntfy-me.com accepts up to 2,000 relayed messages a day from one server (one IP address), in bursts of up to 120. Beyond that the messages still reach the app, just without an instant banner.
The banner only says "New message"
The phone was woken up but couldn't fetch the message from your server. Usually the server isn't reachable from where the phone is (for example a home-only address while you're on mobile data), or the server needs a login the app doesn't have yet (Settings → Users). Open the app on a network that can reach the server and the message appears.
The app can't subscribe, or says unauthorized
Open https://ntfy.example.com/v1/health in Safari on the phone. If it doesn't load, the phone can't reach the server (DNS, firewall, VPN). If it loads but subscribing fails, add your login under Settings → Users, or your proxy's headers under Settings → Server headers.
The official ntfy iOS app stopped getting notifications
Expected after switching upstream-base-url to ntfy-me.com; see One server, one upstream.
Does it work over plain http?
Yes, the app accepts http:// servers, but messages and passwords then travel unencrypted. Use HTTPS for anything beyond a quick test.
What ntfy-me.com sees
For each message on your server, ntfy-me.com receives only:
- a SHA-256 hash of the topic address (not the topic name or server address),
- the message ID, and
- your server's IP address and the time, which are used for rate limiting.
It never receives the title, message, tags or attachments. The same goes for Apple and Google, which carry the push. Full details: Privacy Policy.