Files
duffyduckandClaude Opus 5 efdaefc39e Nameserver ohne Route ueberspringen statt abzustuerzen
Die Nameserver von fon-aria.de haben AAAA-Records, der Container hat aber
kein IPv6. dns.query.udp() ist deshalb mit OSError [Errno 99] "Cannot
assign requested address" durchgeschlagen und hat den ganzen Lauf
abgebrochen - nach dem Anlegen der Challenge-Records.

Adressen, zu denen es keine Route gibt, werden jetzt vorher aussortiert
(UDP-connect, verschickt nichts) und waehrend des Wartens zusaetzlich
verworfen, falls doch eine ausfaellt. Bleibt keine uebrig, gibt es eine
klare Meldung statt eines Tracebacks. Bei fon-aria.de bleiben so die
beiden IPv4-Adressen derselben Nameserver uebrig.

- OSError wird in main() sauber abgefangen
- tests/test_dns.py deckt die Faelle ab; im Container gegen die echte
  Zone gegengeprueft

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 10:21:30 +02:00

7.6 KiB
Raw Permalink Blame History

Wildcard Let's Encrypt Cert Creator für Plesk

Legt in Plesk den DNS-Eintrag für einen Namen an (bzw. aktualisiert ihn) und holt anschließend per DNS-01-Challenge ein Wildcard-Zertifikat von Let's Encrypt. Alle Bestandteile des Zertifikats landen als einzelne Dateien im Projektverzeichnis zusätzlich als kombinierte bundle.pem und als passwortgeschützte cert.pfx.

./run.sh vpn.example.com 192.0.2.10

erzeugt ein Zertifikat für vpn.example.com und *.vpn.example.com.


1. Voraussetzungen

  • Docker (oder lokal Python 3.10+)
  • Die Zone muss auf dem Plesk-Server liegen und Plesk muss der autoritative Nameserver dafür sein (sonst kann die DNS-Challenge nicht validiert werden). Der Name selbst braucht kein eigenes Abo und keine eigene Domain zu sein: für endian.fon-aria.de reicht die Zone fon-aria.de das Tool sucht sich die passende Zone von unten nach oben zusammen und legt dort einfach die Records an.
  • Ein Plesk-API-Zugang (API-Key empfohlen).

Plesk API-Key erzeugen

Auf dem Plesk-Server (SSH, als root):

plesk bin secret_key --create -ip-address <IP-des-Rechners-der-das-Skript-ausfuehrt> \
    -description "wildcard cert creator"

Der ausgegebene Key kommt in die .env als PLESK_API_KEY. Alternativ geht auch PLESK_USER=admin + PLESK_PASSWORD=....

Der Port 8443 des Plesk-Panels muss von dem Rechner aus erreichbar sein.

2. Einrichten

cp .env.example .env
$EDITOR .env          # Plesk-Daten, ACME_EMAIL, CERT_PASSWORD eintragen
chmod +x run.sh

Wichtig in der .env:

Variable Bedeutung
PLESK_HOST / PLESK_PORT Plesk-Panel (Standard-Port 8443)
PLESK_API_KEY API-Key (oder PLESK_USER/PLESK_PASSWORD)
PLESK_VERIFY_TLS bei selbstsigniertem Panel-Zertifikat false
ACME_EMAIL Kontaktadresse für Let's Encrypt
ACME_STAGING true zum Testen (kein Rate-Limit-Risiko)
CERT_PASSWORD Passwort der .pfx und des verschlüsselten Keys
KEY_TYPE rsa (Standard, 4096 Bit) oder ec
PFX_LEGACY_COMPAT true für alte Windows-/Java-Importer (SHA1/3DES)

3. Benutzen

# DNS-Eintrag anlegen/aktualisieren + Wildcard-Zertifikat holen
./run.sh vpn.example.com 192.0.2.10

# erstmal testen (Staging-CA von Let's Encrypt)
./run.sh vpn.example.com 192.0.2.10 --staging

# nur den DNS-Eintrag setzen, kein Zertifikat
./run.sh vpn.example.com 192.0.2.10 --dns-only

# Zertifikat erneuern, obwohl das alte noch gültig ist
./run.sh vpn.example.com 192.0.2.10 --force

# ohne Wildcard, dafür mit weiteren Namen
./run.sh vpn.example.com 192.0.2.10 --no-wildcard --san www.example.com

Ohne Docker:

python3 -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
python -m app.main vpn.example.com 192.0.2.10

Alle Optionen

Option Wirkung
--ip <adresse> Alternative zum zweiten Positionsargument (IPv4 → A, IPv6 → AAAA)
--san <name> zusätzlicher Name im Zertifikat (mehrfach möglich)
--zone <zone> Plesk-Zone explizit angeben statt sie zu ermitteln
--no-wildcard nur der reine Name, ohne *.<fqdn>
--skip-dns A/AAAA-Record unangetastet lassen
--dns-only nur DNS, kein Zertifikat
--staging Let's-Encrypt-Staging-Umgebung
--force erneuern, auch wenn noch gültig
--keep-txt _acme-challenge-Records stehen lassen (Debugging)
--ignore-propagation-timeout trotz fehlender DNS-Propagation weitermachen
--env <datei> andere .env verwenden
-v ausführliche Ausgabe (inkl. XML-Requests)

