🛠️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.org

Kette 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

Angelehnt an das „intermediate“-Profil des Mozilla SSL Configuration Generator. Häkchen setzen – die Konfiguration passt sich an.

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;
    }
}
⚠️ HSTS mit Bedacht
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.
💡 Cipher Suites in TLS 1.3
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

Bei Mutual TLS verlangt der Server im Handshake ein Client-Zertifikat (CertificateRequest). Typisch für Maschine-zu-Maschine-APIs, Admin-Zugänge und Zero-Trust-Netze.
  1. Server → Client: {CertificateRequest} (verschlüsselt, nach EncryptedExtensions), mit erlaubten Signaturverfahren.
  2. Client → Server: {Certificate}, {CertificateVerify} (Kontext „TLS 1.3, client CertificateVerify“), {Finished}.
  3. 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

Im Zertifikats-Labor auf der Seite „Zertifikate“ lassen sich die ersten vier live nachstellen.

Zertifikat abgelaufen

NET::ERR_CERT_DATE_INVALID

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

NET::ERR_CERT_AUTHORITY_INVALID · „unable to get local issuer certificate“

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

NET::ERR_CERT_COMMON_NAME_INVALID

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

NET::ERR_CERT_AUTHORITY_INVALID

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

Mixed Content: … was loaded over HTTPS, but requested an insecure resource

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

ERR_SSL_VERSION_OR_CIPHER_MISMATCH

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

„Sie können diese Website jetzt nicht aufrufen, weil sie HSTS verwendet“

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.