Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Monitoring 16.09.2026 · 19 min Lesezeit

Gotify 3.1.1 mit Docker Compose selbst hosten: Push-Benachrichtigungen ohne Cloud

Gotify bringt Alarme aus Monitoring, Backup-Jobs und Skripten ohne Cloud-Dienst auf die Geräte der Administratoren. Diese Anleitung zeigt die vollständige Einrichtung mit Docker Compose, den praktisch getesteten Push per curl, die Abgrenzung zu ntfy sowie Backup, Wiederherstellung und saubere Deinstallation.

Grafik zur Anleitung: Gotify mit Docker Compose selbst hosten, mit Benachrichtigungsliste und Architekturdiagramm KI-generiert

Wenn ein Backup-Job nachts scheitert oder ein Server nicht mehr antwortet, entscheidet sich an einer simplen Frage, wie teuer der Vorfall wird: Erfährt jemand rechtzeitig davon? E-Mail landet im Spam oder wird erst am Morgen gelesen, SMS-Gateways kosten pro Nachricht, und Cloud-Push-Dienste wie Pushover oder Pushbullet schicken die Inhalte der Meldungen über fremde Server. Gotify ist ein selbst gehosteter Server für Push-Benachrichtigungen, der genau diese Lücke schließt: ein einzelner Go-Dienst mit REST-API, Weboberfläche und Android-App, der Meldungen aus Monitoring, Shell-Skripten und Backup-Jobs entgegennimmt und in Echtzeit auf die Geräte der Administratoren schiebt.

Diese Anleitung führt durch die komplette Einrichtung mit Docker Compose: Compose-Datei, Erstkonfiguration, Application-Token, echter Push-Test per curl, Reverse Proxy mit TLS, Backup und Wiederherstellung der SQLite-Datenbank, Updates und saubere Deinstallation. Alle Befehle im Abschnitt zum Funktionstest wurden auf einem Ubuntu-Host mit Docker 29.1.3 und Docker Compose 2.40.3 mit dem Image gotify/server:3.1.1 tatsächlich ausgeführt, die gezeigten Ausgaben stammen aus diesem Testlauf.

Was Gotify ist und welches Problem es löst

Gotify besteht aus drei Bausteinen. Der Server ist ein einzelnes Go-Binary, das eine REST-API, eine WebSocket-Schnittstelle für den Live-Stream und eine React-Weboberfläche ausliefert. Applications sind die Absender: jede Quelle, die Meldungen schickt, bekommt einen eigenen Token. Clients sind die Empfänger, also die Android-App oder ein Browser-Tab mit der Weboberfläche.

Der praktische Unterschied zu E-Mail-Alarmierung ist die Trennung nach Absender. Wer das Backup-Skript, Uptime Kuma und den Prometheus Alertmanager mit je einer eigenen Application anlegt, kann in der App gezielt einzelne Quellen stummschalten, ohne die gesamte Alarmierung abzuschalten. Das reduziert Alarmmüdigkeit deutlich stärker als Mail-Filterregeln.

Der zweite Punkt ist die Einfachheit der Schnittstelle. Eine Meldung abzusetzen ist ein einziger HTTP-POST mit einem Header und zwei Formularfeldern. Das lässt sich in jedes Shell-Skript, jeden Cron-Job und jede Ansible-Rolle einbauen, ohne SMTP-Zugangsdaten zu verteilen oder eine Bibliothek zu installieren.

Nutzen und Grenzen ehrlich benannt

Gotify ist bewusst klein gehalten. Das ist gleichzeitig seine Stärke und seine Grenze. Wofür es sich eignet:

  • Technische Alarmierung im eigenen Netz: Monitoring-Alerts, Backup-Ergebnisse, Zertifikatsablauf, Festplatten-SMART-Warnungen, abgeschlossene Batch-Läufe.
  • Umgebungen ohne Cloud-Abhängigkeit: Der Server läuft komplett im eigenen Netz, es geht kein Alarmtext an einen externen Anbieter.
  • Sehr kleine Hardware: Im Test belegte der Container im Leerlauf rund 5 MiB RAM. Ein Raspberry Pi oder eine kleine VM mit 512 MB reicht aus.
  • Skript-Integration: Ein curl-Aufruf genügt, keine SDK-Installation nötig.

