# Die Host-Oberfläche unter ihrem Namen, mit gültigem Zertifikat, ohne Port

**Stand:** Entwurf, 1. August 2026 — Punkt 1 beantwortet, Weg entschieden
**Auslöser:** gemessen auf `fsn-01`, dem ersten echten Host

---

## Wie es heute ist

Der Betreiber erreicht Proxmox unter:

```
https://fsn-01.node.clupilot.com:8006
```

Mit Zertifikatswarnung, weil Proxmox sich selbst eines ausstellt. Und mit
Port, weil nichts davor sitzt.

Beides ist kein Schönheitsfehler. Eine Warnung, die bei jedem Aufruf kommt,
lehrt den Betreiber, Warnungen wegzuklicken — und irgendwann klickt er die
weg, die echt war.

## Wie es sein soll

```
https://fsn-01.node.clupilot.com
```

Gültiges Zertifikat, kein Port, **weiterhin nur im VPN erreichbar**. Der Host
bleibt hinter der Firewall.

## Warum kein Port 80 aufgemacht wird

Der naheliegende Weg wäre HTTP-01: Let's Encrypt ruft den Host auf Port 80 auf.
Das verlangt eine öffentliche, offene Maschine — genau das, was
`SecureHostFirewall` gerade zugemacht hat.

**DNS-01 verlangt das nicht.** Let's Encrypt ruft überhaupt nichts auf. Es
fragt das öffentliche DNS nach einem TXT-Eintrag:

```
_acme-challenge.fsn-01.node.clupilot.com   TXT   "…"
```

Wer den setzen kann, besitzt den Namen. Der Host braucht keine öffentliche
Adresse, keinen offenen Port, und ein **A-Eintrag ist nicht nötig** — der Name
löst weiterhin nur im Tunnel auf `10.66.0.100` auf.

---

## Der Punkt, der eine Entscheidung brauchte — beantwortet am 1. August 2026

Die Frage war: **Lässt sich bei Hetzner ein DNS-Token beschränken?** Denn für
DNS-01 muss ein Token auf den Host, und ein kontoweiter Token auf jeder
Proxmox-Maschine wäre ein Schlüssel, der weiter reicht als die Maschine.

**Ja — über Projekte, nicht über Zonen.** Und zwar erst seit kurzem.

Die alte DNS-Konsole (`dns.hetzner.com`) kannte genau einen Tokentyp:
kontoweit, alle Zonen, keine Abstufung. Wäre das noch der Stand, hieße die
Antwort nein und dieser Entwurf sähe anders aus. Diese Konsole ist am
**20. Mai 2026 abgeschaltet** worden; `HttpHetznerDnsClient` fährt seit dem
Umbau vom 31. Juli auf `api.hetzner.cloud/v1`.

Im neuen Modell liegen DNS-Zonen **in Cloud-Projekten**, und Token sind
projektweit. Hetzner nennt das ausdrücklich als den vorgesehenen Weg:

> „As API tokens are scoped per project, you can restrict access to specific
> zones by placing them in separate projects."

### Der Haken, der bleibt

**Ein Projekt-Token ist kein DNS-Token.** Es hat genau zwei Achsen: Projekt und
Lesen/Schreiben. Ein Schreibtoken darf in seinem Projekt alles, was die
Cloud-API kann — auch Server anlegen. Eine Beschränkung auf einen Ressourcentyp
gibt es nicht.

Daraus folgt die Regel für das Projekt: **es enthält nichts außer der einen
Zone.** Keine Server, keine Netze, kein Speicher. Dann ist der Schaden eines
gestohlenen Tokens auf diese Zone begrenzt, plus Kosten in einem sonst leeren
Projekt.

### Und die Zone, die er erreicht, enthält nichts Wichtiges

Der Token wird nicht auf `node.clupilot.com` ausgestellt, sondern auf eine
eigene Zone `acme.clupilot.com`, die **nichts enthält als Challenge-Einträge**.
Proxmox kann das von Haus aus — Alias-Modus:

```
pvenode config set -acmedomain0 \
  domain=fsn-01.node.clupilot.com,alias=fsn-01.acme.clupilot.com,plugin=hetznercloud
```

Dazu ein fester CNAME im öffentlichen DNS, einmal je Host:

