# 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:
```bash
# Prüfen
dig +short example.com
# Erwartetes Ergebnis: Ihre Server-IP (z.B. 203.0.113.42)
```
2. **AAAA Record** für IPv6 (optional):
```bash
dig +short example.com AAAA
```
3. **CAA Record** für Let's Encrypt (empfohlen):
```dns
example.com. CAA 0 issue "letsencrypt.org"
example.com. CAA 0 issuewild "letsencrypt.org"
```
### Firewall-Konfiguration
Port 80 und 443 müssen offen sein:
```bash
# 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`**:
```nginx
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:
```bash
# 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):
```nginx
# 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
```bash
# 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`**:
```yaml
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**:
```bash
# 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):
```yaml
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`**:
```ini
dns_cloudflare_email = admin@example.com
dns_cloudflare_api_key = your_cloudflare_api_key
```
**Ausstellung**:
```bash
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
```bash
# 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**:
```bash
#!/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**:
```bash
# 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**:
```bash
# 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**:
```bash
# 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**:
```nginx
# Content Security Policy Header
add_header Content-Security-Policy "upgrade-insecure-requests" always;
# In Application
# Verwende relative URLs oder HTTPS:
# Relativ - empfohlen
# Absolut HTTPS
```
## Security Best Practices
### 1. SSL-Konfiguration Härten
**Mozilla SSL Configuration Generator**: https://ssl-config.mozilla.org/
```nginx
# 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:
```nginx
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
```
2. **Submit to Preload List**: https://hstspreload.org/
### 3. OCSP Stapling
Verbessert SSL-Handshake-Performance:
```nginx
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:
- https://crt.sh/?q=example.com
- https://transparencyreport.google.com/https/certificates
## Testing & Validation
### SSL Labs Test
Teste SSL-Konfiguration:
```bash
# 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
```bash
curl -I https://example.com | grep -i "strict-transport-security\|x-frame-options\|x-content-type-options"
```
### Certificate Chain Validation
```bash
openssl s_client -connect example.com:443 -showcerts
```
## Backup & Recovery
### Backup Zertifikate
```bash
# 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
```bash
# 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