Wofür Gotify nicht taugt, und das sollte vor der Einführung klar sein:

  • Kein offizieller iOS-Client. Das Projekt stellt eine Android-App bereit. Für iPhones gibt es nur Drittanbieter-Apps oder die Weboberfläche im Browser. Wer ein gemischtes Gerätefeld hat, muss das einplanen.
  • Keine Ende-zu-Ende-Verschlüsselung. Die Nachrichteninhalte liegen im Klartext in der Datenbank auf dem Server. Wer den Server administriert, kann alle Meldungen lesen. Personenbezogene oder vertrauliche Inhalte gehören daher nicht in den Meldungstext.
  • Keine Eskalationslogik. Es gibt keine Bereitschaftspläne, keine Weiterleitung nach Nichtquittierung, keine Rufgruppen. Wer das braucht, ist bei einer echten Alerting-Plattform besser aufgehoben.
  • Kein Ersatz für Monitoring. Gotify prüft nichts. Es stellt nur zu, was ihm geschickt wird.
  • Kein Zwei-Wege-Kanal. Meldungen lassen sich nicht aus der App heraus quittieren oder beantworten.

Abgrenzung zu ntfy

Gotify und ntfy lösen dasselbe Grundproblem, gehen aber unterschiedlich vor. Die Wahl sollte nicht nach Sympathie, sondern nach Gerätepark und Bedienmodell fallen.

KriteriumGotify 3.1.1ntfy
AdressierungApplication mit festem Token, vom Server vergebenFrei wählbares Topic im URL-Pfad
Zugriff ohne TokenNicht möglich, jede Anfrage braucht Token oder LoginStandardmäßig offen, Zugriffsschutz muss aktiviert werden
BenutzerverwaltungFest eingebaut, Admin- und Standardbenutzer, OIDC möglichÜber Access-Control-Listen konfigurierbar
Offizielle Mobil-AppsAndroidAndroid und iOS
DatenhaltungSQLite als Standard, MySQL und PostgreSQL möglichSQLite als Cache, primär flüchtig
Typischer EinsatzFester Kreis bekannter Absender und EmpfängerAd-hoc-Kanäle, auch für Gelegenheitsnutzer

Kurz gefasst: Gotify passt, wenn eine überschaubare Zahl von Systemen an eine überschaubare Zahl von Administratoren meldet und jede Quelle sauber nachvollziehbar bleiben soll. ntfy passt, wenn Kanäle spontan entstehen, wenn iOS-Geräte im Spiel sind oder wenn ein öffentlicher Dienst genutzt werden soll. Beide Wege sind legitim, und beide lassen sich parallel betreiben. Zu ntfy gibt es auf S-EDV bereits eigene Anleitungen, die am Ende dieses Artikels verlinkt sind.

Projekt- und Versionsstand

Die folgenden Angaben wurden am 16.09.2026 über die GitHub-API der Endpunkte /repos/gotify/server und /repos/gotify/server/releases/latest abgerufen:

  • Aktuelle Version: v3.1.1, veröffentlicht am 15.09.2026.
  • Sterne auf GitHub: 15.905, Forks: 872.
  • Letzter Push in das Repository: 15.09.2026, das Projekt ist nicht archiviert.
  • Offene Issues und Pull Requests: 84.
  • Lizenz: Das Repository enthält eine LICENSE-Datei, die GitHub-API meldet dafür jedoch NOASSERTION, weist die Lizenz also nicht automatisch einem Standardtyp zu. Wer die Lizenzlage rechtlich bewerten muss, sollte die LICENSE-Datei im Repository direkt lesen.

Die Release-Notes zu v3.1.1 nennen unter anderem eine Sicherheitsverbesserung: Das Anlegen von Benutzern verlangt nun eine erhöhte Sitzung (Advisory GHSA-phfm-q6fr-wv34). Außerdem wurde ein Absturz behoben, der auftrat, wenn eine Let's-Encrypt-Anforderung fehlschlug. Wer noch eine ältere 3.x-Version betreibt, sollte deshalb zeitnah aktualisieren.

Wichtig für alle, die von Gotify 2.x kommen: Ab Version 3 erfolgt die Konfiguration über Umgebungsvariablen beziehungsweise eine env-Datei. Die frühere config.yml gibt es in dieser Form nicht mehr. Das Projekt stellt dafür eine ausführlich kommentierte Beispieldatei gotify-server.env.example im Repository bereit sowie einen eigenen Migrationsleitfaden in der Dokumentation.

Voraussetzungen und Ressourcenbedarf

