ntfy selbst hosten: Push-Alarme per Docker Compose
Betreiben Sie ntfy auf einem eigenen VPS mit TLS, Benutzern und ACLs. Sichern Sie Topics und senden Sie Alarme aus cron sowie systemd-OnFailure-Units.
Was ein selbst gehosteter ntfy-Server leistet
Ein selbst gehosteter ntfy-Server wandelt eine HTTP-POST-Anfrage in eine Push-Benachrichtigung auf Ihrem Telefon um. Sie veröffentlichen Nachrichten mit curl. Die Nachricht erreicht die Android-App, die iOS-App, einen Browser-Tab oder jedes andere Ziel, das eine HTTP-Verbindung offen halten kann. Sie müssen keine Client-Bibliothek installieren und keinen Message Broker betreiben.
ntfy adressiert Nachrichten über ein Topic. Ein Topic ist ein Name im URL-Pfad, etwa https://ntfy.example.com/alerts. Es wird in dem Moment angelegt, in dem jemand eine Nachricht dorthin veröffentlicht. Bei einer Standardinstallation kann jeder, der diesen Namen kennt, das Topic lesen und Nachrichten dorthin schreiben. Deshalb vergleicht die Dokumentation des Projekts den Topic-Namen mit einem Passwort. Dieses Modell ist für den öffentlichen Dienst ntfy.sh geeignet. Für einen Server, der die Fehler Ihrer Backups meldet, ist es nicht geeignet. Daher aktiviert diese Anleitung die Authentifizierung, bevor die erste Nachricht gesendet wird.
Was Sie vor dem Start benötigen
Sie benötigen einen VPS mit Ubuntu 24.04 oder Debian 13, Docker Engine und dem Compose-Plugin, einen Domainnamen und sehr wenig RAM. Erstellen Sie einen DNS-A-Record (Domain Name System), der ntfy.example.com auf die öffentliche IP-Adresse des Servers verweist. Prüfen Sie anschließend, ob die Auflösung funktioniert, bevor Sie weitere Schritte ausführen.
dig +short ntfy.example.com
sudo ufw allow 80,443/tcp
sudo ufw statusdig muss die IP-Adresse Ihres Servers ausgeben. Die Ausstellung des Zertifikats schlägt fehl, wenn keine Ausgabe erfolgt, weil die Zertifizierungsstelle den Namen von außerhalb überprüft. Port 80 bleibt geöffnet, weil ACME (Automatic Certificate Management Environment), das Protokoll hinter Let's Encrypt, diesen Port für die HTTP-Challenge verwendet. Der ntfy-Container selbst erhält keinen öffentlichen Port.
ntfy-Konfigurationsdatei erstellen
Das Docker-Image enthält keine Konfigurationsdatei. Sie erstellen daher eine. Jeder spätere Befehl in dieser Anleitung liest daraus. Ermitteln Sie zunächst die Benutzer-ID und die Gruppen-ID, unter denen der Container ausgeführt wird.
id -u
id -g
sudo install -d -o "$(id -u)" -g "$(id -g)" /etc/ntfy /var/cache/ntfy /var/lib/ntfy
sudo nano /etc/ntfy/server.ymlbase-url: "https://ntfy.example.com"
listen-http: ":2586"
behind-proxy: true
cache-file: "/var/cache/ntfy/cache.db"
cache-duration: "12h"
auth-file: "/var/lib/ntfy/user.db"
auth-default-access: "deny-all"
enable-login: true
enable-signup: falseVier dieser Zeilen sind entscheidend. base-url muss die exakte öffentliche HTTPS-Adresse sein, weil ntfy daraus Links zu Anhängen und die eigenen Anfragen der Webanwendung erstellt. Ein falscher Wert führt daher zu einer Webanwendung, die geladen wird und anschließend bei jeder Aktion fehlschlägt. listen-http: ":2586" bindet an alle Schnittstellen im Container. Das wirkt zunächst unvorsichtig, ist hier aber korrekt: Der Container hat einen eigenen Netzwerk-Namensraum. Wenn dort an 127.0.0.1 gebunden würde, wäre der Port vom Host aus nicht erreichbar, und Docker könnte keine Verbindung über den veröffentlichten Port herstellen. auth-default-access: "deny-all" definiert die gesamte Sicherheitskonfiguration, weil Lese- und Schreibzugriffe für alle ohne ausdrückliche Berechtigung verweigert werden. behind-proxy: true weist ntfy an, die Client-Adresse aus dem Header X-Forwarded-For zu übernehmen. Dadurch berücksichtigen die Ratenbegrenzungen die tatsächlichen Besucher, statt den Reverse Proxy als einen einzigen sehr aktiven Client zu zählen.
enable-login: true ermöglicht der Webanwendung und den Smartphone-Apps die Anmeldung mit einem Passwort. enable-signup bleibt auf false gesetzt, weil die selbstständige Kontoerstellung auf einem privaten Server eine zusätzliche, unnötige Angriffsfläche schafft.
sudo chown "$(id -u):$(id -g)" /etc/ntfy/server.yml
sudo chmod 600 /etc/ntfy/server.ymlntfy mit Docker Compose ausführen
Fügen Sie dies in /opt/ntfy/compose.yaml ein, und ersetzen Sie 1000:1000 durch die beiden oben ausgegebenen Zahlen id -u und id -g.
services:
ntfy:
image: binwiederhier/ntfy:v2.27.0
container_name: ntfy
command: serve
user: "1000:1000"
environment:
- TZ=UTC
volumes:
- /etc/ntfy:/etc/ntfy
- /var/cache/ntfy:/var/cache/ntfy
- /var/lib/ntfy:/var/lib/ntfy
ports:
- "127.0.0.1:2586:2586"
restart: unless-stoppedcd /opt/ntfy
sudo docker compose up -d
sudo docker compose logs ntfy
curl -s http://127.0.0.1:2586/v1/healthEin gesunder Server antwortet mit {"healthy":true}. Zwei Details in dieser Compose-Datei sind absichtlich so festgelegt. Das Image ist auf v2.27.0 festgelegt, das im August 2026 aktuelle Release, und nicht auf latest. Bei latest ändert das nächste docker compose pull die Serverversion, und Sie bemerken dies erst nachträglich im Changelog. Der Port wird als 127.0.0.1:2586:2586 veröffentlicht. Dadurch ist der Container nur über die Loopback-Adresse des Hosts erreichbar. Schreiben Sie stattdessen 2586:2586, fügt Docker seine eigenen Firewall-Regeln vor Ihren Regeln ein. Der Port ist dann aus dem Internet erreichbar, obwohl ufw status angibt, dass er geschlossen ist. Beide Vorgehensweisen gelten auch für den nächsten Container, den Sie hinzufügen: ein selbst gehostetes RustDesk-Relay legt seinen Image-Tag auf dieselbe Weise fest. Es kann sich jedoch nicht hinter der Loopback-Adresse verbergen, weil seine Signal- und Relay-Ports aus dem Internet erreichbar sein müssen.
Wenn curl Connection refused ausgibt, lesen Sie das Container-Log. Ein Berechtigungsfehler bei /var/lib/ntfy/user.db bedeutet, dass die Zeile user: nicht mit dem Besitzer dieser Verzeichnisse übereinstimmt. Der Prozess kann dadurch seine eigene Datenbank nicht erstellen und wird beendet. Der Leitfaden zu den Docker-Compose-Grundlagen für einen VPS behandelt den Besitz von Volumes und Neustartrichtlinien ausführlicher.
TLS mit Caddy vorschalten
Caddy fordert das Zertifikat selbstständig an und erneuert es. Das ist der kürzeste Weg zu funktionierendem TLS (Transport Layer Security).
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
sudo chmod o+r /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install -y caddyErsetzen Sie den Inhalt von /etc/caddy/Caddyfile durch drei Zeilen.
ntfy.example.com {
reverse_proxy 127.0.0.1:2586
}sudo systemctl reload caddy
curl -s https://ntfy.example.com/v1/healthDasselbe {"healthy":true} über HTTPS bedeutet, dass der gesamte Pfad funktioniert. Ein 502 von Caddy bedeutet, dass ntfy nicht lauscht: Prüfen Sie dies mit sudo ss -lntp | grep 2586. Ein Zertifikatsfehler bedeutet meistens, dass der DNS-Eintrag falsch ist oder Port 80 blockiert wird. sudo journalctl -u caddy -n 50 zeigt, welcher Fall vorliegt. Wenn Sie später einen weiteren Dienst hinzufügen, benötigen Sie im selben Caddyfile nur einen weiteren Hostnamen-Block. So kann beispielsweise Halcyon, ein Video-Store-Frontend aus den 90er-Jahren für Ihre Jellyfin-Bibliothek auf einer zweiten Subdomain desselben Servers erreichbar werden.
Wenn Sie bereits nginx verwenden, übernehmen Sie die Proxy-Einstellungen aus der ntfy-Dokumentation: proxy_http_version 1.1, proxy_buffering off, proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for sowie Lese- und Sende-Timeouts von mindestens drei Minuten. Ein Subscriber hält eine HTTP-Verbindung so lange offen, wie er lauscht. nginx schließt eine inaktive Upstream-Verbindung standardmäßig nach 60 Sekunden. Dadurch verbinden sich Subscriber fortlaufend neu, und während dieser Unterbrechung gesendete Nachrichten gehen verloren.
Benutzer anlegen und Topics absichern
Die Authentifizierung ist aktiviert, und bisher hat niemand Zugriff auf irgendetwas. Genau das ist beabsichtigt. Legen Sie ein Administratorkonto für sich selbst und ein Maschinenkonto für Skripte an. Diese Befehle lesen /etc/ntfy/server.yml aus dem Container. Deshalb ist die Konfigurationsdatei als Volume eingebunden.
sudo docker compose exec ntfy ntfy user add --role=admin admin
sudo docker compose exec ntfy ntfy user add robot
sudo docker compose exec ntfy ntfy user listJeder Befehl fordert zur Eingabe eines Passworts auf. Ein Administrator ignoriert die Zugriffsliste und kann jedes Topic lesen und beschreiben. Verwenden Sie dieses Konto daher nur selbst und für die Telefon-App. robot ist ein normaler Benutzer ohne Zugriff, bis Sie ihm Berechtigungen erteilen.
sudo docker compose exec ntfy ntfy access robot alerts write
sudo docker compose exec ntfy ntfy access robot "alerts_*" write
sudo docker compose exec ntfy ntfy accessEin ACL-Eintrag (Access Control List) besteht aus einem Benutzer, einem Topic und einer Berechtigung. Das Topic ist entweder ein wörtlicher Name oder ein Muster, in dem * für beliebige Zeichen steht. Damit deckt alerts_* die Topics alerts_backup und alerts_db ab, ohne dass Sie für jeden Host einen eigenen Befehl ausführen müssen. Die Berechtigung write bedeutet, dass nur veröffentlicht werden darf. Dadurch kann ein aus einem Cronjob gestohlenes Token keine Topics abonnieren und die gesendeten Daten zurücklesen. Der spezielle Benutzername everyone legt fest, was ein nicht authentifizierter Besucher tun darf. Sie verwenden ihn nur, um etwas absichtlich öffentlich bereitzustellen, beispielsweise ntfy access everyone status read.
Skripte sollten ein Token und nicht Ihr Passwort verwenden.
sudo docker compose exec ntfy ntfy token add robotDer Befehl gibt ein Token aus, das mit tk_ beginnt. Ein Token übernimmt exakt die Berechtigungen des Benutzers, zu dem es gehört. Dieses Token kann daher auf den alerts-Topics veröffentlichen und sonst nichts tun. ntfy token list zeigt vorhandene Einträge an, und ntfy token remove widerruft einen Eintrag, ohne das Passwort des Benutzers zu ändern.
Ihre erste Nachricht senden und prüfen, ob die Sperre funktioniert
Prüfen Sie zuerst, ob die Tür geschlossen ist.
curl -s -o /dev/null -w '%{http_code}\n' -d "hello" https://ntfy.example.com/alertsDas gibt 403 aus. 403 ist die erwartete Antwort: auth-default-access: "deny-all" verweigert eine anonyme Veröffentlichung. Senden Sie jetzt eine echte Nachricht.
curl -H "Authorization: Bearer tk_REPLACE_WITH_YOUR_TOKEN" \
-H "Title: Nightly backup finished" \
-H "Priority: default" \
-H "Tags: white_check_mark" \
-d "42 GB copied in 11 minutes" \
https://ntfy.example.com/alertsDer Server antwortet mit der gespeicherten Nachricht als JSON. Daran erkennen Sie, dass sie akzeptiert und nicht verworfen wurde. Title ist die fett formatierte erste Zeile. Priority reicht von 1 bis 5 oder anhand des Namens von min bis urgent. Damit wird festgelegt, ob das Telefon einen Ton ausgibt. Tags werden in der Benachrichtigung zu Emojis, wenn der Name einem bekannten Emoji-Kürzel entspricht. Andernfalls bleiben sie Klartext.
Um ein Topic in einem Terminal zu überwachen, streamen Sie es:
curl -s -u admin https://ntfy.example.com/alerts/rawcurl fordert das Passwort an. Jede Nachricht wird als eine Zeile empfangen. Die gelegentlich auftretenden Leerzeilen dienen als Keepalives. Wenn Sie https://ntfy.example.com in einem Browser öffnen und sich mit demselben Konto anmelden, erhalten Sie die Web-App-Version desselben Streams.
Ratenbegrenzungen festlegen, damit ein einzelnes Script den Server nicht überlasten kann
Standardmäßig erhält jeder Besucher ein Kontingent von 60 Anfragen. Es wird mit einer Anfrage alle 5 Sekunden wieder aufgefüllt. Für einen privaten Server ist das großzügig bemessen. Ein Script in einer Endlosschleife mit wiederholten Anfragen schöpft das Kontingent jedoch vollständig aus. Ergänzen Sie die Begrenzungen in server.yml.
visitor-request-limit-burst: 30
visitor-request-limit-replenish: "10s"
visitor-message-daily-limit: 500sudo docker compose restart ntfyEin Besucher, der das Limit überschreitet, erhält HTTP 429, statt dass eine Nachricht zugestellt wird. Das Limit wird pro Besucheradresse gezählt. Deshalb ist behind-proxy: true so wichtig: Ohne diese Einstellung sieht ntfy nur die Adresse von Caddy. Dann zählt jeder Client als derselbe Besucher, und ein übermäßig aktives Script leert das Kontingent, das Ihr Telefon und Ihre anderen Server gemeinsam verwenden.
Benachrichtigung bei einem fehlgeschlagenen Cron-Job
Übergeben Sie das Token nicht in der Befehlszeile. ps aux zeigt jedem Benutzer auf dem System die vollständige Befehlszeile jedes laufenden Prozesses an. Ein mit -H übergebenes Token ist daher für die gesamte Laufzeit von curl für jedes lokale Konto lesbar. Eine curl-Konfigurationsdatei verhindert das.
sudo install -d -m 700 /etc/ntfy-alert
printf 'header = "Authorization: Bearer tk_REPLACE_WITH_YOUR_TOKEN"\n' | sudo tee /etc/ntfy-alert/curlrc
sudo chmod 600 /etc/ntfy-alert/curlrcKapseln Sie den Job nun. Speichern Sie ihn als /usr/local/bin/backup-with-alert.sh und machen Sie ihn mit chmod 750 ausführbar.
#!/bin/bash
out=$(/usr/local/bin/backup.sh 2>&1)
code=$?
if [ "$code" -ne 0 ]; then
printf '%s' "$out" | tail -c 1000 | curl -K /etc/ntfy-alert/curlrc \
-H "Title: backup.sh failed with exit $code" \
-H "Priority: high" \
-H "Tags: warning" \
--data-binary @- \
https://ntfy.example.com/alerts
fi
exit "$code"17 3 * * * /usr/local/bin/backup-with-alert.sh >> /var/log/backup-alert.log 2>&1$? wird in der Zeile unmittelbar nach dem Befehl erfasst, weil der nächste ausgeführte Befehl den Wert überschreiben würde. Die Ausgabe wird über tail -c 1000 geleitet, weil ntfy eine maximale Nachrichtengröße erzwingt und eine Benachrichtigung kein Log-Viewer ist. Das abschließende exit "$code" bewahrt den ursprünglichen Status. Dadurch erkennt jede andere Überwachung dieses Jobs weiterhin einen Fehler. Testen Sie den gesamten Ablauf, indem Sie das Skript für einen Lauf auf /bin/false verweisen.
Ein Fehlerzweig, der nie ausgeführt wird, ist schlechter als gar keine Benachrichtigung. Er erweckt den Eindruck, dass ausbleibende Meldungen Erfolg bedeuten. Cron stellt Ihrem Job eine nahezu leere Umgebung und eine wesentlich kürzere PATH als Ihre Login-Shell bereit. Daher kann ein Skript, das bei manueller Ausführung funktioniert, bereits beendet werden, bevor es die curl-Zeile erreicht. Der Leitfaden dazu, warum ein Cron-Job nicht ausgeführt wird behandelt diese Umgebungsprobleme. Verwenden Sie überall absolute Pfade. Lesen Sie nach dem ersten geplanten Lauf die Logdatei, anstatt vom Erfolg auszugehen.
Benachrichtigungen bei einem fehlgeschlagenen systemd-Unit
Cron deckt geplante Aufgaben ab. Für lang laufende Dienste benötigen Sie OnFailure=. systemd führt es aus, sobald eine Unit den Zustand failed erreicht. Erstellen Sie eine Template-Unit und verwenden Sie sie für jeden Dienst auf dem Server erneut. Speichern Sie sie als /etc/systemd/system/ntfy-unit-failed@.service.
[Unit]
Description=Send an ntfy alert because %i failed
[Service]
Type=oneshot
ExecStart=/usr/local/bin/ntfy-unit-failed %iFühren Sie anschließend /usr/local/bin/ntfy-unit-failed aus und setzen Sie den Modus auf 750:
#!/bin/bash
unit="$1"
journalctl -u "$unit" -n 15 --no-pager -o cat | tail -c 1000 | curl -K /etc/ntfy-alert/curlrc \
-H "Title: $unit failed on $(hostname -s)" \
-H "Priority: urgent" \
-H "Tags: rotating_light" \
--data-binary @- \
https://ntfy.example.com/alertsBinden Sie die Unit mit einem Drop-in an einen Dienst an. Dadurch kann ein Paket-Upgrade Ihre Änderung nicht überschreiben.
sudo systemctl edit myapp.service[Unit]
OnFailure=ntfy-unit-failed@%n.service%n wird zum vollständigen Unit-Namen erweitert. Dadurch wird die Instanz zu ntfy-unit-failed@myapp.service. %i innerhalb der Template übergibt myapp.service als erstes Argument an das Skript. So kann eine Template für jede Unit verwendet werden. Testen Sie die Funktion mit einer Unit, die absichtlich fehlschlägt. Speichern Sie sie als /etc/systemd/system/ntfy-selftest.service.
[Unit]
Description=Deliberately failing unit
OnFailure=ntfy-unit-failed@%n.service
[Service]
Type=oneshot
ExecStart=/bin/falsesudo systemctl daemon-reload
sudo systemctl start ntfy-selftest.serviceDer Startbefehl wird mit einem Exit-Status ungleich 0 beendet und gibt Job for ntfy-selftest.service failed because the control process exited with error code aus. Das Telefon sollte etwa eine Sekunde später vibrieren. Löschen Sie die Test-Unit anschließend.
Ein Problem verdient besondere Beachtung. OnFailure= wird nur ausgeführt, wenn eine Unit den Zustand failed erreicht. Ein Dienst mit Restart=always erreicht diesen Zustand möglicherweise nie, weil systemd ihn stattdessen fortlaufend neu startet. Die Unit schlägt erst fehl, wenn sie innerhalb von StartLimitIntervalSec mehr als StartLimitBurst Neustarts durchlaufen hat. Setzen Sie diese beiden Werte für jeden Dienst, über dessen Ausfall Sie informiert werden möchten. Andernfalls kann eine Neustartschleife tagelang unbemerkt laufen. Timer sind der sauberere Ersatz für das obige Cron-Muster. Die Service-Unit eines Timers erhält OnFailure= automatisch. Der Leitfaden zu systemd-Diensten und Timern auf einem VPS beschreibt die Umstellung ausführlich.
Überwachen Sie die Betriebszeit über dasselbe Topic
Uptime Kuma, der selbst gehostete Statusmonitor, unterstützt einen ntfy-Benachrichtigungstyp. Öffnen Sie Settings, anschließend Notifications und dann Setup Notification. Wählen Sie Ntfy, setzen Sie die Server-URL auf https://ntfy.example.com und das Topic auf alerts, wählen Sie eine Priorität und fügen Sie das Zugriffstoken robot ein. Senden Sie die Testbenachrichtigung, bevor Sie speichern. Bei einem falschen Topic-Namen schlägt die Zustellung mit einem write-Grant, der dieses Topic nicht abdeckt, lautlos fehl.
Die Einschränkung dieser Konfiguration ist eindeutig: Ein Monitor, der auf demselben VPS läuft, kann nicht erkennen, dass der VPS ausgefallen ist. Außerdem kann ntfy nicht melden, dass ntfy selbst ausgefallen ist. Betreiben Sie den Monitor auf einem anderen Rechner und geben Sie ihm einen zweiten Benachrichtigungskanal, beispielsweise E-Mail. Dieser Kanal überwacht dann ntfy selbst. Der Monitortyp Push von Uptime Kuma deckt den anderen blinden Fleck ab: Ihr Cronjob ruft nach einem erfolgreichen Lauf eine Push-URL auf, und Kuma alarmiert Sie, wenn diese Aufrufe ausbleiben. Ein Fehlerpfad wird nur ausgelöst, wenn der Job läuft. Er sagt daher nichts über einen Job aus, der nie gestartet wurde.
Funktioniert selbst gehostetes ntfy auf Android und iPhone?
Unter Android ja, ohne Einschränkung. Installieren Sie die App über Google Play oder F-Droid, öffnen Sie Settings, setzen Sie den Standardserver auf https://ntfy.example.com, fügen Sie Ihr Konto im Bildschirm für die Benutzerverwaltung hinzu und abonnieren Sie anschließend alerts. Für die sofortige Zustellung bleibt ein Foreground-Service aktiv. Dadurch kommen Nachrichten auch an, wenn sich das Telefon im Doze-Modus befindet. Die permanente Benachrichtigung gehört zu den Android-Anforderungen für Foreground-Services und ist kein Fehler. Der F-Droid-Build enthält überhaupt keinen Firebase-Code. Daher verwendet jedes Abonnement die sofortige Zustellung. ntfy kann außerdem als UnifiedPush-Distributor fungieren. Das ist ein offener Ersatz für den Push-Dienst von Google. Andere Apps mit UnifiedPush-Unterstützung können dadurch ebenfalls über Ihren Server Nachrichten zustellen.
Unter iOS funktioniert es mit einer Abhängigkeit, die Sie nicht entfernen können. Apple weckt eine App im Hintergrund ausschließlich über APNs (Apple push notification service). Außerdem darf nur die Partei mit den Signatur-Anmeldedaten der App Nachrichten an sie senden. Ihr Server kann die App daher nicht direkt erreichen. ntfy löst dieses Problem mit einem Relay: Ihr Server sendet eine poll_request mit der Nachrichten-ID an ntfy.sh. ntfy.sh leitet sie über Firebase und APNs weiter, um die App zu wecken. Die App ruft anschließend den Nachrichteninhalt von Ihrem Server ab.
upstream-base-url: "https://ntfy.sh"Machen Sie sich klar, welche Auswirkungen das hat. Der Nachrichteninhalt bleibt auf Ihrem Server. Die Tatsache, dass eine Nachricht eingegangen ist, sowie ihre ID laufen jedoch über Infrastruktur, die Sie nicht betreiben. Ohne diese Einstellung treffen Benachrichtigungen auf dem iPhone von einem selbst gehosteten Server verspätet oder gar nicht ein, weil nichts die App weckt. Die einzige Möglichkeit, das Relay zu entfernen, besteht darin, die iOS-App selbst mit Ihrem eigenen Apple-Developer-Konto und Ihren eigenen APNs-Schlüsseln zu erstellen und zu veröffentlichen. Dafür fallen eine jährliche Gebühr und für jedes Update ein neuer Build an. Wenn das Relay für Ihren Anwendungsfall nicht akzeptabel ist, verwenden Sie die Benachrichtigungen auf Android oder in der Desktop-Web-App.
Backups, Upgrades und das Fixieren des Images
Zwei Pfade können nicht wiederhergestellt werden: /etc/ntfy/server.yml und /var/lib/ntfy/user.db. Der zweite enthält alle Benutzer, Passwort-Hashes, ACL-Einträge und Token. Behandeln Sie ihn daher wie einen privaten Schlüssel.
sudo tar czf ntfy-backup.tgz -C / etc/ntfy var/lib/ntfy
sudo chmod 600 ntfy-backup.tgzKopieren Sie diese Datei vom Server weg. cache.db enthält nur aktuelle Nachrichten, davon 12 Stunden zusammen mit dem oben genannten cache-duration. Der Verlust dieser Datei verursacht daher keinen schützenswerten Datenverlust. Für ein Upgrade bearbeiten Sie den Tag in der Compose-Datei und führen den Pull aus.
sudo docker compose pull
sudo docker compose up -d
curl -s https://ntfy.example.com/v1/healthLesen Sie zuerst die Release Notes. Die SQLite-Datenbanken werden beim Start migriert. Nach einer Schemaänderung ist ein Rollback auf einen älteren Tag daher nicht sicher. Bewahren Sie das gerade erstellte Backup auf, bis die neue Version einen Tag lang ausgeführt wurde. Jeder Dienst auf dem Server hat eine eigene kurze Liste von Pfaden, die nicht wiederhergestellt werden können. Der Vergleich von PhotoPrism und Immich ermittelt diese Liste sowie die zugehörigen Backup-Befehle für eine Fotobibliothek auf einem vergleichbaren VPS.
Gotify und Apprise
Gotify ist die kleinere Lösung: eine Binärdatei mit Weboberfläche und Android-App, ohne Topic-Wildcards und ohne offiziellen iOS-Client. Das eignet sich für einen privaten Server, auf dem Android das einzige Ziel ist. Apprise ist eine Python-Bibliothek und ein Kommandozeilenwerkzeug, kein Server. Es verteilt eine einzelne Nachricht an mehr als hundert Dienste, darunter ntfy. Das eignet sich für ein Skript, das mehrere Ziele gleichzeitig erreichen muss. ntfy bietet einen Server, eine HTTP-API und Apps für beide mobilen Plattformen. Deshalb ist ntfy für Benachrichtigungen von einem gemieteten Server die übliche Lösung.
FAQ
Warum liefert die Veröffentlichung an meinen ntfy-Server den Status 403?
Mit auth-default-access: "deny-all" in server.yml wird eine anonyme Veröffentlichung abgewiesen. Das ist das beabsichtigte Verhalten. Senden Sie Anmeldedaten mit -u user:pass oder -H "Authorization: Bearer tk_...". Wenn Sie bereits ein Token senden und trotzdem 403 erhalten, besitzt der Benutzer hinter diesem Token keinen passenden ACL-Eintrag für das Topic. Führen Sie ntfy access aus, um die vollständige Liste auszugeben. Beachten Sie, dass eine write-Berechtigung kein Abonnieren erlaubt. Ein Konto, das problemlos veröffentlicht, wird daher weiterhin abgewiesen, wenn es versucht, dasselbe Topic zu lesen.
Funktionieren Benachrichtigungen auf dem iPhone mit einem selbst gehosteten ntfy-Server?
Ja, aber nur über ein Relay, das Sie nicht umgehen können. Apple aktiviert Apps ausschließlich über APNs (Apple push notification service), und nur der Herausgeber der App kann dorthin senden. Deshalb leitet ntfy ein poll_request mit der Nachrichten-ID an ntfy.sh weiter. ntfy.sh leitet es anschließend an das Gerät weiter. Setzen Sie upstream-base-url: "https://ntfy.sh" in server.yml und starten Sie den Container neu. Der Nachrichtentext selbst wird weiterhin von Ihrem Server abgerufen. Ohne diese Einstellung werden iOS-Benachrichtigungen verzögert oder gar nicht angezeigt.
Warum ist die ntfy-Benachrichtigung meines Cronjobs nie angekommen?
Führen Sie die curl-Zeile zunächst manuell aus, um zu prüfen, ob Token und Topic korrekt sind. Wenn sie manuell funktioniert, aber nicht über cron, liegt der Fehler vor der Benachrichtigung: cron führt Jobs mit einer minimalen Umgebung und einem kurzen PATH aus. Ein Skript, das einen Befehl nur über seinen Namen aufruft, kann daher abbrechen, bevor es die curl-Zeile erreicht. Verwenden Sie absolute Pfade, leiten Sie die Ausgabe des Jobs in eine Logdatei um und lesen Sie diese Datei nach dem nächsten Lauf. Eine 429-Antwort statt einer Zustellung bedeutet, dass die Ratenbegrenzung funktioniert und Ihr Skript zu schnell erneut versucht.
Sollte ich ntfy im öffentlichen Internet bereitstellen?
Die Telefon-Apps müssen den Server aus Mobilfunknetzen erreichen können. Daher ist ein öffentlicher HTTPS-Endpunkt mit auth-default-access: "deny-all" und ACLs pro Topic die übliche Konfiguration. Sie ist sicher, solange kein Topic für everyone lesbar ist. Eine Instanz, die ausschließlich über ein VPN erreichbar ist, ist sinnvoll, wenn jeder Abonnent ein von Ihnen kontrollierter Rechner ist. Für Telefone ist sie jedoch schlecht geeignet. Die App empfängt nur, solange der Tunnel aktiv ist. Benachrichtigungen werden daher in die Warteschlange gestellt, bis das Telefon die Verbindung wiederherstellt.