ntfy vs Gotify vs Apprise API: Self-Hosted Push Notifications on a VPS (2026)

ntfy vs Gotify vs Apprise API: Self-Hosted Push Notifications on a VPS (2026)

By Fanny Engriana · · 12 min read · 10 views

For the past two years I have run all my operational alerts — cron failures, blog import errors, SSH fail2ban trips, IndexNow rejections — through a single self-hosted WhatsApp gateway sitting on a Hostinger VPS. It works, mostly. But every few weeks it goes silent for an hour or six, usually right when a script is most loudly trying to tell me something has gone wrong. The gateway needs a manual launchctl kick before it comes back. After the third 3 a.m. wake-up call from a client whose blog had stopped publishing while my notifications were stuck in queue, I started looking at proper self-hosted push notification servers.

Three names keep coming up across r/selfhosted, the awesome-selfhosted list, and the Hacker News threads: ntfy, Gotify, and Apprise API. They all do the same broad thing — receive an HTTP POST and forward it to your phone — but the architecture, ergonomics, and what they are actually good at differ more than the surface-level comparison posts admit. I spent a week running all three on the same 2 vCPU / 4 GB Hostinger VPS that hosts CloudHostReview, pointed at the same workload (about 380 alert events per day across my 7 aggregator sites), and watched what broke.

This is what I learned, and which one I picked.

Server rack with notification status lights
Self-hosted push servers replace fragile gateway scripts with something a VPS can actually keep running.

What each one actually is

Before the comparison, a quick clarification because the three projects sit at different layers of the notification stack and I have seen people try to compare them as if they were direct substitutes. They are not, quite.

ntfy (v2.22.0, May 2026)

ntfy is a topic-based pub/sub HTTP notification server written in Go by Philipp Heckel. You publish to a URL like https://ntfy.example.com/my-alerts with a POST or PUT, and any client subscribed to that topic receives the message. There is a hosted free tier at ntfy.sh, but the project is Apache 2.0 and self-hosting is a single Go binary or Docker container. v2.22.0 shipped on 19 May 2026 and tightened a web-push regex to prevent an SSRF class of bug; v2.18.0 added native PostgreSQL support (previously SQLite only), and v2.17 added priority-field templating. Mobile clients exist for both Android (Play Store and F-Droid) and iOS — the iOS client is the one most people end up choosing ntfy for, because nothing else in this category has a maintained iOS app.

Gotify (v2.6.3, 2026)

Gotify is older, simpler, and architecturally more rigid. It is a WebSocket-based server-to-user notification system with a per-application token model: you create an application in the web UI, get a token, and any HTTP POST that includes that token becomes a message visible to anyone logged in as a user with read access to that application. The Android client is excellent and battery-efficient; there is no official iOS client and there will not be one (the maintainer has been explicit about this on GitHub). Gotify is the easiest of the three to set up — docker run -p 8080:80 gotify/server and you have a working server in under a minute — and the plugin system handles email-to-push and webhook ingest reasonably well.

Apprise API (caronc/apprise-api, 2026)

Apprise API is a different beast. It is a thin Flask wrapper around the apprise Python library, which itself is a unified dispatcher to 130+ downstream services: Slack, Discord, Telegram, Pushover, Pushbullet, email, ntfy, Gotify, Matrix, MQTT, Microsoft Teams, SignalCLI, and dozens more. It is not, in itself, a notification destination. It is a fan-out gateway that lets you write one HTTP call and have it land in five places at once, with the per-channel URL configuration stored server-side as named tags. If you already use Telegram or Discord for personal alerts and just want a central API your scripts can call, Apprise API is the right shape of tool.

So the honest framing: ntfy and Gotify are competing products. Apprise API is something you might run alongside either of them, or instead of either of them if you do not care about owning the delivery layer. I will treat them as a three-way comparison anyway because that is how the question gets asked, but keep that distinction in mind.

The test setup

I installed all three behind the same Caddy reverse proxy on a Hostinger KVM4 VPS (2 vCPU, 4 GB RAM, 80 GB NVMe, Frankfurt region). Docker Compose, one stack each:

  • ntfybinwiederhier/ntfy:v2.22.0, SQLite backend, behind ntfy.test.cloudhostreview.com
  • Gotifygotify/server:2.6.3, SQLite, behind gotify.test.cloudhostreview.com
  • Apprise APIcaronc/apprise:latest, with downstream targets configured for ntfy and a personal Telegram bot, behind apprise.test.cloudhostreview.com

The workload, captured from a week of real ops across my sites: 386 notification events per day on average, with bursty spikes during the 02:00 UTC daily import window when 7 cron scripts fire within a 90-minute window. Payload size is small — the median is 184 bytes, the 99th percentile is 1.2 KB (the occasional stack trace). Latency target: I want a notification on my phone within 10 seconds of the script firing it, including when I am off Wi-Fi.

Resource usage on the VPS

I let each server run for 48 hours under that workload and recorded docker stats samples every 5 minutes. Here is what I saw:

ServerRAM idleRAM peak (burst)CPU avgContainer image size
ntfy 2.22.018 MB47 MB0.3%34 MB
Gotify 2.6.311 MB28 MB0.2%16 MB
Apprise API62 MB118 MB0.8%312 MB

Gotify is the lightest by a meaningful margin and the smallest image. ntfy is a hair heavier but in the same ballpark — both fit comfortably on the kind of $4–6/month VPS where you would not want a Java app running. Apprise API is in a different league because it is Python and because it loads the full notification-target library on startup. None of these will stress a modern VPS, but if you are stacking services on a 1 GB nano box, the difference between 47 MB and 118 MB matters.

Latency from POST to phone

I scripted a loop that POSTed a numbered message every 15 seconds for an hour, and on the receiving phone I logged the arrival timestamp from the notification metadata. The median observed delivery times:

  • ntfy on Android via FCM: 1.4 seconds. On iOS via APNs: 2.1 seconds. WebSocket subscription (no mobile push): 0.3 seconds.
  • Gotify on Android via UnifiedPush + WebSocket: 0.9 seconds when the app was warm; 6.8 seconds median when the app had been backgrounded for more than 4 hours and Doze mode had kicked in.
  • Apprise API → Telegram: 1.7 seconds. → ntfy (chained): 2.3 seconds. → Discord: 0.9 seconds.

The Gotify Doze-mode regression is the headline number from this test. When the phone is awake and the Gotify app is fresh, it is the fastest. But the Gotify Android client maintains its own persistent WebSocket connection rather than going through Google's FCM, and Android aggressively kills that socket on modern phones to save battery. In my testing on a Pixel 7 running Android 14 with default battery-optimization settings, anything more than ~3 hours of inactivity meant the next message took 5–10 seconds, and in two cases over the test week it took over a minute. ntfy can run in WebSocket-only mode too, but its default setup ships through Firebase Cloud Messaging on Android and APNs on iOS, which is what you actually want for time-sensitive alerts on a phone you are not actively staring at.

Phone showing notification on lock screen
FCM/APNs routing wins for cold-app latency. Pure WebSocket clients hit Doze-mode penalties.

Authentication and access control

This is where the three projects diverge most sharply, and it is the most important section if you are putting any of them on a public-facing VPS.

ntfy ships with a fine-grained ACL system: users, roles, per-topic read/write permissions, token-based or password authentication. You can run a topic in fully public mode (no auth, no privacy — anyone who guesses the URL can read it), in write-only mode (anyone can publish, only authorized users can read — useful for status pages), or fully locked down. Tokens can be scoped per-topic, which means I can issue a token to my import-foods.py script that can ONLY publish to the hsg-imports topic and nothing else. After the recent 770-ASN ceiling incident on the CloudHostReview import where I had to revoke a leaked SSH key, scope-limited tokens are now a hard requirement for anything new I deploy.

Gotify has a simpler model: applications and clients. An application has one token, and that token can publish messages. A client is a user account, and clients can read messages from any application they have access to (which by default is "all"). There is no per-topic ACL, no read-only-publish mode, no anonymous access. This is fine for personal use; it gets awkward the moment you want to give a freelance developer push access to one specific kind of alert without showing them everything else.

Apprise API defers entirely to the underlying services. Authentication on the API itself is basic-auth or a stateless token; access control on what each script can dispatch where is just a question of which configuration keys you give them. There is a stateful and a stateless mode — stateful stores configurations server-side under a key, stateless requires the client to include the full URL spec in each request. For a multi-user setup, you want stateful.

If you are routing alerts that may contain customer data, stack traces with credentials, or anything else you would not want a random scanner to read, ntfy is the only one of the three with a defensible authentication story. Gotify can be made acceptably private behind a VPN or behind reverse-proxy basic-auth, but the protocol itself has nothing to offer.

Templating and message formatting

ntfy 2.17 added priority-field templating and v2.21 expanded it to other headers, so you can send a JSON payload and have ntfy fill in {{.severity}} as the priority level, {{.host}} in the title, and so on. This is genuinely useful for alert pipelines — you can ship raw monitoring JSON straight from Prometheus Alertmanager or Uptime Kuma without writing a transform layer in the middle.

Gotify supports Markdown in message bodies if you set extras["client::display"]["contentType"] = "text/markdown", which the Android client renders. No header templating, no payload transformation.

Apprise API exposes the full body-template capability of the underlying apprise library, which is more flexible than ntfy's because it can rewrite for the destination — the same source message can become a rich Discord embed, a plain SMS, and a Telegram Markdown post simultaneously. The cost is that you have to learn the apprise syntax, which is its own dialect.

Backend and persistence

All three default to SQLite. ntfy added PostgreSQL support in v2.18 (December 2025) and now also supports MySQL via the same SQL abstraction layer; if you are running enough volume that SQLite WAL contention is a concern (more than ~50 messages/second sustained), you would want one of those. Gotify is SQLite-or-MySQL-or-PostgreSQL too, but the schema is simpler so SQLite handles much higher volumes — I never saw lock contention on Gotify in this test. Apprise API does not really have a "backend" in the message-storage sense because messages are dispatched-and-forgotten; it has a small config store for stateful mode, file-based by default.