```
_acme-challenge.fsn-01.node.clupilot.com  CNAME  _acme-challenge.fsn-01.acme.clupilot.com
```

Let's Encrypt folgt dem CNAME bei der Prüfung; der Host schreibt nur in die
Alias-Zone. **Ein kompromittierter Host erreicht `node.clupilot.com` damit gar
nicht mehr** — auch nicht lesend. Das ist der Unterschied zwischen „der
Schlüssel kann nur eine Zone ändern" und „die Zone, die er ändern kann, ist
wertlos".

Der CNAME kostet nichts: `RegisterHostDns` legt ohnehin Einträge für den Host
an, mit dem zentralen Token. Ein Eintrag mehr, an der Stelle, die es schon tut.

### Was damit vom Tisch ist

Weg 3 aus dem ursprünglichen Entwurf — CluPilot setzt den TXT selbst und
übernimmt die Erneuerung — wird **nicht** gebaut. Er wäre eine zweite Stelle,
die still ausfallen kann, und Zertifikate laufen nach 90 Tagen ab.

---

## Was einmal von Hand eingerichtet wird

Nicht pro Host, nicht durch die Pipeline. Einmal, vom Betreiber, bevor der
Schritt überhaupt laufen kann:

1. Ein eigenes Hetzner-Cloud-Projekt, z. B. `clupilot-acme`. **Leer** — nur die
   Zone darin.
2. Zone `acme.clupilot.com` in diesem Projekt anlegen und beim Registrar auf
   Hetzners Nameserver delegieren.
3. API-Token für dieses Projekt, Lesen **und** Schreiben. Roher Wert, ohne
   Anführungszeichen.
4. Der Token wird als **eigener Schlüssel** hinterlegt, nicht als der aus
   `provisioning.dns.token` — der ist kontoweit und bleibt zentral. Zwei
   Schlüssel, zwei Reichweiten; wer sie zusammenlegt, hebt die ganze
   Entscheidung oben wieder auf.

Fehlt einer dieser vier Punkte, muss der Schritt mit einer klaren Meldung
stehenbleiben und nicht auf den kontoweiten Token zurückfallen.

---

## Die zwei Teile

### 1. Zertifikat — eingerichtet in Proxmox, nicht in CluPilot

Proxmox bringt ACME mit und kann DNS-01. Der Schritt richtet über SSH ein:

- ACME-Konto (Kontaktadresse aus den Einstellungen)
- DNS-Modul `hetznercloud` mit dem Token des ACME-Projekts, Variable
  `HETZNER_TOKEN`
- Zertifikatsauftrag für den FQDN des Hosts, **im Alias-Modus** (siehe oben)

**Warum in Proxmox und nicht in CluPilot:** Proxmox erneuert danach selbst,
über seinen täglichen Zeitgeber. Eine Erneuerung, die in CluPilot hinge, wäre
eine zweite Stelle, die still ausfallen kann — und Zertifikate laufen nach 90
Tagen ab. Das merkt man dann an einem Dienstag um sieben.

**Das Modul heißt `hetznercloud`, nicht `hetzner`.** Das alte `hetzner` spricht
mit `dns.hetzner.com/api/v1` und ist damit tot. Ein Host, auf dem noch das alte
Modul konfiguriert ist, muss es losbekommen, bevor das neue greift.

**Vor allem anderen: Version prüfen.** Das Modul `hetznercloud` kam erst mit
`libproxmox-acme-plugins` **1.7.1**. Darunter existiert es nicht — und der
Schritt scheitert dann nicht beim Einrichten, sondern erst beim Ausstellen,
also spät und mit einer Meldung, die nicht danach aussieht. Deshalb steht die
Prüfung **an den Anfang** des Schritts, nicht ans Ende:

```
dpkg-query -W -f='${Version}' libproxmox-acme-plugins
```

- ≥ 1.7.1 → weiter
- darunter → aktualisieren; geht das nicht, **abbrechen mit klarer Meldung**.
  Kein Nachrüsten der Datei von Hand nach `/usr/share/proxmox-acme/dnsapi/` —
  das überlebt kein Paket-Update und hinterlässt einen Host, der sich von den
  anderen unterscheidet, ohne dass es jemand aufgeschrieben hat.

Nach dem Einrichten `pveproxy` und `pvedaemon` neu starten, sonst kennt die
Oberfläche das Modul nicht.

