duffyduckandClaude Opus 5 611593b5c0 Zone gezielt bei Plesk erfragen statt alle Domains aufzulisten
Ein Name wie endian.fon-aria.de ist oft nur ein Record in der Zone
fon-aria.de und existiert weder als Domain noch als Abo. Ausserdem darf
nicht jeder API-User alle Domains des Servers auflisten - dann brach das
Tool mit "No domains found on the Plesk server" ab, obwohl die Zone da war.

Die Zone wird jetzt Label fuer Label von unten nach oben gezielt abgefragt
(endian.fon-aria.de -> fon-aria.de), jeweils per site.get und webspace.get
mit Namensfilter. Das Auflisten aller Domains dient nur noch der
Fehlermeldung, die jetzt auch zeigt, welche Namen probiert wurden und was
Plesk dazu gesagt hat.

- neue Option --zone, um die Zone bei Bedarf fest vorzugeben
- get_rec meldet jetzt als Warnung, wenn Plesk keine Records liefert
- run.sh baut das Image immer (Cache), damit kein alter Stand haengen bleibt
- Tests bilden den Fall nach: Zone nur als Abo auffindbar, Auflisten verboten

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

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
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_acme_api.py  # prueft die Let's-Encrypt-Anbindung (nur Directory-Abruf)
S
Description
No description provided
Readme
66 KiB
Languages
Python 98.3%
Shell 1%
Dockerfile 0.7%