CluPilotCloud/docs/handoffs/2026-07-31-hetzner-dns-api-...

3.8 KiB

Hetzner-DNS: die alte API ist abgeschaltet

Gemessen am 31. Juli 2026, am Live-Server und von außen.

https://dns.hetzner.com/api/v1/zones  →  301  →  https://console.hetzner.com/   (HTML)
https://api.hetzner.cloud/v1/zones    →  401 ohne Token, 200 mit dem Token des Betreibers

Hetzner hat die DNS-Verwaltung am 7. Oktober 2025 in die Cloud-Konsole verlegt. Die alte DNS-Konsole ging am 20. Mai 2026 in den Nur-Lese-Betrieb; seither leitet der alte Endpunkt auf die Weboberfläche um. Alte Token und Zonen-IDs sind mit der neuen Konsole nicht kompatibel und wurden nicht übernommen.

Was dadurch kaputt ist

  • App\Services\Dns\DnsTokenCheck — meldet zone_not_found, obwohl Token und Zone in Ordnung sind. Seit v1.3.77 meldet es immerhin ehrlich zone_list_unreadable mit dem Anfang der HTML-Antwort.
  • App\Services\Dns\HttpHetznerDnsClientdas ist das Teure. Er legt den A-Eintrag jeder Kundeninstanz an. Solange er auf den alten Endpunkt zeigt, stirbt jede Bereitstellung in ConfigureDnsAndTls — nachdem der Kunde bezahlt hat. Genau der Satz steht als Begründung auf der Bereitschaftsseite.
  • Der Token des Betreibers ist bereits ein Cloud-Token und antwortet mit 200. Es fehlt nur der Code.

Die neue Schnittstelle

Aus https://docs.hetzner.cloud/cloud.spec.json gezogen, nicht geraten.

Anmeldung: Authorization: Bearer <token> — nicht mehr Auth-API-Token.

Basis: https://api.hetzner.cloud/v1

Zweck Aufruf
Zonen auflisten GET /zones{"zones":[{id,name,…}], "meta":{…}}
Eine Zone direkt GET /zones/{id_or_name}der Name geht direkt, kein Suchen mehr
Eintragssatz anlegen POST /zones/{zone}/rrsets
Eintragssatz ersetzen POST /zones/{zone}/rrsets/{name}/{type}/actions/set_records
Eintragssatz löschen DELETE /zones/{zone}/rrsets/{name}/{type}

Das Modell hat sich geändert: keine einzelnen Records mehr, sondern RRSets, adressiert über {name}/{typ}. Ein RRSet trägt mehrere Werte. set_records ersetzt den ganzen Satz — damit wird das Schreiben idempotent, was der alte Client mit Suchen-und-Aktualisieren nachbauen musste.

Rumpf beim Anlegen, Pflichtfelder name, type, records:

{ "name": "kunde", "type": "A", "ttl": 60,
  "records": [{"value": "203.0.113.10"}] }

Zwei Fallen im name:

  1. Er darf nicht auf den Zonennamen enden. kunde, nicht kunde.clupilot.cloud — der alte Client hat den vollen Namen geschickt.
  2. Für die Zonenwurzel ist es @, und er muss klein geschrieben sein.

PUT /zones/{zone}/rrsets/{name}/{type} ist nicht das Schreiben der Werte — es setzt nur labels. Wer dort die Records erwartet, schreibt ins Leere.

Was zu tun ist

  1. HttpHetznerDnsClient auf Basis, Kopfzeile und RRSets umstellen. Der Zonen-Lookup entfällt: der Name geht direkt in den Pfad.
  2. DnsTokenCheck dito. Die Schreibprobe bleibt der Kern der Prüfung — ein lesender Token sieht sonst genauso aus wie ein schreibender, und der Unterschied fällt erst auf, wenn ein Kunde bezahlt hat.
  3. FakeHetznerDnsClient und die Tests nachziehen.
  4. Den Namen ohne Zonensuffix bilden — Falle 1 oben. Ein Test, der genau das festhält, ist billiger als der erste Kunde, dessen Adresse kunde.clupilot.cloud.clupilot.cloud heißt.

Wieso das so lange gesucht wurde

Die Prüfung stufte nur 401 und 403 als abgelehnt ein und las danach den Rumpf. Jede andere Antwort — hier eine 301 auf eine HTML-Seite — hatte kein zones, und ?? [] machte daraus „keine Zone in diesem Konto". Eine Aussage über ein fremdes Hetzner-Konto, hergeleitet aus einer Antwort, die niemand angesehen hat. v1.3.76 und v1.3.77 haben das behoben; die Meldung zeigt jetzt Status und die ersten Zeichen der Antwort. Ohne diese zwei Schritte wäre die Ursache weiter im Dunkeln.