Legt den A/AAAA-Record fuer einen Namen in Plesk an bzw. aktualisiert ihn und holt anschliessend per DNS-01-Challenge ein Wildcard-Zertifikat von Let's Encrypt. Alle Bestandteile werden einzeln abgelegt, zusaetzlich als kombinierte bundle.pem und als passwortgeschuetzte cert.pfx. - Plesk-Anbindung ueber die XML-API (Zone finden, Records lesen/anlegen/loeschen) - ACME-Order mit dns-01, Account-Key wird wiederverwendet - Propagations-Check gegen die autoritativen Nameserver der Zone, bricht vor der Validierung ab statt einen Fehlversuch bei Let's Encrypt zu verbrennen - Renewal-Check: laeuft idempotent, taugt so direkt fuer den Cron - Docker-Container, Konfiguration ueber .env, Ausgabe im Projektverzeichnis - Tests ohne echten Plesk-Server bzw. ohne Zertifikatsausstellung Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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 Domain muss auf dem Plesk-Server als DNS-Zone liegen und Plesk muss der autoritative Nameserver dafür sein (sonst kann die DNS-Challenge nicht validiert werden).
- 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) |
--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
- Plesk-Zone zum Namen suchen (längste passende Zone, z. B.
example.comfürvpn.example.com). - A/AAAA-Record anlegen oder auf die neue IP korrigieren.
- Prüfen, ob überhaupt erneuert werden muss (
RENEW_DAYS_BEFORE_EXPIRY, Standard 30 Tage). - Key + CSR für
<fqdn>und*.<fqdn>erzeugen. - ACME-Order bei Let's Encrypt, für jede Autorisierung einen
_acme-challenge-TXT-Record in Plesk setzen (alte Reste werden vorher entfernt). - Warten, bis alle autoritativen Nameserver die TXT-Records ausliefern
(
DNS_PROPAGATION_TIMEOUT). - Challenges beantworten, Zertifikat abholen, TXT-Records wieder löschen.
- 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 … |
Domain liegt nicht auf diesem Plesk oder der API-User sieht sie nicht |
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)