Der Bedarf ist gering, was Gotify auch für vorhandene Nebenhosts interessant macht:

  • Docker Engine und Compose-Plugin: Im Test kamen Docker 29.1.3 und Docker Compose 2.40.3 zum Einsatz.
  • Arbeitsspeicher: Im Leerlauf zeigte docker stats im Test 5,07 MiB. Mit 128 MB Limit läuft der Dienst komfortabel, 256 MB sind großzügig.
  • Speicherplatz: Das Image belegte im Test rund 54 MB. Die frisch angelegte SQLite-Datenbank war 68 KB groß und wächst mit der Zahl gespeicherter Meldungen.
  • CPU: Keine besonderen Anforderungen. Anders als Dienste mit MongoDB-Backend benötigt Gotify keine AVX-Befehlssatzerweiterung, weil SQLite als Standarddatenbank genügt. Das ist auf älteren Servern und sparsamen Mini-PCs ein handfester Vorteil.
  • Architekturen: Laut offizieller Dokumentation sind die Images gotify/server und ghcr.io/gotify/server Multi-Arch-Images für amd64, i386, arm64, armv7 und riscv64. Im Test wurde amd64 verwendet, die übrigen Architekturen wurden hier nicht geprüft.
  • Netz: Ein erreichbarer Hostport. Für den Zugriff von außen zusätzlich ein Reverse Proxy mit gültigem Zertifikat.

Compose-Datei und Konfiguration

Für den Betrieb wird ein eigenes Verzeichnis angelegt, zum Beispiel /opt/gotify. Darin liegen die Compose-Datei, die Datei mit den Zugangsdaten und das Datenverzeichnis.


# Verzeichnis anlegen und hineinwechseln
sudo mkdir -p /opt/gotify
cd /opt/gotify

Die Compose-Datei bindet den Port bewusst nur an die Loopback-Adresse. Der Zugriff von außen läuft später ausschließlich über den Reverse Proxy. Wer Gotify zunächst nur im lokalen Netz testen will, kann die Bindung an 127.0.0.1 weglassen.


# /opt/gotify/compose.yaml
services:
  gotify:
    image: gotify/server:3.1.1
    container_name: gotify
    restart: unless-stopped
    ports:
      # Nur lokal erreichbar, Zugriff von aussen via Reverse Proxy
      - "127.0.0.1:18080:80"
    env_file:
      - .env
    environment:
      TZ: "Europe/Berlin"
    volumes:
      - ./gotify_data:/app/data
    mem_limit: 256m

Die Zugangsdaten gehören in eine getrennte .env-Datei, damit sie nicht versehentlich in einem Git-Repository landen. Die Platzhalter sind vor dem ersten Start zu ersetzen.


# /opt/gotify/.env
# ACHTUNG: Platzhalter ersetzen, niemals die Beispielwerte uebernehmen
GOTIFY_DEFAULTUSER_NAME=admin
GOTIFY_DEFAULTUSER_PASS=BITTE-HIER-EIGENES-STARKES-PASSWORT-EINSETZEN
# Selbstregistrierung durch Unbekannte unterbinden
GOTIFY_REGISTRATION=false
# Kosten des bcrypt-Hashes, hoeher ist sicherer und langsamer
GOTIFY_PASSSTRENGTH=10
# Hinter TLS-Proxy sinnvoll: Session-Cookie nur ueber HTTPS senden
GOTIFY_SERVER_SECURECOOKIE=true
# IP des Reverse Proxy, damit die echte Client-IP im Log steht
GOTIFY_SERVER_TRUSTEDPROXIES=127.0.0.1/32

# Rechte der Datei mit den Zugangsdaten einschraenken
sudo chmod 600 /opt/gotify/.env

Das Projekt liefert eine vollständig kommentierte Referenz aller Variablen als gotify-server.env.example im Repository aus. Sie lässt sich direkt herunterladen, statt Werte aus Blogartikeln abzuschreiben:


# Offizielle Referenzkonfiguration zum Nachschlagen holen
curl -o /opt/gotify/gotify-server.env.example \
  https://raw.githubusercontent.com/gotify/server/v3.1.1/gotify-server.env.example

Die wichtigsten Variablen im Überblick:

VariableStandardBedeutung
GOTIFY_DEFAULTUSER_NAMEadminName des ersten Administrators, wirkt nur beim allerersten Start
GOTIFY_DEFAULTUSER_PASSadminPasswort des ersten Administrators, wirkt nur beim allerersten Start
GOTIFY_REGISTRATIONfalseErlaubt unangemeldeten Besuchern das Anlegen eigener Konten
GOTIFY_SERVER_PORT80Port im Container, im Image bereits gesetzt
GOTIFY_SERVER_SECURECOOKIEfalseSetzt das Secure-Flag am Sitzungs-Cookie, hinter HTTPS einschalten
GOTIFY_SERVER_TRUSTEDPROXIESleerProxys, deren X-Forwarded-For-Header vertraut wird
GOTIFY_DATABASE_DIALECTsqlite3Datenbanktreiber, alternativ mysql oder postgres
GOTIFY_DATABASE_CONNECTIONdata/gotify.dbPfad beziehungsweise Verbindungszeichenkette der Datenbank
GOTIFY_PASSSTRENGTH10bcrypt-Kostenfaktor für Passwort-Hashes
GOTIFY_LOCALAUTH_ENABLEDtrueAnmeldung mit Benutzername und Passwort, bei reinem OIDC abschaltbar

