Files
michaelschiemer/docs/deployment/ssl-setup.md
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

14 KiB

SSL/TLS Certificate Setup with Let's Encrypt

Automatische SSL/TLS-Zertifikat-Verwaltung mit Let's Encrypt und Certbot für Production Deployment.

Übersicht

Das Framework nutzt Let's Encrypt für kostenlose, automatisch erneuernde SSL/TLS-Zertifikate via Certbot im Docker-Container.

Features:

  • Automatische Zertifikat-Ausstellung
  • Automatische Erneuerung alle 12 Stunden
  • Wildcard-Zertifikate möglich (DNS-01 Challenge)
  • Zero-Downtime Erneuerung
  • Nginx-Integration via Shared Volumes

Architektur

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Certbot       │───▶│  Let's Encrypt  │◀───│  ACME Server    │
│   Container     │    │  Certificates   │    │  (HTTP-01)      │
└─────────────────┘    └─────────────────┘    └─────────────────┘
         │                      │
         ▼                      ▼
┌─────────────────────────────────────────────────────────────────┐
│  Shared Volumes: /etc/letsencrypt, /var/www/certbot            │
└─────────────────────────────────────────────────────────────────┘
         │
         ▼
┌─────────────────┐
│   Nginx Web     │
│   Container     │
└─────────────────┘

Voraussetzungen

DNS-Konfiguration

Bevor SSL-Zertifikate ausgestellt werden können:

  1. A Record muss auf Server-IP zeigen:
# Prüfen
dig +short example.com
# Erwartetes Ergebnis: Ihre Server-IP (z.B. 203.0.113.42)
  1. AAAA Record für IPv6 (optional):
dig +short example.com AAAA
  1. CAA Record für Let's Encrypt (empfohlen):
example.com. CAA 0 issue "letsencrypt.org"
example.com. CAA 0 issuewild "letsencrypt.org"

Firewall-Konfiguration

Port 80 und 443 müssen offen sein:

# UFW
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Iptables
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT

Erste Zertifikat-Ausstellung

Schritt 1: Nginx-Konfiguration für ACME Challenge

Erstelle temporäre Nginx-Config für initiale Zertifikat-Ausstellung:

docker/nginx/conf.d/certbot-challenge.conf:

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    # ACME Challenge für Let's Encrypt
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
        try_files $uri =404;
    }

    # Redirect alle anderen Requests zu HTTPS (nach Zertifikat-Ausstellung)
    location / {
        return 301 https://$host$request_uri;
    }
}

Schritt 2: Initiales Certbot-Setup

Starte nur Web und Certbot Container für initiale Zertifikat-Ausstellung:

# 1. Erstelle Certbot-Verzeichnisse
mkdir -p ./certbot/{conf,www,logs}

# 2. Starte nur Nginx für ACME Challenge
docker-compose up -d web

# 3. Teste ACME Challenge Endpoint
curl -I http://example.com/.well-known/acme-challenge/test
# Sollte 404 zurückgeben (Endpoint erreichbar)

# 4. Initiale Zertifikat-Ausstellung (dry-run)
docker run --rm \
  -v $(pwd)/certbot/conf:/etc/letsencrypt \
  -v $(pwd)/certbot/www:/var/www/certbot \
  -v $(pwd)/certbot/logs:/var/log/letsencrypt \
  certbot/certbot:latest certonly \
    --webroot \
    --webroot-path=/var/www/certbot \
    --email admin@example.com \
    --agree-tos \
    --no-eff-email \
    --dry-run \
    -d example.com \
    -d www.example.com

# 5. Echte Zertifikat-Ausstellung (ohne --dry-run)
docker run --rm \
  -v $(pwd)/certbot/conf:/etc/letsencrypt \
  -v $(pwd)/certbot/www:/var/www/certbot \
  -v $(pwd)/certbot/logs:/var/log/letsencrypt \
  certbot/certbot:latest certonly \
    --webroot \
    --webroot-path=/var/www/certbot \
    --email admin@example.com \
    --agree-tos \
    --no-eff-email \
    -d example.com \
    -d www.example.com

Output bei Erfolg:

Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/example.com/fullchain.pem
Your key file has been saved at:
   /etc/letsencrypt/live/example.com/privkey.pem
Your cert will expire on 2025-04-15.

Schritt 3: Nginx HTTPS-Konfiguration

