1. What Gotify Actually Is (and What It Is Not)
Gotify is a self-hosted push-notification server written in Go. At its core it does one thing and does it deliberately narrowly: it receives short messages over a REST API and delivers them to clients that are listening over a WebSocket connection. The server ships with a clean web interface, a token system for separating message sources, and an official Android app that keeps a live WebSocket open to your server so alerts reach your phone without ever passing through Firebase Cloud Messaging, Apple Push Notification Service, or any other third-party relay.
It is worth being precise about the boundaries, because people arrive at Gotify expecting a chat platform and leave confused. Gotify is not a chat application. There is no threading, no replies, no rooms, no presence, no typing indicators. It is a one-way message broker for machines and scripts to shout at humans. A backup job finishing, a disk filling up, a home-automation sensor tripping, a deploy succeeding, an SSL certificate about to expire — these are the messages Gotify was built to carry. It is the last mile of your alerting pipeline, not the sensor that detects the problem.
The project is split across a few repositories under the
gotify organization:
gotify/server is the heart,
gotify/android is the official client, and
gotify/cli is a small command-line tool for sending messages without writing your own HTTP client. The server is licensed MIT, which is about as permissive as open source gets: you can fork it, run it commercially, close a derivative, or bundle it into a product without obligation beyond preserving the copyright notice. That permissive license is part of why Gotify has been embedded in so many other self-hosted stacks as the notification leg.
Gotify is also small. The maintainers report it running comfortably in roughly 64 MB of RAM, and a single statically linked Go binary plus an optional SQLite file is the entire deployment. That tiny footprint is the reason it fits next to the rest of a homelab instead of demanding its own box. The rest of this article explains how that small surface area translates into real advantages, real costs, and real limitations you should weigh before you point your monitoring stack at it.
2. Why Push Notifications Are a Data-Sovereignty Problem
Most people never think about where a push notification travels. On Android, an app that wants to notify you hands the message to Google's Firebase Cloud Messaging, which forwards it to your device. On iOS, the same hand-off happens with Apple's Push Notification Service. Even when the app is your own and the message is "database replication lag on client-prod-03," the content of that alert crosses a vendor boundary you do not control. The vendor can see the message metadata, the destination device, and often the payload, and it can change its retention, its terms, or its availability at any time.
This is not a paranoid edge case. Incident alerts routinely contain hostnames, customer names, error strings, and internal project codenames. Handing that text to a push vendor means you have extended your data perimeter to a company whose only obligation is its own privacy policy. For a solo operator that may be acceptable; for anyone under GDPR, HIPAA-adjacent obligations, or a contractual confidentiality clause, it is a real exposure that is easy to overlook because the push just works.
Gotify removes the relay entirely. When you self-host it, a message posted to your server lives on your server, is held in your database, and is delivered to your Android app over a WebSocket your server terminates. There is no Google, no Apple, no upstream "push network" in the path. The notification content never leaves infrastructure you operate. That is the entire value proposition, and it is the reason Gotify belongs in a self-hosting catalog focused on data sovereignty rather than merely convenience.
The sovereignty angle is sharpened by the alternative. A hosted push SaaS keeps your alert text in its cloud, typically across multiple regions, under its retention policy, and frequently with analytics on delivery. Self-hosting Gotify means you are the data controller, the processor, and the storage provider at once. The trade you make is operational: you now own uptime, backups, and TLS, which the vendor used to own for you. Sections 14 and 15 put hard numbers and a hard data map on both sides of that trade.
3. The Architecture: Server, WebSocket, and Clients
Gotify's design is refreshingly boring, and boring is good for infrastructure you rely on at 3 a.m. The server is a single Go process. It exposes an HTTP API on one port (80 inside the container, conventionally remapped), serves the web UI from the same port, and holds all state in a database. By default that database is a SQLite file in the data directory; MySQL and PostgreSQL are supported for larger or shared deployments. There is no separate front-end service, no Redis, no message queue in the default install. One container, one volume, one port.
The delivery mechanism is the interesting part. A client does not poll the server. Instead the Android app, the web UI, and any custom integration open a WebSocket to
/stream and keep it open. When something POSTs a message to
/message, the server writes it to the database and immediately pushes it down every open WebSocket for that user. Latency from POST to phone is usually well under a second on a local network, and a few hundred milliseconds over a fast connection. Because the connection is server-terminated, there is no vendor broker in the middle.
Authentication on the API uses application tokens, not user passwords. You create an "application" inside Gotify — think of it as a named source like "backups," "monitoring," or "doorbell" — and the server mints a token for it. Any client holding that token can POST messages. This is a deliberate segmentation: you can revoke the backup script's token without touching the monitoring token, and the app shows you which source each message came from. User accounts are separate and exist for logging into the web UI; the REST API for sending is token-scoped to an application, not a person.
The Android app deserves a specific note. It maintains the WebSocket itself and, on a local network or over a TLS-protected public URL, can deliver notifications without Firebase. On newer Android versions, background WebSocket reliability depends on battery-optimization exemptions, which is a real operational wrinkle covered in the limitations section. The app stores message history locally and syncs from the server, so a restarted phone catches up on missed alerts.
4. Installing Gotify with Docker Compose
The fastest supported path is Docker Compose. The official image is
gotify/server:latest, though pinning to a specific tag like
gotify/server:3.1.1 is strongly recommended for reproducibility. A minimal working compose file looks like this:
``
yaml
services:
gotify:
image: gotify/server:3.1.1
environment:
- GOTIFY_DEFAULTUSER_NAME=admin
- GOTIFY_DEFAULTUSER_PASS=change-me-to-something-long
volumes:
- ./data:/app/data
ports:
- "127.0.0.1:8080:80"
restart: unless-stopped
`
A few details matter. The port mapping binds only to localhost on the host (127.0.0.1:8080
), which is correct because you should never expose Gotify's raw port directly to the internet; it goes behind a reverse proxy with TLS (Section 11). The data volume at /app/data
holds the SQLite database, any uploaded application icons, and — if you enable it — Let's Encrypt certificates, so it is the entire backup surface. The default user credentials are set at first boot and the password should be long and random; the admin account is what you use to log into the web UI, not what scripts use to send.
After docker compose up -d
, the web UI is available on http://localhost:8080
. The first thing you should do is change the default admin password, because a forgotten default is the most common Gotify breach. Then create your applications and tokens. The container is health-checkable via its HTTP endpoint, and because there is no external dependency, it comes up in seconds and stays up. Resource limits of 128 MB RAM and a modest CPU cap are more than enough; most installs sit near 64 MB resident.
One operational subtlety: Gotify writes a small amount of state to disk for uploaded icons and, if you use the built-in Let's Encrypt client, certificate material. If you run your own reverse proxy (recommended), leave Gotify's TLS off and let the proxy handle certificates. That keeps the data volume to just the database and icons, which makes backups trivial.
5. Creating Applications and Tokens
Applications are the organizational primitive of Gotify. Each one represents a source of messages and owns its own token. In the web UI you click "Apps," create an application with a name and an optional icon, and the server returns a token. That token is the only secret a sender needs. A typical homelab ends up with a handful: backups
, monitoring
, homelab-nas
, deploy-bot
, doorbell
, and so on.
The token model has two practical benefits. First, segmentation: if your backup script's token leaks, you revoke one token, not your whole alerting system. Second, attribution: every message in the UI is tagged with its application, so you can immediately see that an alert came from monitoring rather than the doorbell. This sounds minor until you are woken at 3 a.m. and need to know whether the notification is "disk full" or "package delivered."
Tokens are bearer credentials. Anyone holding a token can post messages that appear to come from that application. Treat them like passwords: store them in your secret manager, inject them via environment variables in CI, and never commit them to a git repository. Gotify does not currently support scoping a token to read-only or to a priority range, so a leaked token can post at any priority. Revocation is instant and requires no server restart, which is the mitigation that matters.
For programmatic creation, the REST API exposes /application
with standard CRUD. This lets you provision tokens as code in your infrastructure repos, which is cleaner than clicking in the UI for teams. The response includes the token once at creation; Gotify does not store the raw token in a retrievable form after that, so capture it on first use or regenerate it.
6. Sending Your First Message via REST API
Sending a message is a single HTTP POST. The endpoint is /message?token=YOUR_TOKEN
and the body is JSON with at least a message
field and an optional title
and priority
. A minimal curl looks like:
`
bash
curl -X POST "https://push.example.com/message?token=YOUR_TOKEN" \
-F "title=Backup finished" \
-F "message=Nightly backup of client-prod completed in 14m" \
-F "priority=5"
`
The priority
is an integer from 0 to 10. Gotify uses it to sort and visually mark messages; on the Android app it can also drive notification channel importance if you map priorities to channels. A disk-filling alert deserves a high priority like 8 or 10; a "deploy succeeded" notice is a 2 or 3. Using priorities consistently is what turns a noisy Gotify instance into a usable one, because you can then mute low priorities on your phone and only get paged for the high ones.
Beyond the REST API, gotify/cli
wraps this for shell use: gotify push "hello" --title "Test" --priority 5 --token ...
. Most people, however, never call Gotify directly. They wire it into tools that already support a webhook or generic HTTP notification. Uptime Kuma has a native Gotify provider; n8n can POST to it from any node; a bash script can curl it; a Python service can use requests
. Because the contract is just "POST JSON to a URL with a token," almost anything that can make an HTTP request can become a Gotify sender.
A subtle but important point: the message body is plain text by default, but Gotify supports a limited Markdown-ish rendering for the web UI and app. Do not rely on rich formatting for critical alerts; keep the actionable text plain and front-loaded. "DISK 97% on db-03 — rotate logs now" beats a pretty card when you are half asleep.
7. The Android App and the WebSocket Connection
The official Gotify Android app is the reason most people pick Gotify over a bare webhook. It is published on Google Play and F-Droid, and unlike most push apps it does not require Google Play Services to deliver notifications. Instead it opens and maintains a WebSocket to your self-hosted URL. When the server pushes a message, the app receives it directly and raises a system notification. No FCM, no Google relay, no dependency on a vendor push network.
There is a catch that every Android user must internalize: a persistent background WebSocket is exactly the kind of thing modern Android battery management tries to kill. If you do not exempt the Gotify app from battery optimization, the OS may defer or drop the socket when the app is backgrounded, and you will miss alerts. The fix is to grant the app "unrestricted" battery usage and, on some vendors, to lock it in recent-apps memory. This is a one-time setup per device, but it is a real step that the "just install the app" mental model glosses over. On a home network where the phone is usually on Wi-Fi, the socket is stable; on mobile data while roaming, carrier NAT timeouts can drop it and the app reconnects, with a small window where alerts may queue.
The app shows full message history, lets you manage applications if you are an admin, and supports multiple Gotify servers if you run more than one. Notifications respect Android channels, so you can route high-priority Gotify alerts to make sound while muting low-priority ones. The app stores history locally and syncs from the server on open, so a reboot does not lose context. For desktop users, the web UI in a pinned browser tab or a PWA provides the same live stream without a native client.
8. Message Priorities and What They Change
Gotify's priority field is an integer 0–10 and it is the closest thing the system has to an escalation model. The server uses it for sorting and display; the Android app can map priority ranges to distinct notification channels with different sounds, vibration, and importance. This is how you prevent alert fatigue: send routine notices at priority 2–4 and reserve 8–10 for things that should wake you.
Concretely, a sensible convention is: priority 1–3 for informational events (deploy finished, cron job ran, certificate renewed); 4–6 for warnings (disk at 80%, latency elevated, a retry succeeded); 7–8 for actionable incidents (disk at 90%, a service down, replication lag); 9–10 for page-now emergencies (primary DB unreachable, security event, site down for customers). Because Gotify itself has no on-call schedule or escalation policy, the priority is only as meaningful as your phone's channel configuration. Pair high priorities with an insistent sound and low ones with silent delivery.
A limitation worth stating plainly: priority is advisory, not enforced. Gotify will happily deliver a priority-10 message and a priority-1 message through the same channel if you have not configured channels, and it will not auto-escalate, auto-ack, or auto-resolve. There is no "if unacknowledged after 10 minutes, re-alert" logic. If you need SRE-grade escalation, Gotify is the transport, not the pager-duty logic; you would run that logic in the system that calls Gotify. That boundary is fine, but know it before you expect Gotify to page a rotation on its own.
9. Authentication: Local Accounts and OIDC/SSO
Gotify supports two authentication stories. The simple one is local accounts: you create users in the web UI, they log in with username and password, and they see their own messages. This is perfect for a single operator or a small family. The server hashes passwords and there is no phone-home, no external identity dependency, and no third-party account required.
The more interesting one for self-hosters is OpenID Connect. Gotify can delegate login to an external identity provider — Authentik, Keycloak, Authelia, or any standards-compliant OIDC server you already run. This lets you apply your existing SSO, MFA, and user-lifecycle policies to Gotify instead of managing yet another password. If you already run an IdP for the rest of your stack (and many readers of this blog do, given how often Authentik and Keycloak come up), pointing Gotify at it is a few environment variables and a redirect-URI registration.
There is a sharp distinction between OIDC login and application tokens. OIDC governs who can open the web UI; tokens govern who can post messages. They are independent. You might log in via your IdP but still issue per-source tokens for scripts, because scripts should not hold your personal SSO credentials. Gotify's OIDC support is for the human-facing UI, not for the machine-to-machine send path, which remains token-based. That separation is correct and you should preserve it: never embed an interactive SSO flow in a cron job.
One honest gap: Gotify's user model is flat. There is an admin and there are users, but there is no fine-grained role matrix, no per-application visibility scoping beyond ownership, and no team or group construct. For a household or a small team this is fine; for an organization that needs "only the on-call group sees production alerts," Gotify alone will not enforce it, and you would need to run separate instances or accept that all users see all messages.
10. Database Backends: SQLite, MySQL, and PostgreSQL
The default and recommended backend for almost everyone is SQLite. It lives in the data volume as a single file, it needs no separate service, and for the message volumes a personal or small-team alerting system produces it is more than fast enough. Backing up Gotify is then literally copying one file while the container is stopped or via a consistent snapshot. The downside of SQLite is the same as its upside: it is one file on one machine, so it inherits that machine's durability.
For larger deployments, Gotify supports MySQL and PostgreSQL through environment variables (GOTIFY_DATABASE_MYSQL_
or GOTIFY_DATABASE_POSTGRES_). The motivation is usually operational consistency — you already run Postgres for other services and want Gotify in the same database cluster with the same backup story — rather than raw performance, because Gotify's write load is tiny. If you go this route, point it at a dedicated database or schema and include it in your existing backup routine. There is no migration tool to switch backends after the fact; you would export messages and re-import, which is fiddly, so choose at install time.
A practical note on growth: Gotify does not auto-expire messages. Every alert you ever sent stays in the database until you delete it, either per-message in the UI, per-application, or via the API. Over years a busy instance can accumulate hundreds of thousands of rows, and while SQLite handles that fine, the web UI pagination and your backup size grow. A simple retention job — delete messages older than N days via the API on a cron — keeps the system lean. This is a small operational task Gotify offloads to you rather than automating.
11. Running Gotify Behind a Reverse Proxy
You should not expose Gotify's port directly. Put it behind a reverse proxy that terminates TLS, enforces HTTPS, and optionally adds brute-force protection. Caddy and Traefik are the usual choices in this ecosystem, and both handle the one non-obvious requirement: WebSockets must be passed through. Caddy does this automatically for upgrade requests; nginx needs explicit Upgrade
and Connection
header handling, which is why Caddy/Traefik are gentler here.
A Caddyfile snippet is almost trivial:
`
push.example.com {
reverse_proxy localhost:8080
}
`
Caddy obtains and renews the Let's Encrypt certificate automatically, and because Gotify's WebSocket uses the standard upgrade handshake, no extra config is needed. Traefik is similarly straightforward with a services
and routers
entry and the default websecure
entrypoint. The proxy should redirect HTTP to HTTPS and, ideally, sit behind your existing CrowdSec or fail2ban setup so repeated failed logins get throttled.
Why bother with a public URL at all if Gotify is for your phone? Because you want alerts when you are off your home network, not just on Wi-Fi. A public TLS URL lets the Android app keep its WebSocket open over mobile data. If you would rather not expose it, the alternative is a VPN or tunnel (WireGuard, Tailscale, or a Cloudflare Tunnel) so the phone reaches Gotify over your private network only. That is more sovereign but means no alerts when the tunnel is down; for most people a small public URL with a strong password and rate limiting is the pragmatic balance.
12. The Plugin System
Gotify has a plugin architecture that lets you extend the server without forking it. Plugins are separate Go binaries that the server loads at startup via a configured directory, and they can hook into message events — for example, to forward a message elsewhere, transform it, or act on it. The official documentation shows a format for building plugins against the server's plugin API, and community plugins exist for things like additional delivery targets.
In practice the plugin system is underused by most operators because the core already covers the common case: receive over REST, deliver over WebSocket. Plugins matter when you need to, say, mirror Gotify messages into another system, enrich them, or apply custom retention. If you are a Go developer the API is approachable; if you are not, the plugin system is a nice-to-have you will likely never touch, and that is okay. Gotify is not trying to be a general automation engine — that is n8n's job, and the two pair well (Section 13).
A caution: plugins run inside the server process and share its privileges and data access. A malicious or buggy plugin can read every message and affect server stability. Treat third-party plugins like you would any code running with access to your alert content — review the source, build it yourself, and pin versions. For a privacy-critical notification system, the safest plugin is the one you wrote or the one you audited.
13. Integrating Gotify with Your Homelab
Gotify earns its place by being the easy destination every other tool already knows how to call. The canonical wiring is monitoring-to-phone: Uptime Kuma watches your services and, on failure, posts to Gotify; you get paged on your own infrastructure. Changedetection.io watches a price or a page and alerts you the same way. A bash backup script curls Gotify on success and on failure, so you stop discovering failed backups from angry customers.
n8n is a natural orchestration layer: a workflow that completes, or a webhook that fires, ends in an HTTP Request node POSTing to Gotify. Home Assistant can call Gotify as a notification service through its REST notification platform, so a door sensor or a leak detector becomes a phone alert without a cloud account. The pattern repeats across the self-hosting world because the contract is so small: an HTTP POST with a token. Gotify is the last mile; your sensors and automations are everything upstream.
The mental model that prevents confusion: Gotify does not monitor, detect, or decide. It receives and displays. If you expect "Gotify tells me when the server is down," what you actually built is "Uptime Kuma detects down, then tells Gotify, which tells your phone." Each link is independently replaceable, which is the whole point of composable self-hosted infrastructure. When one tool in the chain is better suited, you swap it without rewriting the others, and Gotify stays the stable destination.
14. Real-World Self-Hosting Costs
The honest cost answer for Gotify is "almost nothing," and the reason is its footprint. In the cheapest realistic configuration — running it on hardware you already own, like the same mini-PC or Raspberry Pi that runs the rest of your homelab — the marginal electricity for a 64 MB container is a rounding error, roughly one to three dollars a year. There is no per-message fee, no seat fee, no tier, because the MIT license puts the entire feature set in your hands.
If you want Gotify on a dedicated public URL rather than piggybacking on existing hardware, a tiny VPS is plenty. An ARM-based entry VPS with 2 GB of RAM runs a few dollars a month (roughly three to five USD) and can host Gotify plus several other lightweight services. You do not need that whole box for Gotify; you need maybe 128 MB of it. Storage is negligible: the SQLite database for a personal instance stays in the megabytes for years unless you send large attachments or never prune, so disk cost is effectively zero. TLS certificates from Let's Encrypt are free, and a domain name costs roughly ten to fifteen dollars a year if you do not already own one.
Add it up for a standalone setup: VPS at about four to six USD per month, domain amortized at about one USD per month, backups essentially free if you already back up the host. Total all-in is on the order of five to eight USD per month, and that is the generous case where Gotify gets its own machine. Run it alongside existing infrastructure and the true marginal cost approaches zero. Compare that to a hosted incident-push or monitoring-alert SaaS, which can run five to fifty USD per month for comparable volume, and unlike Gotify those services keep your alert content in their cloud. The "free" push relays from Google and Apple are free in money but cost you data residency, which is the trade this entire blog is about.
15. Where Your Data Goes: Data Sovereignty Deep Dive
This is the section the rest of the article exists to support. When you self-host Gotify, here is the complete data map. A message you post contains a title, a body, a priority, and an application token. That payload is written to your database — SQLite, MySQL, or PostgreSQL — on your machine. It is then delivered over a WebSocket that your server terminates, to your Android app or your web UI. The notification text traverses only your network and your device. Google, Apple, and any push vendor are not in the path. Your alert content is, end to end, on infrastructure you control, in a jurisdiction you choose, under a retention policy you set.
The caveats are the interesting part. First, the transport security depends on your reverse proxy. If you expose Gotify over plain HTTP, the message travels in clear text; with TLS (Section 11) it is encrypted in transit, which is the normal case. Second, the data at rest is only as protected as your disk. Gotify does not encrypt the database itself; if you need at-rest encryption, that is a full-disk or volume-encryption concern on the host, not a Gotify feature. Third, when you read a Gotify message on the Android app, a copy lives in the app's local storage on your phone, which is outside Gotify's server entirely — standard for any notification system, but worth naming.
Sovereignty also means responsibility. You are the data controller, so GDPR "right to erasure" is satisfied by deleting the message in your UI or API; there is no vendor to petition. You decide retention, backups, and who can log in. But you also own the failure modes: if your server is down, alerts queue nowhere and are simply lost unless the sender retries, because Gotify does not guarantee delivery to an offline client beyond holding the message for when it reconnects. For critical paging, run Gotify on the most reliable host you have and monitor the monitor.
Contrast this with a push SaaS: your alert text lands in their database, crosses their region choices, sits under their retention, and is visible to their staff under their policy. For "disk 97% on db-03," that is a needless perimeter expansion. Gotify collapses the perimeter to your own stack, which is the single strongest reason to run it.
16. Honest Limitations
No tool this small is without edges, and Gotify's are worth naming before you commit. The first and most cited is the absence of a native iOS app. The maintainers have stated for years that an official iOS client is not coming, because iOS push fundamentally requires Apple's APNs relay, which would defeat Gotify's no-vendor-relay design. iPhone users in your household cannot get Gotify notifications the way Android users can; they would need a third-party Matrix or email bridge, or simply use the web UI. For an Android-only home, this is a non-issue; for a mixed household, it is a real blocker for some members.
Second, Gotify is one-way. There is no reply, no acknowledgment, no conversation. If your use case needs "acknowledge the alert," you build that elsewhere. Third, there is no email or SMS delivery built in. Gotify will not email you or text you; it delivers to its own clients over WebSocket. If you need SMS as a fallback when the server is unreachable, Gotify cannot provide it — that is a different tool.
Fourth, the user model is flat (Section 9): admin and users, no fine-grained roles or teams. Fifth, there is no built-in escalation, scheduling, or on-call logic (Section 8); priority is advisory only. Sixth, messages do not auto-expire, so retention is your job. Seventh, for high-availability you would run multiple instances, but Gotify has no native multi-node replication of the message store — each instance is independent, so you would point senders at one and accept that a second is a manual warm standby, not automatic failover. Eighth, the WebSocket-on-Android battery caveat (Section 7) means setup effort on every phone. None of these are fatal, but together they define Gotify as a personal or small-team alert pipe, not an enterprise incident platform.
17. Gotify vs ntfy vs Pushover vs FCM
Gotify's nearest self-hosted sibling is ntfy, which this blog has covered separately. Both are Go servers that deliver messages over a pub-sub or WebSocket model; ntfy uses topic-based pub-sub and has both Android and iOS apps plus a simpler "just PUT a message" UX, while Gotify uses application tokens and WebSocket and is Android-only on mobile. ntfy's community and ecosystem are larger, and its iOS support makes it the better pick for mixed-platform households. Gotify's edge is its tight application/token model and its bundled web UI, which some operators find cleaner for many distinct sources.
Against Pushover, a popular one-time-purchase push SaaS, Gotify wins on sovereignty and loses on zero-maintenance. Pushover "just works" across platforms for a few dollars and you never patch a server, but your messages pass through its cloud. Gotify keeps messages local at the cost of running and backing up a service. Against raw Firebase Cloud Messaging or Apple APNs, Gotify is slower to set up and Android-only, but it removes the vendor entirely — which is the whole point for a sovereignty-focused operator.
The practical recommendation: if everyone who matters carries Android and you value keeping alert content on your own disk, Gotify is the tidier tool. If you need iOS coverage or the absolute minimum setup, ntfy is the stronger start. Both are MIT/Apache, both are tiny, and both belong in a self-hosted stack; the choice is about mobile platform mix and UI preference more than capability, because their ceilings are similar.
18. Security: Secrets, TLS, and Exposure
Gotify's security posture is "you own it," which cuts both ways. The server itself has no known unpatched vulnerabilities in the current release (v3.1.1 as of this writing), and the project's historical advisories are few. The realistic risks are operational. The default admin password must be changed immediately; a Gotify instance with admin
/admin
exposed to the internet is a free message-spoofing foothold. Application tokens are bearer secrets and should live in a secret manager, not in scripts committed to git.
TLS is non-negotiable for a public URL, because without it the WebSocket and token travel in clear text and anyone on the path can read or inject messages. A reverse proxy with Let's Encrypt (Section 11) closes that. For brute-force protection, put Gotify behind CrowdSec or fail2ban so repeated failed logins are throttled or banned; the proxy is the right place for that, not Gotify itself. If you expose it, also consider allowing only specific source IPs or requiring your VPN for login while permitting the WebSocket from anywhere.
Disclosure responsibility is yours too. Because Gotify holds your alert content, a compromised server leaks your internal hostnames and incident details. Keep the server patched (Section 20), keep it on a host with good general hygiene, and limit who can log in. The MIT license means you can audit the code yourself; the small codebase makes that audit practical in a way it is not for a sprawling platform.
19. Backups and Disaster Recovery
Backing up Gotify is almost trivial because its state is one directory. Stop the container (or snapshot the volume while consistent) and copy /app/data
— the SQLite file, uploaded icons, and any certs. That is the entire backup. For SQLite specifically, a file copy while the process is running can occasionally capture a mid-write state, so the safe pattern is to stop the container, copy, and start it, or use a tool that snapshots the volume at the filesystem layer. The backup is small, so daily copies cost nothing.
Restore is the reverse: replace the data directory and start the container. Because there is no separate database service to coordinate, there is no "restore order" puzzle. The only data you lose in a worst case is messages sent after your last backup, which for an alerting system is usually acceptable — you care that future alerts arrive, not that a "disk 90%" notice from last Tuesday survives. If message history matters to you, back up more often; the cost is still near zero.
A subtle DR consideration: Gotify does not replicate. If your host dies and you restore from yesterday's backup on a new host with a new IP, you must update the URL in the Android app and in every sender's configuration. Treat the Gotify URL as infrastructure that changes on restore, and document it. For most operators the simplest resilience is to run Gotify on the same reliable host that runs their other critical services, so it shares that host's backup and uptime story rather than introducing a new fragile point.
20. Updating Without Breaking Things
Gotify releases land roughly every six weeks, and updates are low-risk because the surface is small. The safe update path is: take a backup of the data directory, pull the new image tag, and recreate the container. Because the data volume is mounted from the host, your messages survive the container rebuild. Pin to specific tags in production (gotify/server:3.1.1
) rather than latest` so an unexpected release cannot surprise you at 3 a.m.
Database migrations, when they occur, are handled by the server on startup and are generally uneventful for a tool this size. Still, the backup-first habit protects you if a migration ever goes wrong. Read the release notes for breaking changes, especially around environment-variable renames or OIDC config, before bumping a major version. The maintainers are conservative about breaking the simple path, which is why homelab guides from years ago still largely work.
One operational discipline pays off: keep Gotify on the same update cadence as the rest of your stack so you are not tempted to "leave it, it's just notifications." A year-old Gotify is not a fire, but an unpatched anything on a public URL is needless risk, and the update is cheap enough to fold into your monthly maintenance window.
21. Scaling and Multi-User Setups
For a household or a small team, Gotify scales by doing almost nothing special: create a user per person, issue tokens per source, and let everyone read their own messages. The server handles thousands of messages and several concurrent WebSocket connections on a few dozen megabytes of RAM. Throughput is not the constraint; it is effectively unbounded for personal use.
Larger or shared deployments usually move to PostgreSQL for operational consistency with other services and to avoid one SQLite file becoming a single point of contention, though contention is unlikely at alert volumes. True high availability — multiple Gotify servers behind a load balancer with a shared, replicated message store — is not a native feature. Each instance is independent, so "HA" means running a standby and accepting manual failover, or accepting that your alerting has the same availability as the one host it runs on. For most self-hosters that host is already the most reliable thing they own, so this is acceptable; for a company with an on-call rotation, weigh a managed or more redundant incident tool instead.
Multi-tenant isolation is the real ceiling. Gotify's flat user model means all users in an instance see all messages unless you run separate instances per team. If "only on-call sees production alerts" is a hard requirement, you either run separate Gotify instances or accept the visibility. This is the boundary between Gotify-as-personal-pipe and Gotify-as-team-pager, and it is an honest limit to design around.
22. Privacy: What Gotify Never Sees
Privacy and sovereignty overlap but are not identical, so it is worth separating them. Gotify never sees your messages leave your infrastructure, which is the sovereignty claim. The privacy claim is about the project itself: there is no telemetry, no phone-home, no account required with any vendor, and no analytics. The server does not call out to the internet to function. The Android app, when configured for your self-hosted URL, does not route through a Gotify-operated relay.
The limits of that privacy are the ones you introduce. If you expose Gotify publicly without TLS, your ISP or network path can see messages. If you grant the app battery exemptions and run it on a phone with other tracking apps, that is your phone's posture, not Gotify's. If you post secrets — passwords, tokens, full customer records — into alert bodies, you have put sensitive data into your own database, which is sovereign but still sensitive and should be pruned. Gotify's design minimizes what it could leak; your operational choices determine what actually stays private.
The cleanest privacy configuration is: TLS everywhere, OIDC login through your own IdP with MFA, tokens in a secret manager, retention pruning on a schedule, and the Android app exempted from battery optimization but with no other accounts on the device. Under that configuration, an alert about "db-03 replication lag" exists only on your server and your phone, which is exactly the threat model a sovereignty-minded operator wants.
23. Migrating From a SaaS Push Service
Moving from Pushover, a monitoring SaaS, or raw FCM/APNs to Gotify is a small project with a clear shape. Step one: stand up Gotify behind TLS and confirm the Android app receives a test message over mobile data, not just on Wi-Fi. Step two: create one application token per source you currently alert from. Step three: replace the old webhook or SDK call in each sender with a POST to Gotify using that source's token, preserving the priority you used before. Step four: cut over, watch for a day, and only then retire the old service.
The painful part is never Gotify; it is finding every place that sends an alert. Backup scripts, cron jobs, monitoring tools, home-automation rules, CI pipelines — each has its own notification setting, and the migration is a checklist across your infrastructure, not a Gotify feature. Budget an afternoon. The reward is that every one of those alerts now lands on your own disk, and you can delete the SaaS account that was holding your incident text.
For teams leaving a paid incident platform, the gap to plan for is escalation and scheduling, which Gotify does not provide (Section 8). If your old tool paged a rotation on a schedule, you keep that logic in the system upstream of Gotify or adopt a dedicated on-call tool, and let Gotify be the delivery channel. Treated as transport, Gotify replaces the vendor relay cleanly; treated as a full incident platform, it does not, and pretending otherwise is the common mistake.
24. Bottom Line
Gotify is a tiny, MIT-licensed Go server that does one job — receive messages over REST, deliver them over WebSocket to your own devices — and does it with a footprint small enough to ride alongside the rest of your homelab. Its reason to exist is data sovereignty: your alert content never crosses Google, Apple, or a push SaaS, so "disk 97% on db-03" stays on infrastructure you control, in a jurisdiction you choose, under a retention policy you set. Real self-hosting cost is effectively zero on existing hardware and about five to eight USD per month for a dedicated public URL including domain amortization, against five to fifty USD per month for a comparable hosted alert service that keeps your text in its cloud.
The limitations are real and specific: no native iOS app, one-way delivery with no reply or acknowledgment, no built-in email or SMS, a flat user model with no fine-grained roles, no native escalation or scheduling, no auto-expiring messages, and no multi-node replication. For an Android-only household or a small team that already runs a monitoring or automation layer upstream, those edges are easy to design around, and Gotify becomes the clean last mile of a composable alerting stack. For mixed-platform families or companies needing on-call scheduling and role-scoped visibility, ntfy or a dedicated incident platform will fit better.
If your threat model includes "I do not want my incident alerts in someone else's cloud," Gotify is one of the simplest, cheapest, most auditable ways to close that gap. Stand it up behind TLS, point your monitors at it, and your phone starts ringing from your own server instead of a vendor's.
Related
Comments (0)
No comments yet. Be the first to comment!