Ein nützliches Detail für Umgebungen mit Secret-Verwaltung: Jede Variable akzeptiert laut offizieller Dokumentation zusätzlich eine Schreibweise mit dem Suffix _FILE, etwa GOTIFY_DEFAULTUSER_PASS_FILE=/run/secrets/admin_pass. Der Wert wird dann aus der angegebenen Datei gelesen. Das wurde in diesem Testlauf nicht selbst geprüft.

Installation und erster Start

Nach dem Anlegen der beiden Dateien wird der Stack gestartet:


cd /opt/gotify
# Image holen und Container im Hintergrund starten
docker compose up -d

Der erste Start lädt das Image und legt anschließend die Datenbank an. Im Test sah der Status danach so aus:


docker compose ps

NAME      IMAGE                 COMMAND                SERVICE   STATUS                    PORTS
gotify    gotify/server:3.1.1   "./gotify-app serve"   gotify    Up 32 seconds (healthy)   127.0.0.1:18080->80/tcp

Das Image bringt einen eigenen Healthcheck mit, der intern /health abfragt. Bis der Status von health: starting auf healthy wechselt, vergehen einige Sekunden. Das Startlog zeigte im Test:


docker compose logs --tail 20

gotify | 2026-09-16T10:00:34+02:00 INF Gotify build_date=2026-09-15-18:26:53 version=3.1.1
gotify | 2026-09-16T10:00:34+02:00 INF Listen address=[::]:80 connection_type="plain connection" network=tcp

Erstkonfiguration: Benutzer und Applications

Die Weboberfläche ist nach dem Start unter der gebundenen Adresse erreichbar, im Beispiel also http://127.0.0.1:18080. Die Anmeldung erfolgt mit den Werten aus GOTIFY_DEFAULTUSER_NAME und GOTIFY_DEFAULTUSER_PASS. Beide Variablen wirken ausschließlich beim allerersten Start, solange die Datenbank noch nicht existiert. Spätere Änderungen in der .env ändern das Passwort nicht mehr, dafür ist die Weboberfläche zuständig.

Der angemeldete Administrator wurde im Test über die API bestätigt:


curl -s -u admin:IHR-PASSWORT http://127.0.0.1:18080/current/user

{"id":1,"name":"admin","admin":true,"createdAt":"2026-09-16T10:00:34.440612241+02:00"}

Für den täglichen Betrieb empfiehlt sich ein zusätzlicher Standardbenutzer ohne Administratorrechte. Auch das wurde im Test ausgeführt:


# Nicht-privilegierten Benutzer anlegen
curl -s -u admin:IHR-PASSWORT http://127.0.0.1:18080/user \
  -F "name=monitoruser" \
  -F "pass=EIN-STARKES-PASSWORT" \
  -F "admin=false"

{"id":2,"name":"monitoruser","admin":false,"createdAt":"2026-09-16T10:02:14.461961829+02:00"}

Jede Meldequelle braucht anschließend eine eigene Application. Der zurückgegebene Token ist der Schlüssel zum Senden. Ab Gotify 3 wird er laut offizieller Dokumentation nur einmal angezeigt, nämlich beim Anlegen oder beim Rotieren. Er muss also sofort im Passwortmanager landen.


# Application fuer das Monitoring anlegen
curl -s -u admin:IHR-PASSWORT http://127.0.0.1:18080/application \
  -F "name=monitoring" \
  -F "description=Alerts aus dem Monitoring"

Die Antwort im Test, der Token ist hier gekürzt dargestellt:


{"id":1,"token":"gtfya.8SJmFpc4...","name":"monitoring","description":"Alerts aus dem Monitoring","internal":false,"image":"static/defaultapp.png","defaultPriority":0,"createdAt":"2026-09-16T10:00:45.811261134+02:00","lastUsed":null,"sortKey":"a0"}

Für die Android-App wird ein Client-Token benötigt, das anders aufgebaut ist als ein Application-Token. Auch dieser Aufruf lief im Test erfolgreich durch und lieferte ein Token mit dem Präfix gtfyc. zurück, während Application-Token mit gtfya. beginnen. Diese Unterscheidung hilft bei der Fehlersuche erheblich.


# Client-Token fuer ein Endgeraet erzeugen
curl -s -u admin:IHR-PASSWORT http://127.0.0.1:18080/client -F "name=handy-admin"

Funktions- und Healthcheck

Drei Prüfungen reichen, um den Dienst als betriebsbereit zu betrachten. Alle drei wurden im Test durchgeführt.