Nach erfolgreicher Zertifikat-Ausstellung, aktiviere HTTPS:

docker/nginx/conf.d/default.conf (Production):

# HTTP Server - Redirect zu HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    # ACME Challenge für Zertifikat-Erneuerung
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
        try_files $uri =404;
    }

    # Redirect zu HTTPS
    location / {
        return 301 https://$host$request_uri;
    }
}

# HTTPS Server
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com www.example.com;

    # SSL Zertifikate
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # SSL Konfiguration (Mozilla Modern)
    ssl_protocols TLSv1.3;
    ssl_prefer_server_ciphers off;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;

    # Security Headers
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;

    # Application
    root /var/www/html/public;
    index index.php;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

Schritt 4: Starte Production Stack

# Mit Certbot für automatische Erneuerung
docker-compose -f docker-compose.yml -f docker-compose.production.yml up -d

# Prüfe Logs
docker-compose logs -f certbot

# Teste HTTPS
curl -I https://example.com

Automatische Erneuerung

Der Certbot-Container erneuert Zertifikate automatisch alle 12 Stunden:

docker-compose.production.yml:

certbot:
  image: certbot/certbot:latest
  entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --webroot -w /var/www/certbot --quiet; sleep 12h & wait $${!}; done;'"
  volumes:
    - certbot-conf:/etc/letsencrypt
    - certbot-www:/var/www/certbot
    - certbot-logs:/var/log/letsencrypt

Manuelle Erneuerung:

# Test Renewal (dry-run)
docker-compose exec certbot certbot renew --dry-run

# Force Renewal (vor Ablauf)
docker-compose exec certbot certbot renew --force-renewal

# Nginx Reload nach Erneuerung
docker-compose exec web nginx -s reload

Wildcard-Zertifikate (DNS-01 Challenge)

Für Wildcard-Zertifikate (*.example.com) ist DNS-01 Challenge erforderlich:

Voraussetzungen

  • DNS-Provider API-Zugang (Cloudflare, Route53, etc.)
  • Certbot DNS Plugin installiert

Beispiel: Cloudflare

docker-compose.production.yml (erweitert):

certbot:
  image: certbot/dns-cloudflare:latest
  environment:
    - CLOUDFLARE_EMAIL=admin@example.com
    - CLOUDFLARE_API_KEY=${CLOUDFLARE_API_KEY}
  volumes:
    - certbot-conf:/etc/letsencrypt
    - ./certbot/cloudflare.ini:/cloudflare.ini:ro
  command: certonly --dns-cloudflare --dns-cloudflare-credentials /cloudflare.ini -d example.com -d *.example.com

certbot/cloudflare.ini:

dns_cloudflare_email = admin@example.com
dns_cloudflare_api_key = your_cloudflare_api_key

Ausstellung:

docker run --rm \
  -v $(pwd)/certbot/conf:/etc/letsencrypt \
  -v $(pwd)/certbot/cloudflare.ini:/cloudflare.ini:ro \
  certbot/dns-cloudflare:latest certonly \
    --dns-cloudflare \
    --dns-cloudflare-credentials /cloudflare.ini \
    --email admin@example.com \
    --agree-tos \
    --no-eff-email \
    -d example.com \
    -d *.example.com

Zertifikat-Monitoring

Ablaufdatum prüfen

# Via OpenSSL
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -dates

# Via Certbot
docker-compose exec certbot certbot certificates

# Output:
# Certificate Name: example.com
#   Expiry Date: 2025-04-15 12:34:56+00:00 (VALID: 89 days)

Automatisches Monitoring

Nagios/Icinga Check:

#!/bin/bash
DAYS_LEFT=$(echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | \
  openssl x509 -noout -checkend $((86400 * 30)))

if [ $? -eq 0 ]; then
  echo "OK - Certificate valid for more than 30 days"
  exit 0
else
  echo "CRITICAL - Certificate expires in less than 30 days"
  exit 2
fi

Troubleshooting

Problem: ACME Challenge fehlgeschlagen

Symptom:

Challenge failed for domain example.com

Lösung:

# 1. Prüfe DNS
dig +short example.com
# Muss auf Server-IP zeigen

# 2. Prüfe Port 80 erreichbar
curl -I http://example.com/.well-known/acme-challenge/test

# 3. Prüfe Nginx Logs
docker-compose logs web

