🛠️Praxis
Werkzeuge und Konfigurationen für den Alltag. Alle Beispiele nutzen www.example.org; die Ausgaben sind gekürzte, synthetische Beispiele im Format von OpenSSL 3.x und curl 8.x.
⌨️Befehle
Handshake ansehen: openssl s_client
Baut eine TLS-Verbindung auf und zeigt Kette, Prüfergebnis, Version, Cipher Suite und Schlüsselaustauschgruppe. -servername setzt SNI – ohne liefert der Server evtl. ein anderes Zertifikat.
$ openssl s_client -connect www.example.org:443 -servername www.example.org -showcerts </dev/null
Beispielausgabe
depth=2 C=DE, O=Beispiel-Vertrauensdienste, CN=Beispiel Root CA R1 verify return:1 depth=1 C=DE, O=Beispiel-Vertrauensdienste, CN=Beispiel TLS CA 2026 verify return:1 depth=0 CN=www.example.org verify return:1 --- Certificate chain 0 s:CN=www.example.org i:C=DE, O=Beispiel-Vertrauensdienste, CN=Beispiel TLS CA 2026 a:PKEY: EC, (prime256v1); sigalg: ecdsa-with-SHA256 1 s:C=DE, O=Beispiel-Vertrauensdienste, CN=Beispiel TLS CA 2026 i:C=DE, O=Beispiel-Vertrauensdienste, CN=Beispiel Root CA R1 --- Peer signature type: ecdsa_secp256r1_sha256 Negotiated TLS1.3 group: X25519MLKEM768 --- New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384 Verify return code: 0 (ok)
Bestimmte Version erzwingen
Prüft, ob der Server TLS 1.2 bzw. eine bestimmte Suite noch anbietet. Mit -tls1_1 sollte ein moderner Server ablehnen.
$ openssl s_client -connect www.example.org:443 -tls1_2 -cipher 'ECDHE-RSA-AES128-GCM-SHA256' </dev/null
Beispielausgabe
New, TLSv1.2, Cipher is ECDHE-RSA-AES128-GCM-SHA256 Server Temp Key: X25519, 253 bits Secure Renegotiation IS supported
Zertifikat lesen: openssl x509 -text
Dekodiert das erste Zertifikat einer PEM-Datei. Nützlich: -noout -dates, -noout -ext subjectAltName, -noout -fingerprint -sha256.
$ openssl x509 -in fullchain.pem -noout -text
Beispielausgabe
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 4d:1e:…
Signature Algorithm: ecdsa-with-SHA256
Issuer: C=DE, O=Beispiel-Vertrauensdienste, CN=Beispiel TLS CA 2026
Validity
Not Before: Sep 14 00:00:00 2026 GMT
Not After : Oct 29 00:00:00 2026 GMT
Subject: CN=www.example.org
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
ASN1 OID: prime256v1
X509v3 extensions:
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Key Usage: critical
Digital Signature
X509v3 Extended Key Usage:
TLS Web Server Authentication
X509v3 Subject Alternative Name:
DNS:www.example.org, DNS:example.orgKette offline prüfen
Prüft Signaturen und Gültigkeit gegen eine explizite Root. -untrusted = mitgeschickte Zwischenzertifikate.
$ openssl verify -CAfile root.pem -untrusted intermediate.pem leaf.pem
Beispielausgabe
leaf.pem: OK
curl -v
Zeigt den Handshake auf einen Blick: ALPN-Angebot, Nachrichten, Version/Suite, Zertifikatsdaten und SAN-Treffer. --resolve testet einen neuen Server, bevor DNS umgestellt ist.
$ curl -v https://www.example.org/ -o /dev/null
Beispielausgabe
* ALPN: curl offers h2,http/1.1 * TLSv1.3 (OUT), TLS handshake, Client hello (1): * TLSv1.3 (IN), TLS handshake, Server hello (2): * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): * TLSv1.3 (IN), TLS handshake, Certificate (11): * TLSv1.3 (IN), TLS handshake, CERT verify (15): * TLSv1.3 (IN), TLS handshake, Finished (20): * TLSv1.3 (OUT), TLS handshake, Finished (20): * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 * ALPN: server accepted h2 * Server certificate: * subject: CN=www.example.org * expire date: Oct 29 00:00:00 2026 GMT * subjectAltName: host "www.example.org" matched cert's "www.example.org" * SSL certificate verify ok.
Schlüssel + CSR erzeugen
Der private Schlüssel verlässt den Server nie – die CA bekommt nur den CSR (öffentlicher Schlüssel + Namen, signiert).
$ openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \ -keyout privkey.pem -out request.csr -subj '/CN=www.example.org' \ -addext 'subjectAltName=DNS:www.example.org,DNS:example.org'
Beispielausgabe
(erzeugt privkey.pem und request.csr)
Ablauf überwachen
-checkend in Sekunden (hier 14 Tage) – Exit-Code 1, wenn das Zertifikat bis dahin abläuft. Ideal für Monitoring.
$ echo | openssl s_client -connect www.example.org:443 -servername www.example.org 2>/dev/null \ | openssl x509 -noout -enddate -checkend 1209600
Beispielausgabe
notAfter=Oct 29 00:00:00 2026 GMT Certificate will not expire
Let's Encrypt mit certbot
HTTP-01 über ein Webroot-Verzeichnis. Die Erneuerung läuft per systemd-Timer/cron; nach der Erneuerung nginx neu laden (--deploy-hook 'systemctl reload nginx').
$ certbot certonly --webroot -w /var/www/acme -d www.example.org -d example.org certbot renew --dry-run
Beispielausgabe
Successfully received certificate. Certificate is saved at: /etc/letsencrypt/live/www.example.org/fullchain.pem Key is saved at: /etc/letsencrypt/live/www.example.org/privkey.pem
⚙️nginx-TLS-Konfigurator
Vor dem Neuladen immer nginx -t. Test danach: curl -vI https://www.example.org.
# HTTP → HTTPS umleiten (Port 80 bleibt offen für ACME HTTP-01)
server {
listen 80;
listen [::]:80;
server_name www.example.org;
location /.well-known/acme-challenge/ { root /var/www/acme; }
location / { return 301 https://$host$request_uri; }
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on; # nginx ≥ 1.25.1
server_name www.example.org;
ssl_certificate /etc/letsencrypt/live/www.example.org/fullchain.pem; # Leaf + Intermediate
ssl_certificate_key /etc/letsencrypt/live/www.example.org/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# nur für TLS 1.2 relevant – TLS 1.3 hat eigene, immer sichere Suites
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
ssl_ecdh_curve X25519MLKEM768:X25519:prime256v1; # hybrid Post-Quanten, braucht OpenSSL ≥ 3.5
ssl_session_timeout 1d;
ssl_session_cache shared:TLS:10m;
ssl_session_tickets off; # Tickets nur mit regelmäßig rotierten Schlüsseln
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
}
}max-age=63072000 heißt zwei Jahre. Wer HSTS mit includeSubDomains setzt, zwingt alle Subdomains auf HTTPS – auch interne. Zum Testen mit kleinem max-age (z. B. 300) beginnen; preload ist nur schwer rückgängig zu machen.ssl_ciphers betrifft nur TLS ≤ 1.2. Die TLS-1.3-Suites (TLS_AES_128_GCM_SHA256 usw.) sind alle sicher und werden in OpenSSL separat (Ciphersuites) konfiguriert – normalerweise muss man sie nicht anfassen.🪪mTLS: auch der Client zeigt ein Zertifikat
- Server → Client:
{CertificateRequest}(verschlüsselt, nach EncryptedExtensions), mit erlaubten Signaturverfahren. - Client → Server:
{Certificate},{CertificateVerify}(Kontext „TLS 1.3, client CertificateVerify“),{Finished}. - Server prüft die Kette gegen die eigene Client-CA (
ssl_client_certificate) und reicht z. B. den Subject-DN an die Anwendung weiter.
Vorteil: Zugriff hängt an einem Schlüssel, nicht an einem Passwort. Nachteil: Zertifikate müssen verteilt, erneuert und bei Verlust gesperrt werden.
# eigene Client-CA (Beispiel, nur zum Lernen) openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \ -keyout client-ca.key -out client-ca.pem -days 3650 -subj "/CN=Beispiel Client CA" # Client-Schlüssel + CSR + Zertifikat openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \ -keyout alice.key -out alice.csr -subj "/CN=alice" openssl x509 -req -in alice.csr -CA client-ca.pem -CAkey client-ca.key \ -days 90 -out alice.pem -extfile <(printf "extendedKeyUsage=clientAuth") # Test curl --cert alice.pem --key alice.key https://api.example.org/
🩺Häufige Fehler
Zertifikat abgelaufen
Ursache: notAfter überschritten – meist ist die automatische Erneuerung still gescheitert (oder die Uhr des Clients falsch).
Prüfen: openssl x509 -noout -dates
Lösung: Erneuerung reparieren (certbot renew --dry-run), Monitoring mit -checkend, Server nach Erneuerung neu laden.
Kette unvollständig
Ursache: Server schickt nur das Leaf (cert.pem statt fullchain.pem). Browser mit Cache funktionieren oft trotzdem, curl/Apps nicht.
Prüfen: openssl s_client -showcerts … (nur „0 s:“ sichtbar?)
Lösung: fullchain.pem konfigurieren (Leaf + Intermediate, die Root gehört nicht dazu).
Name passt nicht
Ursache: Der aufgerufene Hostname steht nicht im subjectAltName (der CN wird von Browsern ignoriert). Häufig: example.org vs. www.example.org, oder falscher vhost ohne SNI.
Prüfen: openssl x509 -noout -ext subjectAltName
Lösung: Alle Namen in den SAN aufnehmen; server_name/SNI prüfen.
Selbstsigniert / private CA
Ursache: Die Root ist nicht im Vertrauensspeicher des Clients.
Prüfen: openssl verify -CAfile …
Lösung: Öffentliche CA verwenden oder die eigene Root gezielt verteilen (intern, mTLS).
Gemischte Inhalte
Ursache: Eine HTTPS-Seite lädt Skripte/Bilder per http://. Aktive Inhalte blockiert der Browser.
Prüfen: Browser-Konsole
Lösung: Alle Ressourcen über https:// (oder relativ) laden; CSP upgrade-insecure-requests.
Veraltete Version / keine gemeinsame Suite
Ursache: Server bietet nur TLS 1.0/1.1 oder Suites, die der Client ablehnt.
Prüfen: openssl s_client -tls1_2 / -tls1_3
Lösung: ssl_protocols TLSv1.2 TLSv1.3 und moderne Suites.
HSTS verhindert Ausnahme
Ursache: Nach einem gesetzten HSTS-Header erlaubt der Browser bei Zertifikatsfehlern keinen Klick auf „Trotzdem fortfahren“.
Prüfen: Response-Header Strict-Transport-Security
Lösung: Zertifikat reparieren – HSTS ist hier gewollt. max-age erst nach Tests hochsetzen.