Erstens die Versionsabfrage. Sie benötigt keine Anmeldung und eignet sich als einfachster Erreichbarkeitstest:


curl -s http://127.0.0.1:18080/version

{"version":"3.1.1","commit":"e6361caa512547052b1013dbf4544e6d7ff32604","buildDate":"2026-09-15-18:26:53"}

Zweitens der Gesundheitsstatus. Dieser Endpunkt prüft zusätzlich die Datenbankverbindung und eignet sich als Ziel für externe Überwachung:


curl -s http://127.0.0.1:18080/health

{"health":"green","database":"green"}

Drittens der echte Push. Hier zeigt sich, ob der Application-Token funktioniert. Der folgende Aufruf entspricht dem, was später ein Backup-Skript absetzen würde:


# Token aus dem Anlegen der Application einsetzen
export GOTIFY_TOKEN="gtfya.IHR-APPLICATION-TOKEN"

curl -s "http://127.0.0.1:18080/message" \
  -H "X-Gotify-Key: $GOTIFY_TOKEN" \
  -F "title=Backup fehlgeschlagen" \
  -F "message=Job nightly-backup auf srv-01 endete mit Exit-Code 1." \
  -F "priority=8"

Die Antwort im Test:


{"id":1,"appid":1,"message":"Job nightly-backup auf srv-01 endete mit Exit-Code 1.","title":"Backup fehlgeschlagen","priority":8,"date":"2026-09-16T10:01:14.061735815+02:00"}

Wer lieber JSON sendet, etwa aus einem Skript mit strukturierten Daten, kann das ebenfalls tun. Auch diese Variante wurde im Test erfolgreich ausgeführt und lieferte eine Meldung mit der ID 2 zurück:


curl -s -X POST "http://127.0.0.1:18080/message" \
  -H "X-Gotify-Key: $GOTIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title":"Testalarm","message":"JSON-Push funktioniert.","priority":5}'

Die Gegenprobe, dass ein ungültiger Token wirklich abgewiesen wird, ergab im Test HTTP 401 mit der Meldung you need to provide a valid access token or user credentials to access this api. Der Endpunkt ist also nicht offen.

Die Priorität steuert laut offizieller Dokumentation, wie die Clients eine Meldung darstellen, etwa ob die Android-App einen Ton abspielt. Eine sinnvolle Konvention im Betrieb ist: 0 bis 3 für reine Information, 4 bis 7 für Warnungen, ab 8 für Störungen, die sofortige Aufmerksamkeit verlangen. Das Verhalten der Android-App bei unterschiedlichen Prioritäten wurde hier nicht selbst getestet.

Integration in Monitoring und Skripte

Der eigentliche Nutzen entsteht, wenn bestehende Systeme automatisch melden. Für Shell-Skripte genügt eine kleine Hilfsfunktion, die nur bei Fehlern sendet:


#!/usr/bin/env bash
set -euo pipefail

GOTIFY_URL="https://push.example.com"
# Token aus geschuetzter Datei lesen, nicht im Skript hinterlegen
GOTIFY_TOKEN="$(cat /etc/gotify/backup.token)"

notify() {
  local title="$1"
  local msg="$2"
  local prio="${3:-5}"
  curl -sS --max-time 10 "${GOTIFY_URL}/message" \
    -H "X-Gotify-Key: ${GOTIFY_TOKEN}" \
    -F "title=${title}" \
    -F "message=${msg}" \
    -F "priority=${prio}" > /dev/null
}

if ! /usr/local/bin/backup.sh; then
  notify "Backup fehlgeschlagen" "Host $(hostname), siehe journalctl -u backup" 8
  exit 1
fi

Wichtig ist der Zeitüberschreitungsparameter --max-time. Ohne ihn kann ein nicht erreichbarer Gotify-Server ein Backup-Skript blockieren. Die Alarmierung darf niemals der Grund sein, warum ein Wartungsjob hängt.

Für die Anbindung weiterer Systeme gilt: Jede Software, die einen generischen Webhook oder eine benutzerdefinierte HTTP-Benachrichtigung unterstützt, kann Gotify ansprechen. Uptime Kuma bringt laut Projektdokumentation einen eigenen Gotify-Benachrichtigungstyp mit, bei dem nur Server-URL und Application-Token einzutragen sind. Für den Prometheus Alertmanager existieren Brücken-Dienste von Drittanbietern, weil der Alertmanager selbst Webhooks in einem eigenen Format sendet. Beide Integrationen wurden in diesem Testlauf nicht selbst geprüft und sind hier nur als Hinweis auf den üblichen Weg genannt.

