- 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.
541 lines
14 KiB
Markdown
541 lines
14 KiB
Markdown
# 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:
|
|
<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/
|
|
|
|
```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
|