### 2. Port — ein Proxy, gebunden an den Tunnel

443 → 127.0.0.1:8006, **nur auf der Tunneladresse lauschend**, nicht auf der
öffentlichen. `deploy/bootstrap/lib/traefik.sh` liegt bereits im Repo.

Das Zertifikat aus Teil 1 wird von Proxmox verwaltet; der Proxy liest es von
dort (`/etc/pve/local/pveproxy-ssl.pem`) oder reicht TLS unverändert durch.
Zwei Wege, einer davon ist zu wählen — durchreichen ist einfacher und hat
keine zweite Kopie des Schlüssels.

## Wo der Schritt hingehört

Nach `RegisterHostDns` — der Name muss registriert sein, bevor ein Zertifikat
auf ihn ausgestellt wird, und der CNAME in die Alias-Zone entsteht dort. Vor
`SecureHostFirewall` ist nicht nötig (DNS-01 braucht nichts Eingehendes), aber
es schadet auch nicht.

```
… RegisterHostDns → SecureHostConsole → RegisterCapacity → SecureHostFirewall …
```

`RegisterHostDns` bekommt eine Aufgabe dazu: den festen CNAME
`_acme-challenge.<host>.node.clupilot.com` → `_acme-challenge.<host>.acme.clupilot.com`,
gesetzt mit dem **zentralen** Token. Er ist dauerhaft und wird nie wieder
angefasst.

## Wiederholbarkeit

- `libproxmox-acme-plugins` < 1.7.1 → abbrechen, bevor irgendetwas geändert wird
- Zertifikat vorhanden und gültig → nichts tun, `advance()`
- Proxy schon eingerichtet und läuft → nichts tun
- ACME-Modul schon mit demselben Token eingerichtet → nichts tun; bei anderem
  Token überschreiben, nicht ein zweites danebenstellen
- CNAME existiert schon → nichts tun (`RegisterHostDns` ist ohnehin schon so
  gebaut)
- Beides prüfen, nicht annehmen: `pvenode cert info` sagt, was wirklich liegt

Dieselbe Regel wie überall in dieser Pipeline — ein Wiederanlauf darf nichts
zerstören, was funktioniert.

## Abnahme

1. Neuen Host anlegen → am Ende ist `https://<name>.node.clupilot.com` im VPN
   erreichbar, ohne Port, ohne Warnung
2. Von außen (ohne VPN) ist derselbe Name **nicht** erreichbar
3. Im öffentlichen DNS existiert **kein A-Eintrag** für den Namen
4. Wiederholung des Schritts stellt kein zweites Zertifikat aus
5. `pvenode cert info` zeigt ein Let's-Encrypt-Zertifikat mit Laufzeit
6. Der Token auf dem Host kann `node.clupilot.com` **nicht** lesen und nicht
   schreiben — einmal von Hand nachgewiesen, mit genau diesem Token gegen die
   Zone
7. Ein Host mit `libproxmox-acme-plugins` < 1.7.1 bricht den Schritt ab und
   hinterlässt keine halbe ACME-Konfiguration

Punkt 2, 3 und 6 sind die wichtigen: Sie beweisen, dass der Weg das Zertifikat
gebracht hat, **ohne** die Maschine zu öffnen und **ohne** einen Schlüssel zu
verteilen, der weiter reicht als sie.

---

## Belege

- Hetzner, DNS-Migration ins Console (Projekt-Scoping der Token):
  <https://docs.hetzner.com/networking/dns/migration-to-hetzner-console/features-and-differences/>
- Hetzner, API-Token erzeugen (nur Lesen/Schreiben, keine Ressourcentypen):
  <https://docs.hetzner.com/cloud/api/getting-started/generating-api-token/>
- Hetzner Status, Abschaltung der DNS-Konsole im Mai 2026:
  <https://status.hetzner.com/incident/c2146c42-6dd2-4454-916a-19f07e0e5a44>
- Proxmox-Forum, `hetznercloud` in `libproxmox-acme-plugins` 1.7.1, bestätigt
  im April 2026:
  <https://forum.proxmox.com/threads/acme-plugin-hetzner-dns-console-is-moving-to-the-hetzner-console.174363/page-2>
- Proxmox VE Wiki, Alias-Modus für DNS-01:
  <https://pve.proxmox.com/wiki/Certificate_Management>