4. Was wird erzeugt

Alles unter certs/<fqdn>/:

Datei Inhalt
privkey.pem Private Key, PKCS#8, unverschlüsselt
privkey-traditional.pem derselbe Key im klassischen OpenSSL-Format
privkey-encrypted.pem Key, verschlüsselt mit CERT_PASSWORD
pubkey.pem öffentlicher Schlüssel
csr.pem der verwendete Certificate Signing Request
cert.pem / cert.crt das reine Zertifikat (Leaf)
cert.der Leaf im DER-Format (für Windows)
chain.pem nur die Zwischenzertifikate
chain-01.pem, … jedes Zwischenzertifikat einzeln
fullchain.pem Leaf + Kette (das, was Webserver meist wollen)
bundle.pem Key + Leaf + Kette in einer Datei
cert.pfx PKCS#12 mit Key + Kette, Passwort = CERT_PASSWORD
cert-info.txt / cert-info.json Subject, SANs, Gültigkeit, Fingerprints

Der ACME-Account-Key liegt in data/ nicht löschen, sonst wird bei jedem Lauf ein neuer Let's-Encrypt-Account registriert.

Schnellprüfung:

openssl x509 -in certs/vpn.example.com/cert.pem -noout -text
openssl pkcs12 -info -in certs/vpn.example.com/cert.pfx -nodes -passin pass:<CERT_PASSWORD>

5. Wie es abläuft

  1. Plesk nach der Zone fragen erst nach dem vollen Namen, dann Label für Label nach oben (endian.fon-aria.defon-aria.de), jeweils als Domain und als Abo. Der Server muss dafür nicht alle Domains herausrücken; mit --zone lässt sich die Zone auch fest vorgeben.
  2. A/AAAA-Record anlegen oder auf die neue IP korrigieren.
  3. Prüfen, ob überhaupt erneuert werden muss (RENEW_DAYS_BEFORE_EXPIRY, Standard 30 Tage).
  4. Key + CSR für <fqdn> und *.<fqdn> erzeugen.
  5. ACME-Order bei Let's Encrypt, für jede Autorisierung einen _acme-challenge-TXT-Record in Plesk setzen (alte Reste werden vorher entfernt).
  6. Warten, bis alle autoritativen Nameserver die TXT-Records ausliefern (DNS_PROPAGATION_TIMEOUT).
  7. Challenges beantworten, Zertifikat abholen, TXT-Records wieder löschen.
  8. Alle Dateien schreiben und die Zertifikatsdaten ausgeben.

6. Automatische Erneuerung

Das Skript ist idempotent: Läuft es, obwohl das Zertifikat noch länger als RENEW_DAYS_BEFORE_EXPIRY Tage gültig ist, passiert nichts. Also einfach per Cron:

17 3 * * * cd /home/duffy/Dokumente/programmierung/wildcard-lets-encrypt-cert-plesk-creator && ./run.sh vpn.example.com 192.0.2.10 >> renew.log 2>&1

7. Wenn etwas klemmt

Symptom Ursache / Lösung
Plesk rejected the credentials (HTTP 401) API-Key falsch oder nicht für diese Quell-IP erzeugt
No Plesk DNS zone found for … Zone liegt nicht auf diesem Plesk oder der API-User sieht sie nicht. Die Meldung listet auf, welche Namen probiert wurden und was Plesk dazu gesagt hat. Notfalls die Zone mit --zone example.com fest vorgeben
DNS propagation timed out Plesk ist nicht der autoritative NS, oder die Zone ist deaktiviert. Mit -v prüfen, welcher Nameserver antwortet
Skipping unreachable nameserver address(es): 2001:… Normal: der Container hat kein IPv6, die IPv4-Adressen derselben Nameserver werden benutzt
no dns-01 challenge Wildcards gehen ausschließlich über DNS-01 Domain-Validierung per HTTP ist nicht möglich
urn:ietf:params:acme:error:rateLimited Rate-Limit von Let's Encrypt (5 Zertifikate pro Woche pro Domain). Erst mit --staging testen
PFX lässt sich in Windows nicht importieren PFX_LEGACY_COMPAT=true setzen und neu erzeugen

Bei -v werden auch die XML-Requests an Plesk geloggt hilfreich, wenn ein Record nicht so landet wie erwartet.

8. Tests

Ohne echten Plesk-Server und ohne Zertifikatsausstellung:

.venv/bin/python tests/test_offline.py   # XML-Parsing, DNS-Logik, Dateiausgabe, PFX
.venv/bin/python tests/test_cli.py       # kompletter Ablauf gegen ein Plesk-Fake
.venv/bin/python tests/test_dns.py       # Propagations-Check, unerreichbare Nameserver
.venv/bin/python tests/test_acme_api.py  # prueft die Let's-Encrypt-Anbindung (nur Directory-Abruf)