[{"data":1,"prerenderedAt":188},["ShallowReactive",2],{"seo-verification":3,"blog-self-host-ntfy-on-a-vps-push-notifications-for-scripts-and-cicd-ma-en":6,"promo-bar-likely":187},{"google":4,"bing":5},"EycwPY2XMyTkVzas3n1ygeNJFGAH513qrMjfDljzsMQ","",{"key":7,"data":8},"blog-self-host-ntfy-on-a-vps-push-notifications-for-scripts-and-cicd-ma-en",{"id":9,"slug":10,"slugs":11,"title":15,"excerpt":16,"readTime":17,"views":18,"isPinned":19,"noindex":19,"publishedAt":20,"updatedAt":21,"category":22,"categories":28,"featuredImage":30,"bgImage":31,"posterImage":32,"relatedSolution":33,"intro":36,"sections":37,"ctaTitle":124,"ctaBody":125,"ctaButton":126,"ctaUrl":127,"relatedPosts":128},194,"self-host-ntfy-on-a-vps-push-notifications-for-scripts-and-cicd",{"fr":12,"en":10,"ar":13,"es":14},"self-host-ntfy-vps","استضافة-ntfy-على-vps-إشعارات-فورية-من-سكريبتاتك-وcicd","autoalojar-ntfy-en-un-vps","Self-host ntfy on a VPS: push notifications for scripts and CI\u002FCD","Deploy ntfy on your VPS and send push notifications from any script, cron job or CI\u002FCD pipeline with a plain curl — no SaaS, no API key, no subscription.",16,2,false,"2026-07-28T00:00:00+00:00","2026-11-04T16:30:26+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":27},8,"Security & Monitoring","securite-monitoring","bg-rose-500\u002F10 text-rose-400","security",[29],{"id":23,"name":24,"slug":25,"color":26,"icon":27},null,"\u002Fblog\u002Fcovers\u002Fbg.svg","\u002Fblog\u002Fcovers\u002Fself-host-ntfy-vps-poster.svg",{"categorySlug":34,"appSlug":35},"monitoring-observability","ntfy","Slack charges per seat. Emails get lost in the noise. Discord webhooks vanish in a thread. ntfy (Apache 2.0, ~32k ⭐, v2.30.0 — Oct. 2026) takes a different approach: an HTTP pub\u002Fsub server you self-host on your VPS, where any script sends an alert with a plain `curl -d`. The mobile app receives the push notification in real time — even off the local network. Single Docker container, \u003C 30 MB RAM, no third-party account required. In this article: deployment, authentication, multi-tenant configuration with `server.yml`, Backrest \u002F Uptime Kuma \u002F cron integrations, SMTP notifications, and the five real errors every admin hits in production.",[38,42,55,58,86,89,92,96,99,102,112,115,118,121],{"type":39,"title":40,"body":41},"h2","The problem: too many monitoring tools, too many notification channels","Uptime Kuma monitors your services, Beszel monitors your VPS metrics, Backrest manages your backups, and your cron jobs go silent at night — except when they fail. Each has its own notification channel: Slack, email, webhook, Telegram. The result: five different integrations to maintain, five alert formats to read, and no unified view when something breaks at 3 AM. ntfy solves this from the other direction: you host the notification hub, and every tool or script sends its alerts over plain HTTP. A single topic to watch on your phone, zero dependency on a third-party service.",{"type":43,"title":44,"items":45},"ul","What ntfy gives you out of the box",[46,47,48,49,50,51,52,53,54],"Send a notification with a plain curl — `curl -d \"Build failed\" https:\u002F\u002Fntfy.example.com\u002Fci-alerts` — zero SDK, zero library.","Receive on Android (F-Droid \u002F Play Store), iOS (App Store), desktop browser and the ntfy web UI — all subscribe to the same topic via WebSocket or SSE.","Notification actions — buttons in the push that trigger HTTP callbacks, open URLs or run commands on the subscriber device.","Scheduled notifications — delayed delivery (`X-Delay: 30min`) to schedule reminders from scripts.","Priority levels (min \u002F low \u002F default \u002F high \u002F urgent) with DND bypass for urgent alerts.","Attachments — images or files up to 15 MB via the `X-Attach` header or multipart upload.","SMTP notifications — ntfy can send a fallback email if your mobile device is offline or the app is not installed.","Granular access control — users, hashed passwords, access tokens and per-topic ACLs via `ntfy user` and `ntfy access`.","\u003C 30 MB RAM, SQLite (or PostgreSQL for high-throughput deployments), single container — the lightest self-hosted push notification server in its class.",{"type":39,"title":56,"body":57},"Architecture: single-container HTTP pub\u002Fsub","ntfy is a Go binary that runs in a single Docker container. It exposes a simple HTTP interface: `PUT` or `POST` to `\u002Fyour-topic` publishes a message; any client listening to that topic (via WebSocket, SSE or the mobile app) receives it instantly. Messages are cached in SQLite (`\u002Fvar\u002Flib\u002Fntfy\u002Fcache.db`) with configurable retention (12 hours by default). In production, ntfy is expected to sit behind an nginx reverse proxy that handles TLS — `behind-proxy: true` in `server.yml` tells ntfy to read the real client IP from `X-Forwarded-For`. All advanced configuration — authentication, SMTP, access control — lives in a single `\u002Fetc\u002Fntfy\u002Fserver.yml` file mounted as a Docker volume.",{"type":59,"title":60,"steps":61},"steps","Deploy ntfy in production: 8 steps",[62,65,68,71,74,77,80,83],{"title":63,"body":64},"Deploy ntfy from the ServOrbit marketplace","Open your ServOrbit dashboard → Marketplace → Monitoring → ntfy → Deploy. Docker pulls `binwiederhier\u002Fntfy:v2.30.0`, starts ntfy on port 8080, and the web UI is accessible within 5 seconds. An nginx reverse proxy with a Let's Encrypt certificate is automatically configured on your chosen domain.",{"title":66,"body":67},"Send your first notification","From any terminal with curl:\n`curl -d \"Hello from my VPS\" https:\u002F\u002Fntfy.example.com\u002Fmy-topic`\nOpen the ntfy web UI at the same address, navigate to `my-topic` and your message appears live. Topics are created on first publish — no prior setup needed.",{"title":69,"body":70},"Create the full server.yml","Mount a configuration file into the container. Here is a production `server.yml`:\n`base-url: https:\u002F\u002Fntfy.example.com`\n`listen-http: \":8080\"`\n`behind-proxy: true`\n# SQLite cache (12h)\n`cache-file: \u002Fvar\u002Flib\u002Fntfy\u002Fcache.db`\n`cache-duration: 12h`\n# Authentication: nobody can read or write without an account\n`auth-file: \u002Fvar\u002Flib\u002Fntfy\u002Fuser.db`\n`auth-default-access: deny-all`\n# Outbound SMTP — ntfy sends fallback emails\n`smtp-sender-addr: smtp.example.com:587`\n`smtp-sender-user: notifications@example.com`\n`smtp-sender-pass: smtpPassword`\n`smtp-sender-from: notifications@example.com`\nRestart the container after any change to this file: `docker restart ntfy`.\nTo get notifications on iOS as well, add `upstream-base-url: \"https:\u002F\u002Fntfy.sh\"` to this file: the \"iOS\" section below explains why.",{"title":72,"body":73},"Create users and access tokens","Access tokens let your scripts publish without exposing a password:\n# Create an interactive admin user\n`docker exec -it ntfy ntfy user add --role=admin admin`\n# Create a dedicated user for scripts\n`docker exec -it ntfy ntfy user add scripts`\n# Generate an access token for that user\n`docker exec -it ntfy ntfy token add scripts`\n# Output: tk_AbCdEf123456...\n# Grant that user read\u002Fwrite on a specific topic\n`docker exec -it ntfy ntfy access scripts server-alerts rw`\nThe token `tk_AbCdEf123456` replaces the password in all your scripts.",{"title":75,"body":76},"Subscribe on your phone with authentication","Install the ntfy app (Android: F-Droid or Play Store; iOS: App Store). Tap + → enter your server URL → the topic name. When ntfy asks for credentials, enter the admin username and password (or a dedicated read-only account). You will receive push notifications as soon as anything is published — via UnifiedPush (Android F-Droid), FCM (Android Play) or APNs (iOS). On iOS, instant push only arrives if your server has `upstream-base-url` set (see the dedicated section below).",{"title":78,"body":79},"Alert from cron jobs using a token","Use the access token in the `Authorization` header:\n#!\u002Fbin\u002Fbash\n`NTFY_URL=\"https:\u002F\u002Fntfy.example.com\"`\n`NTFY_TOKEN=\"tk_AbCdEf123456\"`\n`\u002Fusr\u002Flocal\u002Fbin\u002Fbackup.sh`\n`STATUS=$?`\n`if [ $STATUS -eq 0 ]; then`\n`curl -s -H \"Authorization: Bearer $NTFY_TOKEN\"        -H \"Title: Nightly backup\" -H \"Priority: default\"        -H \"Tags: white_check_mark\"        -d \"Backup completed at $(date)\"        \"$NTFY_URL\u002Fcron-jobs\"`\n`else`\n`curl -s -H \"Authorization: Bearer $NTFY_TOKEN\"        -H \"Title: Backup FAILED\" -H \"Priority: urgent\"        -H \"Tags: warning\"        -d \"Backup failed — exit code $STATUS\"        \"$NTFY_URL\u002Fcron-jobs\"`\n`fi`",{"title":81,"body":82},"Configure Backrest and Uptime Kuma","**Backrest**: in the Backrest UI → Plans → your plan → Hooks → Add Hook → type `Webhook (ntfy)` → enter the URL `https:\u002F\u002Fntfy.example.com\u002Fbackups` and the Bearer token. Backrest notifies on every completed or failed backup with snapshot stats.\n\n**Uptime Kuma**: Settings → Notifications → New → type `ntfy` → Server URL: `https:\u002F\u002Fntfy.example.com` → Topic: `server-alerts` → Username \u002F Password. Test the notification from the UI before saving.\n\n**Grafana**: Contact points → New → type `webhook` → URL `https:\u002F\u002Fntfy.example.com\u002Fgrafana` → HTTP headers: `Authorization: Bearer tk_AbCdEf123456` + `Content-Type: text\u002Fplain`.",{"title":84,"body":85},"Enable fallback email notifications (SMTP)","Once `smtp-sender-addr` is set in `server.yml`, send a notification with the `X-Email` header:\n`curl -H \"Authorization: Bearer $NTFY_TOKEN\"      -H \"Title: Critical alert\"      -H \"X-Email: you@example.com\"      -d \"Disk full at 95%\"      https:\u002F\u002Fntfy.example.com\u002Fserver-alerts`\nntfy sends both the push notification and an email. Useful when the mobile app is not installed, or to archive incidents in your inbox.",{"type":39,"title":87,"body":88},"Authentication and access tokens: never expose a public topic","By default, ntfy starts with no access control (the docs give `auth-default-access` as `read-write`) — anyone who knows the URL can read and write. In production, three layers of protection stack up: first `auth-default-access: deny-all` locks out all unknown requests; then users created with `ntfy user add` with bcrypt-hashed passwords stored in `user.db`; finally access tokens (`ntfy token add`) tied to a specific user and revocable independently of the password. A compromised token can be revoked without changing the user's password. Per-topic ACLs (`ntfy access`) provide compartmentalization: the `backrest` user can only write to `backups` and never sees `server-alerts`.",{"type":39,"title":90,"body":91},"Multi-tenant configuration: one server, multiple teams","ntfy supports multiple namespaces on the same server through topic naming conventions. Create one user per service or team, grant rights only to that user's topics, and each service believes it has its own dedicated hub. The `admin` role sees everything; regular `user` role accounts are scoped to their ACLs. For teams that must be fully isolated (separate billing, separate data), deploy two ntfy containers on two distinct subdomains.",{"type":93,"title":94,"body":95},"tip","Tip: use topic names as namespaces and set up a nightly mute strategy","Organise your alerts by source: `ci-builds` for GitHub Actions\u002FGitLab CI, `server-alerts` for Uptime Kuma\u002FBeszel, `cron-jobs` for batch scripts, `backups` for Backrest. The ntfy app lets you mute specific topics at night (e.g., `cron-jobs`) while keeping `server-alerts` at maximum priority with DND bypass. No server-side configuration — just the topic name in the curl command and the app's notification preferences.",{"type":39,"title":97,"body":98},"Troubleshooting: 5 real production errors","**1. `HTTP 403 Forbidden` on publish or subscribe**\nMessage: `{\"code\":40301,\"http\":403,\"error\":\"Unauthorized\"}`\nCause: `auth-default-access: deny-all` is active and the token is missing or expired. Check: `curl -v -H \"Authorization: Bearer tk_...\" https:\u002F\u002Fntfy.example.com\u002Fmy-topic`. Regenerate the token with `ntfy token add \u003Cuser>` and update your scripts.\n\n**2. Notification sent but never received on mobile**\nOn Android, first check that the app is subscribed to your instance and not to the public `ntfy.sh` server (URL in the app settings). On iOS, the most likely cause is a missing `upstream-base-url: \"https:\u002F\u002Fntfy.sh\"` in `server.yml`: without this relay, the docs warn that delivery can take hours. Set it, then run through the checklist in the iOS section below. `base-url` must also be set, but the docs tie it to attachment URLs and email footers, not to push delivery.\n\n**3. Wrong links in outbound emails**\nSMTP emails sent by ntfy contain links back to the notifications. Without `base-url` in `server.yml`, the email footer (link back to the notification) is built without the public URL and is unusable externally. Fix: set `base-url: https:\u002F\u002Fntfy.example.com` and restart the container.\n\n**4. Repeated WebSocket drops in production (reconnects every 30 s)**\nCause: nginx keepalive timeout is shorter than ntfy's heartbeat interval (`keepalive-interval: \"45s\"` in the docs' example configuration). The proxy closes the WebSocket before ntfy can send its heartbeat. Add to the nginx `location` block: `proxy_read_timeout 60;` and `proxy_send_timeout 60;`. Also verify that `proxy_http_version 1.1;` and `proxy_set_header Upgrade $http_upgrade;` are present — without them, WebSocket degrades to SSE long-polling.\n\n**5. OOM (Out of Memory) on a high-throughput topic**\nntfy buffers recent messages in memory before writing to SQLite. On a topic receiving hundreds of messages per second (e.g., unfiltered real-time logs), the in-memory buffer can exhaust a 512 MB VPS. Immediate fix: reduce `cache-duration` to `1h` or `30m`. Structural fix: never use ntfy as a real-time log sink — filter at the script level (`| grep -E 'ERROR|WARN'`) and push only actionable events. For high-throughput deployments, ntfy can use PostgreSQL instead of SQLite for its stores (see the configuration docs).",{"type":39,"title":100,"body":101},"iOS: why a self-hosted ntfy goes through ntfy.sh for push","On iOS an app cannot keep a connection open in the background: the ntfy documentation explains that this restriction makes instant push impossible without a central server. For every message, your self-hosted server therefore forwards a \"poll\" request to an upstream server, `ntfy.sh` by default, which takes care of waking the app. The setting is `upstream-base-url: \"https:\u002F\u002Fntfy.sh\"`. That request carries only the message ID (`X-Poll-ID` header) and the SHA-256 hash of the topic URL, never the text: the self-hosted server literally sends \"New message\", and the app then fetches the real content from your server. Without `upstream-base-url`, the documentation warns that notifications eventually arrive, but delivery can take hours (20 to 30 minutes if the phone is actively in use). `upstream-access-token` is optional: the docs only reserve it for when the upstream server's limits are exceeded or when it is protected.",{"type":43,"title":103,"items":104},"iOS checklist: seven checks before digging deeper",[105,106,107,108,109,110,111],"**`base-url`** — set to the public HTTPS URL of your server, the one your phone can reach.","**`upstream-base-url`** — present in `server.yml` (or as an environment variable), value `https:\u002F\u002Fntfy.sh`, container restarted.","**The app's default server** — in the iOS app settings, your server URL is the one declared as the default server.","**Subscribed to the right server** — the topic was added by choosing your server, not `ntfy.sh`: a topic with the same name on the public server is a different topic.","**Upstream token** — `upstream-access-token` is only for when the relay's limits are reached; without an established cause, do not add it by reflex hoping it fixes things.","**Server logs** — check that the message is published and then forwarded to the relay. If it is, your side works and the problem sits between the relay, Apple and the phone.","**Phone settings** — Background App Refresh and Notifications enabled for the app, and no DNS blocker (AdGuard Home, Pi-hole) filtering `ntfy.sh`.",{"type":39,"title":113,"body":114},"When nothing arrives anyway: what issues #1305 and #1844 really say","These two issues in the ntfy repository describe the symptom you are chasing: a self-hosted server, an iOS push that does not show, messages that appear only when you refresh the app by hand. Read on 30 October 2026, both are open and neither names a confirmed cause.\n\n**#1305 \"No push notifications on ios?\"** (opened in March 2025, labels ios and bug). The reporter gets no banner, neither with ntfy.sh nor with their own instance. Comments say reinstalling the app helped, others that the problem came back months later. A contributor proposes that the app re-subscribes to the Firebase topics at launch and opened a draft PR, stating they could not test it. A maintainer asks the reporter whether the Firebase subscription is the cause, without concluding.\n\n**#1844 \"No iOS Push notification, also with valid subscription\"** (opened in July 2026, labels ios, server and bug). The reporter runs ntfy in Docker with `NTFY_UPSTREAM_BASE_URL` set to `https:\u002F\u002Fntfy.sh` and `NTFY_UPSTREAM_ACCESS_TOKEN` filled in. They say they hit the free limit of 250 messages per day, then took a paid subscription. Their logs show the message published and sent to the relay, and still no push. A contributor answers that 429 errors can lead to an IP ban of up to 4 hours, notes that the reporter's `curl` test succeeds (so no ban), and that the account showed 48 messages sent out of 2,500. They add that iOS notifications are finicky without anyone knowing exactly why. Reported workaround: switch \"Background App Refresh\" and \"Notifications\" off and on again in the iOS settings. The author writes they do not know why this works; the problem came back, then went away with no known cause, and another user confirms the workaround in September 2026. Another report ties a similar case to DNS blocklists.\n\n**What you can conclude, and what you cannot.** Neither a daily message limit nor a 429 block is established as the cause in these threads: the 429 is ruled out for the reporter's IP, and the 250-message limit is a figure given by the reporter, not by the configuration documentation. The upstream token does not fix the problem there. Treat the list above as checks, not as a guaranteed fix.",{"type":93,"title":116,"body":117},"Tip: do not trust a critical alert to iOS push alone","As long as these issues stay open, double the alerts that matter: add the `X-Email` header (see the SMTP step) to also get an email, or keep a second channel on the phone. Contributors in both issues also suggest the PWA (web push) as an alternative to the iOS app. And test end to end after each app or iOS update, with the watchdog heartbeat described above.",{"type":39,"title":119,"body":120},"Advanced integrations: Backrest, Uptime Kuma, Grafana","**Backrest** natively supports ntfy as a hook type: configure the topic URL and Bearer token in the Backrest UI, and every completed or failed snapshot automatically generates a push notification with the snapshot name, deduplicated size, and duration. Zero scripting required — the integration is built into the tool.\n\n**Uptime Kuma** supports ntfy natively in its notification manager. Configuration takes under a minute. Each monitor (HTTP, TCP port, ping, TLS certificate) can have multiple notification channels — combine ntfy for mobile push and ntfy's SMTP for auditability.\n\n**Grafana** uses generic webhooks. Create a contact point of type `webhook`, point it at `https:\u002F\u002Fntfy.example.com\u002Fgrafana`, add the `Authorization: Bearer tk_...` and `Content-Type: text\u002Fplain` headers. Grafana alerts (CPU > 90%, P99 latency, disk > 80%) arrive immediately on your phone with the rule name in the notification title.",{"type":93,"title":122,"body":123},"Tip: test the full delivery chain before relying on it","Before wiring your infrastructure to ntfy, test the complete chain: send a notification with `curl`, confirm it appears in the web UI, verify it arrives on the mobile app, and check that the SMTP fallback email lands in the inbox. A silently non-delivering ntfy is worse than no monitoring at all — you believe you are being notified, but you are not. Schedule a `*\u002F30 * * * *` cron that sends a heartbeat to a `watchdog` topic — if the mobile app does not receive this heartbeat within 30 minutes, ntfy itself is down.","Your notification hub under your control","Deploy ntfy on a ServOrbit VPS and centralise all your monitoring, CI\u002FCD and cron alerts in one self-hosted notification hub — no SaaS, no subscription.","Deploy ntfy","\u002Fapplications\u002Fmonitoring-observability\u002Fntfy",[129,146,169],{"id":130,"slug":131,"slugs":132,"title":136,"excerpt":137,"readTime":23,"views":18,"isPinned":19,"noindex":19,"publishedAt":138,"updatedAt":139,"category":140,"categories":141,"featuredImage":30,"bgImage":31,"posterImage":143,"relatedSolution":144},133,"self-host-beszel-multi-server-monitoring-hub-on-your-vps",{"fr":133,"en":131,"ar":134,"es":135},"self-host-beszel-vps","استضف-beszel-على-vps-مركز-مراقبة-متعدد-الخوادم","alojar-beszel-hub-monitorizacion-vps","Beszel on VPS: multi-server monitoring, alerts and security","Deploy Beszel on a VPS, monitor your entire fleet from one hub, set up Telegram\u002Femail alerts, enable 2FA and fix silent agents, TLS errors and blocked ports.","2026-02-14T00:00:00+00:00","2026-11-04T16:30:25+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":27},[142],{"id":23,"name":24,"slug":25,"color":26,"icon":27},"\u002Fblog\u002Fcovers\u002Fself-host-beszel-vps-poster.svg",{"categorySlug":34,"appSlug":145},"beszel",{"id":147,"slug":148,"slugs":149,"title":153,"excerpt":154,"readTime":155,"views":18,"isPinned":19,"noindex":19,"publishedAt":156,"updatedAt":157,"category":158,"categories":164,"featuredImage":30,"bgImage":31,"posterImage":166,"relatedSolution":167},167,"how-to-self-host-dozzle-on-a-vps-real-time-docker-log-viewer",{"fr":150,"en":148,"ar":151,"es":152},"self-host-dozzle-vps","استضافة-dozzle-على-vps-عارض-سجلات-docker-لحظيا","alojar-dozzle-en-un-vps","How to Self-Host Dozzle on a VPS: Real-Time Docker Log Viewer","Deploy Dozzle on your VPS — a stateless, open-source Docker log viewer. Stream, search and tail logs from all your containers in the browser, without SSH or log storage.",4,"2026-07-06T00:00:00+00:00","2026-09-07T11:26:10+00:00",{"id":159,"name":160,"slug":161,"color":162,"icon":163},7,"Self-hosting","self-hosting","bg-indigo-500\u002F10 text-indigo-400","cloud",[165],{"id":159,"name":160,"slug":161,"color":162,"icon":163},"\u002Fblog\u002Fcovers\u002Fself-host-dozzle-vps-poster.svg",{"categorySlug":34,"appSlug":168},"dozzle",{"id":170,"slug":171,"slugs":172,"title":176,"excerpt":177,"readTime":178,"views":179,"isPinned":19,"noindex":19,"publishedAt":180,"updatedAt":157,"category":181,"categories":182,"featuredImage":30,"bgImage":31,"posterImage":184,"relatedSolution":185},182,"self-host-changedetectionio-on-a-vps-monitor-any-website",{"fr":173,"en":171,"ar":174,"es":175},"self-host-changedetection-vps","استضافة-changedetectionio-على-vps-مراقبة-تغييرات-المواقع","alojar-changedetection-io-en-un-vps","Self-host Changedetection.io on a VPS: monitor any website","Deploy Changedetection.io on your VPS: open-source website change detection, CSS\u002FXPath filters, 80+ notification channels. v0.60.x regressions and safe update guide.",9,0,"2026-07-17T00:00:00+00:00",{"id":159,"name":160,"slug":161,"color":162,"icon":163},[183],{"id":159,"name":160,"slug":161,"color":162,"icon":163},"\u002Fblog\u002Fcovers\u002Fself-host-changedetection-vps-poster.svg",{"categorySlug":34,"appSlug":186},"changedetection",true,1794270492139]