Eine Empfehlung aus der Praxis: Pro System eine eigene Application anlegen, nicht einen Sammeltoken für alles. Nur so lässt sich ein einzelner Token bei Verdacht auf Kompromittierung zurückziehen, ohne die gesamte Alarmierung lahmzulegen.

Persistente Daten, Volumes und Rechte

Alle zustandsbehafteten Daten liegen unterhalb von /app/data im Container. Im Test entstanden dort nach dem ersten Start folgende Einträge:


ls -la /opt/gotify/gotify_data/

-rw-r--r-- 1 root root 69632 Sep 16 10:00 gotify.db
drwxr-xr-x 2 root root    40 Sep 16 10:00 images
drwxr-xr-x 2 root root    40 Sep 16 10:00 plugins

Die Datei gotify.db enthält Benutzer, Applications, Token und alle gespeicherten Meldungen. Im Verzeichnis images liegen hochgeladene Anwendungssymbole. Beides gehört zusammen in das Backup. Ein Backup allein der Datenbank stellt die Symbole nicht wieder her.

Im Test lief der Container als root und legte die Dateien entsprechend an. Laut offizieller Dokumentation lässt sich Gotify auch unter einer eigenen Benutzerkennung betreiben, indem das Datenverzeichnis vorher übereignet und in der Compose-Datei ein user-Eintrag gesetzt wird:


# Vor dem ersten Start, Beispiel mit UID und GID 1234
sudo chown -R 1234:1234 /opt/gotify/gotify_data

    # Ergaenzung in compose.yaml unterhalb des Dienstes gotify
    user: "1234:1234"

Diese Variante wurde hier nicht selbst getestet. Wer sie nachträglich einführt, muss die Eigentumsrechte des bestehenden Datenverzeichnisses zwingend mit anpassen, sonst startet der Dienst nicht mehr.

Sichere Netzwerkfreigabe, Reverse Proxy und TLS

Gotify spricht im Container unverschlüsseltes HTTP. Da sowohl das Anmeldepasswort als auch die Application-Token über die Leitung gehen, ist ein Zugriff ohne TLS außerhalb eines vertrauenswürdigen Netzes nicht vertretbar. Zwei Wege sind üblich.

Der empfohlene Weg ist ein vorgelagerter Reverse Proxy. Wichtig ist dabei die korrekte Weiterleitung der WebSocket-Verbindung, denn der Live-Stream der Weboberfläche und der Apps läuft über einen Upgrade auf WebSocket. Fehlen die entsprechenden Header, funktionieren Anmeldung und Versand zwar, die Oberfläche zeigt neue Meldungen aber erst nach einem Neuladen an. Die folgende nginx-Konfiguration orientiert sich an der offiziellen Reverse-Proxy-Dokumentation des Projekts, wurde in diesem Testlauf jedoch nicht selbst gegen einen echten nginx geprüft:


server {
    listen 443 ssl;
    http2 on;
    server_name push.example.com;

    ssl_certificate     /etc/letsencrypt/live/push.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/push.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:18080;
        proxy_http_version 1.1;

        # Notwendig fuer den WebSocket-Stream
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Bilder von Applications koennen groesser sein
        client_max_body_size 10M;

        # Lange Standzeit fuer den Stream
        proxy_read_timeout 3600s;
    }
}

Beim Betrieb hinter einem Proxy gehören zwei Einstellungen in die .env: GOTIFY_SERVER_SECURECOOKIE=true, damit das Sitzungs-Cookie nur über HTTPS übertragen wird, und GOTIFY_SERVER_TRUSTEDPROXIES mit der Adresse des Proxys, damit im Log die echte Client-IP erscheint statt der des Proxys.

Alternativ kann Gotify TLS selbst übernehmen, inklusive automatischer Zertifikate von Let's Encrypt über GOTIFY_SERVER_SSL_ENABLED, GOTIFY_SERVER_SSL_LETSENCRYPT_ENABLED, GOTIFY_SERVER_SSL_LETSENCRYPT_ACCEPTTOS und GOTIFY_SERVER_SSL_LETSENCRYPT_HOSTS. Das setzt voraus, dass der Host aus dem Internet auf Port 80 und 443 erreichbar ist und der Name öffentlich auflöst. Diese Variante wurde hier nicht getestet. Wer ohnehin einen Proxy für andere Dienste betreibt, fährt mit der Proxy-Variante meist einfacher.

Unabhängig vom gewählten Weg sollte die Verwaltungsoberfläche nach Möglichkeit nicht offen im Internet stehen. Eine Einschränkung auf das Firmennetz oder ein VPN ist sinnvoll, während der reine Sende-Endpunkt /message von den meldenden Systemen erreichbar bleiben muss.

Backup und Wiederherstellung

