# 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\HttpHetznerDnsClient` — **das 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 ` — 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`: ```json { "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.