For my workload of ~400 messages/day, SQLite on any of them is fine indefinitely. The PostgreSQL option mattered to me because I already run Postgres on the VPS for other things and consolidating to one backup target is cleaner than juggling SQLite snapshots.

iOS support, the deal-breaker for half of you

If you carry an iPhone, ntfy is your only realistic choice. The official iOS app is in the App Store, it goes through APNs (which means delivery in the 1–3 second range even from a cold-start state), and it can connect to a self-hosted server provided that server is reachable over the public internet (you cannot, unfortunately, point it at a Tailscale-only endpoint without a relay). Apprise API can deliver to iOS only via downstream services that have their own iOS apps (Pushover, Telegram, Slack), so it is technically an option but you are paying for the iOS reach with a second service in the chain.

Gotify is Android-only and the maintainer has been clear that this is not changing. There is a community-built iOS WebSocket client called Gotify-Pebbled, but it does not use APNs — it polls in foreground only — and I would not trust it for anything operational.

Decision matrix

What you wantPickWhy
iPhone deliveryntfyOnly one with a maintained iOS client on APNs
Smallest footprint on a 1 GB VPSGotify16 MB image, 11 MB idle RAM
One API, many destinations (Slack + Discord + Telegram)Apprise APINative fan-out to 130+ services
Multi-tenant with per-topic ACLsntfyToken scoping, role system, write-only topics
Solo homelab, Android only, "just works"Gotify10-minute setup, no surprises
Prometheus/Alertmanager integrationntfyHeader templating from JSON payloads
You already use Telegram and want a backend gatewayApprise APITelegram is one URL spec away
You want to escape Google services entirelyGotify (UnifiedPush)Pure WebSocket, no FCM dependency

What I actually picked

For my specific case — the broken WhatsApp gateway feeding cron alerts from 7 aggregator sites — I went with ntfy with Apprise API in front of it. The reasoning:

  1. I carry an iPhone, so Gotify was out before the test really started. The 60-second Doze regressions I saw on the loaner Pixel would have been worse for me because I rarely touch the alerts app for hours at a stretch.
  2. I want to keep the option open to fan messages out to a Slack channel for client visibility on certain blog incidents, and Apprise API in front means I do not have to rewrite my scripts when I add a destination.
  3. The ntfy auth model lets me issue per-script tokens with topic-scoped publish rights, which is the security posture I should have had on the WhatsApp gateway from day one.
  4. The combined resource cost (~80 MB RAM, less than 1% of a vCPU) is well under what the WhatsApp gateway was using, and a Go binary plus a Python Flask app are both things I am confident I can debug at 2 a.m. when something breaks.

I would have picked plain ntfy without Apprise if I did not want the multi-destination flexibility. Apprise alone would have meant trusting a third party (Pushover, Telegram) for the delivery layer, which is fine for personal use but means another set of credentials I do not fully control.

Migration notes from a WhatsApp gateway

If you, like me, are coming from a brittle WhatsApp gateway (whether whatsapp-web.js, Baileys, or a paid relay), a few things to know up front. First, none of the three projects here will give you the "feels like a real message from a person" quality that WhatsApp provides — they all show up as app notifications, which is actually the right model for ops alerts but can feel like a step down at first. Second, you will probably want to keep WhatsApp around for one or two channels that need actual human-readable presence (client comms, family). Third, the latency floor of FCM/APNs in the 1–3 second range is faster than a WhatsApp message round-trip in practice, so do not under-estimate the upgrade.

My migration was about three hours of work total: spin up ntfy and Apprise in Docker Compose, install Caddy with two automatic HTTPS hostnames, install the iOS app and subscribe to four topics (one per site group), rewrite the bash send-wa.sh wrapper to POST to Apprise instead of the WhatsApp REST endpoint, and run both in parallel for 48 hours before cutting WhatsApp loose. The only gotcha was that ntfy requires the topic name in the URL path and my old wrapper put the recipient phone number there, so I had to grep through all 7 import scripts and fix the path format.

What I would tell my past self

The single biggest mistake I made with the WhatsApp gateway was treating it as a "set it up once and forget" piece of infrastructure for two years. Notification delivery is operational infrastructure, and operational infrastructure needs the same monitoring discipline as the systems it monitors — otherwise it becomes the silent single point of failure that takes down everything downstream.

All three of the projects in this comparison are mature and well-maintained. Pick the one whose shape fits your stack, give it a real reverse proxy with TLS, scope your tokens, and put a meta-monitor on the notification server itself (Uptime Kuma is the obvious choice, and since v2.20 it has native ntfy support built in). The combination of those four things is what I should have had two years ago, and it is what is finally letting me sleep through the night.

Found this helpful?

Subscribe to our newsletter for more in-depth reviews and comparisons delivered to your inbox.