Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 36 additions & 33 deletions tutorials/hetzner-ddns-bridge/01.de.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
SPDX-License-Identifier: MIT
path: "/tutorials/hetzner-ddns-bridge/de"
slug: "hetzner-ddns-bridge"
date: "2026-02-03"
title: "Hetzner DynDNS Bridge: Hetzner Console API und DNS Console API"
short_description: "Ein PHP-/SQLite-Skript, das Hetzner-DNS-Zonen über die APIs der DNS Console (Legacy) und der Hetzner Console synchron hält."
date: "2026-08-07"
title: "Hetzner DynDNS Bridge für die Hetzner Console API"
short_description: "Ein PHP-/SQLite-Skript, das einen DynDNS-Endpunkt bereitstellt und Hetzner-DNS-Zonen über die Hetzner Console API synchron hält."
tags: ["Hetzner DNS", "Hetzner Console", "Automation", "PHP"]
author: "woehrl"
author_link: "https://github.com/woehrl"
Expand All @@ -18,14 +18,17 @@ cta: "cloud"

## Einführung

Deine IPv4/IPv6 soll folgen wie ein treuer Hund, ohne dass du DNS von Hand pflegen musst? Dieses kleine PHP-Skript spielt DynDNS-Server, spricht sowohl die [Hetzner Console](https://console.hetzner.com/) API als auch die Legacy [DNS Console](https://dns.hetzner.com/) API und versteht sich mit Routern wie einer Fritz!Box. Zuerst kommt die Schritt-für-Schritt-Anleitung für Einsteiger, danach die Nerd-Ecke mit den Details. Neue Zonen lassen sich seit 10. November 2025 nicht mehr in der DNS Console anlegen; plane die Migration in die Hetzner Console ein und halte DNS Console nur während des Umzugs aktiv.
Deine IPv4/IPv6 soll folgen wie ein treuer Hund, ohne dass du DNS von Hand pflegen musst? Dieses kleine PHP-Skript spielt DynDNS-Server, spricht die [Hetzner Console](https://console.hetzner.com/) API und versteht sich mit Routern wie einer Fritz!Box. Zuerst kommt die Schritt-für-Schritt-Anleitung für Einsteiger, danach die Nerd-Ecke mit den Details.

> **Update August 2026:** Hetzner hat die alte DNS Console (`dns.hetzner.com`) samt Legacy-API im Mai 2026 abgeschaltet; verbliebene Zonen wurden automatisch in die Hetzner Console migriert. Dieses Tutorial und das Skript wurden entsprechend aktualisiert und nutzen ausschließlich die Hetzner Console API. Außerdem neu: Unterstützung für IPv6-only-Anschlüsse (DS-Lite), dedizierte `myip6`-/`myipv6`-Parameter und protokollkonforme `nochg`-Antworten. Wer noch eine ältere Version des Skripts einsetzt, findet Migrationshinweise in der Nerd-Ecke.

**Voraussetzungen**

- Ein Hetzner-Account mit mindestens einer DNS-Zone (Hetzner Console oder DNS Console während der Migration).
- Ein Hetzner-Account mit mindestens einer DNS-Zone in der [Hetzner Console](https://console.hetzner.com/).
- PHP mit den Erweiterungen `curl` und `SQLite3` (Webspace oder kleine VM reicht).
- Einen Ort für das PHP-Skript und einen Cronjob alle paar Minuten.
- Einen Client, der eine DynDNS-URL aufrufen kann (Router, NAS oder ein einfacher curl-Aufruf).
- Die A-/AAAA-Records, die aktualisiert werden sollen, müssen in der Zone bereits existieren — das Skript ändert nur Werte und legt bewusst keine neuen Records an.

## Schritt 0 - Beispiel-Setup auf Debian/Ubuntu

Expand Down Expand Up @@ -72,7 +75,7 @@ Falls du von einem blanken Debian/Ubuntu startest, bringen dich diese Befehle zu

Nutze die zu diesem Tutorial gebündelten Dateien in `tutorials/hetzner-ddns-bridge/scripts`:

> <small>Gespiegelt von https://github.com/woehrl/hetzner-dyndns, Commit `11582e6`. Kopiere sie auf deinen Webspace oder eine kleine VM.</small>
> <small>Gespiegelt von https://github.com/woehrl/hetzner-dyndns, Commit `fbb3728`. Kopiere sie auf deinen Webspace oder eine kleine VM.</small>

* [GitHub » tutorials/hetzner-ddns-bridge/scripts](https://github.com/hetzneronline/community-content/tree/master/tutorials/hetzner-ddns-bridge/scripts)

Expand Down Expand Up @@ -123,13 +126,14 @@ Bearbeite `hetzner_dyndns.config.php`:
| -- | ------------ |
| <kbd style="background-color:#e2e2e2!important;color:#000;border-radius:6px;">auth_user</kbd> | Optional: Username für HTTP Basic Auth. Wenn leer, wird <kbd style="background-color:#e2e2e2!important;color:#000;border-radius:6px;">update</kbd> verwendet. |
| <kbd style="background-color:#e2e2e2!important;color:#000;border-radius:6px;">auth_password</kbd> | Bestimme ein starkes, geteiltes Passwort. Dein Router nutzt es. |
| <kbd style="background-color:#e2e2e2!important;color:#000;border-radius:6px;">api_order</kbd> | Lege fest, welche API zuerst probiert wird: Verwende <kbd style="background-color:#e2e2e2!important;color:#000;border-radius:6px;">['console', 'dns']</kbd> für Migration oder <kbd style="background-color:#e2e2e2!important;color:#000;border-radius:6px;">['console']</kbd>, wenn alle Zonen auf der neuen API liegen. |
| <kbd style="background-color:#e2e2e2!important;color:#000;border-radius:6px;">console_token</kbd><br><kbd style="background-color:#e2e2e2!important;color:#000;border-radius:6px;">dns_token</kbd> | Tokens hinzufügen:<ul><li><b>Hetzner Console:</b> Erstelle ein API-Token in der Hetzner Console und trage es bei <kbd style="background-color:#e2e2e2!important;color:#000;border-radius:6px;">console_token</kbd> ein.</li><li><b>DNS Console</b> (legacy): Erstelle ein DNS-Token in der DNS Console und trage es bei <kbd style="background-color:#e2e2e2!important;color:#000;border-radius:6px;">dns_token</kbd> ein (nur wenn du dort noch Zonen hast).</li></ul> |
| <kbd style="background-color:#e2e2e2!important;color:#000;border-radius:6px;">console_token</kbd> | Erstelle in der Hetzner Console ein Projekt-API-Token mit Lese-/Schreibrechten für DNS und trage es hier ein. Pflichtfeld pro Realm. |
| <kbd style="background-color:#e2e2e2!important;color:#000;border-radius:6px;">zone_name</kbd> | Wenn die IP-Adresse für eine Subdomain (z.B. `sub.example.com`) aktualisiert werden soll, muss hier die Hauptdomain (z.B. `example.com`) angegeben werden. |
| <kbd style="background-color:#e2e2e2!important;color:#000;border-radius:6px;">auth_realm</kbd> | Optional: Wähle ein Label (z. B. "dynbridge"). |
| <kbd style="background-color:#e2e2e2!important;color:#000;border-radius:6px;">history_db</kbd> | Optional: Zeige auf einen beschreibbaren Pfad (z. B.<br><kbd style="background-color:#e2e2e2!important;color:#000;border-radius:6px;">__DIR__ . '/hetzner_dyndns.sqlite3'</kbd>). |
| TTL | Optional: Passe die TTL pro Realm an, wenn es schneller oder langsamer propagieren soll. |

> **Hinweis für Bestandsinstallationen:** Die früheren Einstellungen `dns_token`, `dns_endpoint` und `api_order` gehörten zur abgeschalteten Legacy-API und werden ignoriert. Es genügt, pro Realm ein `console_token` zu ergänzen — der Rest der Config kann bleiben.

## Schritt 3 - Sicher ins Netz stellen

- Halte `hetzner_dyndns.config.php` aus öffentlichen Repos und Verzeichnislisten heraus.
Expand All @@ -149,60 +153,58 @@ Bearbeite `hetzner_dyndns.config.php`:
- Nutze HTTP Basic Auth mit `auth_user`/`auth_password` (User defaultet auf `update`, wenn leer). DynDNS-Clients senden meist irgendeinen Usernamen plus das Passwort; per `curl` geht es so:
```bash
curl -u user:deinPasswort \
"https://dein-ddns-host.example.com/nic/update?hostname=myhost.example.com&myip=$(curl -s ifconfig.me)"
"https://dein-ddns-host.example.com/nic/update?hostname=myhost.example.com&myip=$(curl -4 https://ip.hetzner.com)"
```
- Erfolgreiche Antworten sind `good <ip>` (aktualisiert) oder `nochg <ip>` (nichts zu tun). Alles andere: ins Debug-Log schauen.
- Kommt „No matching rrset found", existiert der A-/AAAA-Record noch nicht: einmalig in der Hetzner Console anlegen, dann klappt das Update.

## Schritt 5 - Automatisieren

- Cron alle 5 Minuten (nach Bedarf anpassen):
```bash
*/5 * * * * php /path/to/hetzner_dyndns.php --cron --realm=default
```
- Richte deinen Router oder dein NAS auf dieselbe `nic/update`-URL mit dem gesetzten Passwort ein. IPv6 klappt, wenn der Client `myipv6` mitsendet.
- Richte deinen Router oder dein NAS auf dieselbe `nic/update`-URL mit dem gesetzten Passwort ein.
- IPv4 und IPv6 zusammen: entweder kommagetrennt über `myip=<IPv4>,<IPv6>` oder über einen dedizierten Parameter `myip6=<IPv6>` bzw. `myipv6=<IPv6>` (Fritz!Box: `myip=<ipaddr>&myip6=<ip6addr>` in der Update-URL).
- IPv6-only/DS-Lite: einfach nur die IPv6 senden (`myip6=...` ohne `myip`) — das Skript aktualisiert dann ausschließlich den AAAA-Record und schreibt bewusst keine Carrier-IPv4 in den A-Record.
- Legacy-Clients dürfen weiterhin `X-Authentication: <passwort>` oder `?p=<passwort>` (nur Passwort) senden, empfohlen ist Basic Auth.

## Schritt 6 - Kurzer Troubleshooting-Spickzettel

- 401 oder Auth-Prompt: Passwort stimmt nicht oder `.htaccess` greift nicht.
- `not_found`: falsche Zone oder Hostname, oder fehlende Token-Rechte.
- `Zone not found on console API`: falscher `zone_name`, Zone liegt in einem anderen Console-Projekt oder das Token gehört zum falschen Projekt.
- `No matching rrset found`: den A-/AAAA-Record einmalig in der Zone anlegen — das Skript legt bewusst keine Records an.
- SQLite-Schreibfehler: Dateirechte anpassen oder DB in einen beschreibbaren Pfad verschieben.
- Nichts ändert sich: `api_order` prüfen und das richtige Token pro Realm setzen.
- Nichts ändert sich: `console_token` pro Realm prüfen (DNS-Schreibrechte!) und mit `debug` das Log ansehen.

## Nerd-Ecke (so funktioniert es wirklich)

* **Architektur im Überblick**
- Ein PHP-Skript, eine Config, eine SQLite-DB. DynDNS-Aufrufe (`/nic/update` oder `/v3/update`) werden via `.htaccess` auf `hetzner_dyndns.php` umgeschrieben.
- Das Skript authentifiziert mit Benutzername/Passwort, parst Hostname und optionales `realm`, cached bekannte Records in SQLite und antwortet sofort mit `good`, wenn sich nichts geändert hat.
- Das Skript authentifiziert mit Benutzername/Passwort, parst Hostname und optionales `realm`, cached den letzten IP-Stand und die Zonen-ID in SQLite und antwortet sofort mit `nochg`, wenn sich nichts geändert hat — ganz ohne API-Call.

<br>

* **Konfig-Details**
- Gemeinsame Einstellungen: `auth_user`, `auth_password`, `auth_realm`, `history_db` sowie optional `debug`/`debug_log`.
- Benachrichtigungen: `notifications.enabled` aktivieren, `php` oder `smtp` wählen, Empfänger setzen und entscheiden, ob bei Erfolg, Fehler oder beidem gemailt wird.
- Realms: pro Realm `ttl`, `api_order`, `zone_name`, `console_token` und `dns_token`. Setze `zone_name`, wenn der DynDNS-Endpunkt auf einer Subdomain liegt, du aber die Hauptzone aktualisierst.
- Realms: pro Realm `ttl`, `zone_name` und `console_token`. Setze `zone_name`, wenn der DynDNS-Endpunkt auf einer Subdomain liegt, du aber die Hauptzone aktualisierst.

<br>

* **"Hetzner Console"-API-Ablauf** (aktuell)
1. Zone über `/zones?name=<zone>` mit dem Hetzner Console API-Token finden.
* **API-Ablauf (Hetzner Console)**
1. Zone über `/zones?name=<zone>` mit dem Hetzner Console API-Token finden — die Zonen-ID wird danach in SQLite gecacht, gealterte IDs heilen sich per erneutem Lookup selbst.
2. RRsets über `/zones/{id}/rrsets` (A und AAAA) holen.
3. RRset-Namen mit dem Host abgleichen (inkl. `@` am Apex).
4. `set_records` aufrufen, um die IPs zu ersetzen. Erfolgreich = Action-Response; Fehler melden `not_found` oder fehlende Rechte.

<br>

* **"DNS Console"-API-Ablauf** (Legacy)
1. Zonen-ID mit `/zones?name=<zone>` über `dns_token` holen.
2. A/AAAA-Record-IDs finden, in SQLite cachen und per `PUT /records/{id}` aktualisieren.
3. Fehler bleiben als `needs_sync` für Cron-Retries markiert.
3. Den RRset-Namen **exakt** mit dem Host abgleichen (inkl. `@` am Apex). Es wird nur der exakt passende Record aktualisiert; Fallbacks auf Eltern-Records gibt es absichtlich nicht.
4. `set_records` aufrufen, um die IPs zu ersetzen — und zwar nur für die Adressfamilien, die der Client tatsächlich geliefert hat (IPv4-only, Dual-Stack oder IPv6-only).
5. Fehler bleiben als `needs_sync` markiert und werden vom Cron erneut versucht.

<br>

* **Wechsel zwischen den APIs**
- Nutze `['console', 'dns']` während der Migration. Wenn Hetzner Console `not_found` sagt, fällt das Skript auf DNS Console zurück.
- Wenn eine Zone komplett auf Hetzner Console läuft, `dns_token` entfernen oder auf `['console']` umstellen, um Legacy-Calls zu sparen und dem DNS-Console-Abschaltplan voraus zu sein.
- Nach Config-Änderungen `php hetzner_dyndns.php --cron` ausführen, um offene Jobs abzuarbeiten und beide APIs zu testen.
* **Migration von älteren Skript-Versionen**
- Die Legacy-API (`dns.hetzner.com/api/v1`) wurde von Hetzner im Mai 2026 abgeschaltet; der zugehörige Code-Pfad ist entfernt. Die Config-Schlüssel `dns_token`, `dns_endpoint` und `api_order` werden ignoriert (bei aktivem `debug` mit Hinweis im Log).
- Pro Realm ist jetzt ein `console_token` Pflicht; die SQLite-DB bleibt kompatibel und muss nicht angefasst werden.
- Nach dem Update einmal `php hetzner_dyndns.php --cron` ausführen, um offene Jobs abzuarbeiten — der Cron-Aufruf über die Kommandozeile benötigt (und akzeptiert) keine HTTP-Zugangsdaten.

<br>

Expand All @@ -215,20 +217,21 @@ Bearbeite `hetzner_dyndns.config.php`:

* **Benachrichtigungen und Observability**
- E-Mails zu Erfolgen und Fehlern über PHP `mail()` oder SMTP.
- `debug` und `debug_log` zeichnen Requests und Responses beider APIs plus Benachrichtigungsstatus auf.
- Cron-Summen listen Gesamtzahl, Erfolge, Fehler und welche API jeden Host bearbeitet hat.
- `debug` und `debug_log` zeichnen alle API-Requests und -Responses plus Benachrichtigungsstatus auf.
- Cron-Summen listen Gesamtzahl, Erfolge und Fehler pro Host.

<br>

* **Sicherheit und Deployment**
- `.htaccess` blockiert direkte Zugriffe auf PHP und SQLite und lässt nur die DynDNS-Endpunkte durch.
- Tokens nur auf DNS scopen und regelmäßig rotieren.
- Passwortvergleiche laufen timing-sicher über `hash_equals()`.
- SQLite-DB und Debug-Log müssen für den PHP-User schreibbar sein; nach Möglichkeit außerhalb öffentlicher Webroots ablegen.
- DynDNS-Endpunkt per HTTPS bereitstellen. Reines HTTP würde dein Passwort verraten.
- DynDNS-Endpunkt per HTTPS bereitstellen. Reines HTTP würde dein Passwort verraten. `?p=<passwort>` landet zudem in Server-Logs — wenn dein Client Basic Auth kann, nutze Basic Auth.

## Ergebnis

Die Bridge liefert dir einen freundlichen DynDNS-Endpunkt, der Hetzners API-Umstellung überlebt. Starte mit dem Schnellstart, damit Updates laufen, und wirf dann einen Blick in die Nerd-Ecke, wenn du Realms, Benachrichtigungen oder das Verhältnis zwischen Hetzner Console und DNS Console feintunen willst.
Die Bridge liefert dir einen freundlichen DynDNS-Endpunkt auf Basis der Hetzner Console API — inklusive IPv6-only-Support, Retry-Logik und sauberem DynDNS2-Protokoll. Starte mit dem Schnellstart, damit Updates laufen, und wirf dann einen Blick in die Nerd-Ecke, wenn du Realms, Benachrichtigungen oder die Migration von einer älteren Skript-Version feintunen willst.

##### License: MIT

Expand Down
Loading