# 4. Prüfe Certbot Logs
docker-compose logs certbot
cat certbot/logs/letsencrypt.log

Problem: Rate Limit erreicht

Let's Encrypt hat Rate Limits:

  • 50 Zertifikate pro Domain pro Woche
  • 5 fehlgeschlagene Validierungen pro Stunde

Lösung:

# Nutze Staging-Umgebung für Tests
docker run --rm \
  -v $(pwd)/certbot/conf:/etc/letsencrypt \
  -v $(pwd)/certbot/www:/var/www/certbot \
  certbot/certbot:latest certonly \
    --staging \
    --webroot -w /var/www/certbot \
    -d example.com

# Warte 1 Stunde bei fehlgeschlagenen Validierungen

Problem: Zertifikat-Erneuerung schlägt fehl

Symptom:

Failed to renew certificate example.com

Lösung:

# 1. Manuelle Erneuerung mit Debug
docker-compose exec certbot certbot renew --force-renewal --debug

# 2. Prüfe Webroot-Pfad
docker-compose exec web ls -la /var/www/certbot/.well-known/acme-challenge/

# 3. Prüfe Nginx Config
docker-compose exec web nginx -t

# 4. Reload Nginx nach Config-Änderung
docker-compose exec web nginx -s reload

Problem: Mixed Content Warnings

Nach HTTPS-Umstellung erscheinen Mixed Content Warnings.

Lösung:

# Content Security Policy Header
add_header Content-Security-Policy "upgrade-insecure-requests" always;

# In Application
# Verwende relative URLs oder HTTPS:
<script src="/js/app.js"></script>  # Relativ - empfohlen
<script src="https://example.com/js/app.js"></script>  # Absolut HTTPS

Security Best Practices

1. SSL-Konfiguration Härten

Mozilla SSL Configuration Generator: https://ssl-config.mozilla.org/

# Modern Configuration (nur TLS 1.3)
ssl_protocols TLSv1.3;

# Intermediate Configuration (TLS 1.2 + 1.3)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:...';
ssl_prefer_server_ciphers off;

2. HSTS Preload

Nach erfolgreicher HTTPS-Umstellung:

  1. HSTS Header mit Preload:
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
  1. Submit to Preload List: https://hstspreload.org/

3. OCSP Stapling

Verbessert SSL-Handshake-Performance:

ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

4. Certificate Transparency Monitoring

Monitor für Certificate Transparency Logs:

Testing & Validation

SSL Labs Test

Teste SSL-Konfiguration:

# Online
https://www.ssllabs.com/ssltest/analyze.html?d=example.com

# CLI via testssl.sh
docker run --rm -ti drwetter/testssl.sh:latest example.com

Ziel: A+ Rating

Security Headers Check

curl -I https://example.com | grep -i "strict-transport-security\|x-frame-options\|x-content-type-options"

Certificate Chain Validation

openssl s_client -connect example.com:443 -showcerts

Backup & Recovery

Backup Zertifikate

# Backup /etc/letsencrypt
docker run --rm \
  -v certbot-conf:/etc/letsencrypt \
  -v $(pwd)/backups:/backups \
  alpine tar czf /backups/letsencrypt-$(date +%Y%m%d).tar.gz -C / etc/letsencrypt

# Verschlüsselt
gpg --symmetric --cipher-algo AES256 backups/letsencrypt-$(date +%Y%m%d).tar.gz

Restore Zertifikate

# Entschlüsseln
gpg --decrypt backups/letsencrypt-20250115.tar.gz.gpg > letsencrypt-restore.tar.gz

# Restore
docker run --rm \
  -v certbot-conf:/etc/letsencrypt \
  -v $(pwd):/backups \
  alpine tar xzf /backups/letsencrypt-restore.tar.gz -C /

# Nginx Reload
docker-compose exec web nginx -s reload

Automation Scripts

Siehe:

  • scripts/ssl-setup.sh - Initiale SSL-Einrichtung
  • scripts/ssl-renew.sh - Manuelle Erneuerung
  • scripts/ssl-check.sh - Status-Check

Next Steps

Nach SSL-Setup:

  1. Teste HTTPS: https://example.com
  2. SSL Labs Test: A+ Rating verifizieren
  3. Monitor Ablaufdatum: Automatisches Monitoring einrichten
  4. HSTS Preload: Nach Stabilisierung eintragen
  5. Firewall: Port 80 nur für ACME Challenge offen lassen