Weil SQLite eine einzelne Datei nutzt, ist das Backup unkompliziert. Es gibt aber eine Stolperfalle: Eine laufende Datenbank darf nicht einfach im laufenden Betrieb kopiert werden, sonst droht ein inkonsistenter Stand. Der sichere Weg ist, den Container kurz anzuhalten. Die Ausfallzeit liegt bei wenigen Sekunden.


cd /opt/gotify

# Dienst anhalten, damit die SQLite-Datei konsistent ist
docker compose stop

# Datenverzeichnis inklusive Datenbank und Bildern sichern
sudo tar czf /var/backups/gotify-$(date +%F).tar.gz -C /opt/gotify gotify_data

# Dienst wieder starten
docker compose start

Dieser Ablauf wurde im Test durchgeführt. Nach dem Neustart antwortete /version wieder korrekt mit Version 3.1.1. Das Archiv des frisch angelegten Testsystems war 1.844 Byte groß.

Ein Backup, das nie zurückgespielt wurde, ist kein Backup. Deshalb wurde auch die Wiederherstellung im Test ausgeführt: Datenverzeichnis vollständig gelöscht, aus dem Archiv zurückgespielt, Dienst gestartet. Danach waren beide zuvor gesendeten Testmeldungen wieder vorhanden.


cd /opt/gotify

# Dienst anhalten
docker compose stop

# ACHTUNG: loescht den aktuellen Datenbestand unwiderruflich
sudo rm -rf /opt/gotify/gotify_data

# Stand aus dem Archiv zurueckspielen
sudo tar xzf /var/backups/gotify-2026-09-16.tar.gz -C /opt/gotify

# Dienst starten und pruefen
docker compose start
curl -s -u admin:IHR-PASSWORT "http://127.0.0.1:18080/message?limit=5"

Die Prüfung nach der Wiederherstellung ergab im Test zwei Meldungen mit den IDs 1 und 2 sowie den ursprünglichen Titeln. Applications und Token blieben ebenfalls erhalten, weil sie in derselben Datenbank liegen.

Ein Hinweis für alle, die auf ein Online-Backup ohne Stopp hoffen: Das offizielle Image enthält kein sqlite3-Kommandozeilenwerkzeug, ein sqlite3 .backup im Container funktioniert also nicht. Das wurde im Test überprüft. Wer ein konsistentes Sicherungsverfahren ohne Dienstunterbrechung braucht, müsste entweder auf dem Host ein passendes Werkzeug gegen die Datei einsetzen oder auf MySQL beziehungsweise PostgreSQL als Datenbank wechseln.

Updates und Rollback

Der Update-Ablauf ist der übliche Dreischritt. Empfehlenswert ist, in der Compose-Datei eine feste Version wie gotify/server:3.1.1 einzutragen statt latest. Nur dann ist nachvollziehbar, welcher Stand läuft, und nur dann ist ein Rollback überhaupt planbar.


cd /opt/gotify

# Immer zuerst sichern
docker compose stop
sudo tar czf /var/backups/gotify-vor-update-$(date +%F).tar.gz -C /opt/gotify gotify_data
docker compose start

# Versionsangabe in compose.yaml auf die neue Version aendern, dann
docker compose pull
docker compose up -d

# Kontrolle
docker compose ps
curl -s http://127.0.0.1:18080/version

Zur Rollback-Grenze gehört eine ehrliche Einordnung: Ein Zurücksetzen des Images auf eine ältere Version ist nur dann gefahrlos, wenn das Update kein Datenbankschema verändert hat. Gotify führt beim Start Migrationen durch, und eine bereits migrierte Datenbank versteht eine ältere Programmversion unter Umständen nicht mehr. Der verlässliche Rollback besteht deshalb immer aus zwei Teilen: alte Image-Version und das vor dem Update erstellte Datenarchiv. Wer nur das Image zurückdreht, riskiert einen Dienst, der nicht mehr startet.

Besondere Aufmerksamkeit verlangt der Sprung von Gotify 2.x auf 3.x, weil sich das Konfigurationsmodell von der YAML-Datei auf Umgebungsvariablen geändert hat. Das Projekt stellt dafür einen eigenen Migrationsleitfaden bereit, der vor dem Update gelesen werden sollte.

Typische Fehler und Diagnose

HTTP 401 beim Senden einer Meldung. Im Test trat dieser Fall bei einem absichtlich falschen Token auf. Die Antwort lautete {"error":"Unauthorized","errorCode":401,...}, im Log erschien eine WRN-Zeile mit Pfad /message und Status 401. Die häufigste Ursache ist ein verwechseltes Token: Zum Senden wird ein Application-Token mit Präfix gtfya. benötigt, nicht ein Client-Token mit Präfix gtfyc.. Da Token seit Version 3 nur einmal angezeigt werden, hilft bei Verlust nur das Rotieren beziehungsweise das Neuanlegen der Application.

Passwortänderung in der .env wirkt nicht. GOTIFY_DEFAULTUSER_PASS wird nur ausgewertet, wenn die Datenbank neu angelegt wird. Existiert gotify.db bereits, bleibt das alte Passwort gültig. Die Änderung erfolgt über die Weboberfläche. Ein Löschen der Datenbank, um das Passwort zurückzusetzen, vernichtet alle Applications und Token.

Container bleibt auf health: starting. Unmittelbar nach dem Start ist das normal, im Test dauerte der Wechsel auf healthy weniger als eine Minute. Bleibt der Status dauerhaft auf starting oder wechselt er auf unhealthy, liefert docker compose logs in der Regel den Grund, meist Schreibrechte auf dem Datenverzeichnis.

Weboberfläche zeigt neue Meldungen erst nach Neuladen. Klassisches Symptom einer Reverse-Proxy-Konfiguration ohne WebSocket-Weiterleitung. Die Header Upgrade und Connection müssen gesetzt sein, ebenso proxy_http_version 1.1.

Im Log steht immer die Proxy-IP. Solange GOTIFY_SERVER_TRUSTEDPROXIES leer ist, ignoriert Gotify den weitergereichten X-Forwarded-For-Header. Erst mit der eingetragenen Proxy-Adresse taucht die echte Client-IP im Log auf.

Port bereits belegt. Wenn docker compose up -d mit einem Bind-Fehler abbricht, läuft auf dem gewählten Hostport bereits ein Dienst. Der belegende Prozess lässt sich mit ss -ltnp | grep 18080 finden, alternativ wird in der Compose-Datei ein anderer Port gewählt.

Backup-Skript hängt. Fehlt beim curl-Aufruf eine Zeitbegrenzung und ist der Gotify-Server nicht erreichbar, blockiert die Benachrichtigung das aufrufende Skript. --max-time 10 gehört deshalb in jeden Aufruf aus einem Wartungsjob.

Saubere Deinstallation

Warnung: Die folgenden Befehle löschen alle Meldungen, Benutzer, Applications und Token unwiderruflich. Alle ausgerollten Application-Token werden dadurch ungültig, jedes meldende System muss neu konfiguriert werden. Wer den Bestand später noch brauchen könnte, erstellt vorher ein Archiv des Datenverzeichnisses und legt es an einem Ort außerhalb von /opt/gotify ab.


cd /opt/gotify

# Optionale Sicherung vor dem Entfernen
docker compose stop
sudo tar czf /var/backups/gotify-final-$(date +%F).tar.gz -C /opt/gotify gotify_data

# Container und Netzwerk entfernen, benannte Volumes loeschen
docker compose down -v

# ACHTUNG: loescht Datenbank, Bilder und Konfiguration endgueltig
sudo rm -rf /opt/gotify

# Image entfernen, falls nicht mehr benoetigt
docker image rm gotify/server:3.1.1

Im Test wurde docker compose down -v ausgeführt. Danach meldete docker compose ps -a keine Container mehr, das Netzwerk war entfernt und ein Aufruf von http://127.0.0.1:18080/version lief ins Leere. Nicht vergessen: Falls ein Reverse Proxy konfiguriert war, gehören dessen Serverblock und das zugehörige Zertifikat ebenfalls aufgeräumt.

Fazit für den Betrieb

Gotify ist eine der unaufgeregtesten Lösungen in seiner Klasse. Ein Container, eine SQLite-Datei, rund 5 MiB Arbeitsspeicher im Leerlauf und eine API, die sich in zwei Minuten in ein Shell-Skript einbauen lässt. Für kleine und mittlere Unternehmen, die technische Alarme zuverlässig und ohne Cloud-Umweg auf die Geräte der Administratoren bringen wollen, ist das ein sehr gutes Verhältnis von Aufwand zu Nutzen.

Die Entscheidung gegen Gotify fällt in der Regel aus zwei Gründen: fehlender offizieller iOS-Client und fehlende Eskalationslogik. Wer iPhones versorgen muss, sollte ntfy prüfen. Wer Bereitschaftspläne und Quittierungspflichten abbilden muss, braucht ohnehin eine vollwertige Alerting-Plattform. Für alles dazwischen, und das ist im Mittelstand die große Mehrheit der Fälle, erledigt Gotify seine Aufgabe zuverlässig und mit sehr wenig Pflegeaufwand.

Passende Anleitungen auf S-EDV

Quellen

GotifyDocker ComposePush-BenachrichtigungenMonitoringSelfhostingAlertingSQLite