# SSL/TLS Certificate Renewal Runbook ## Overview This runbook covers SSL/TLS certificate management across the homelab, including Let's Encrypt certificates, Cloudflare Origin certificates, and self-signed certificates. It provides procedures for manual renewal, troubleshooting auto-renewal, and emergency certificate fixes. ## Prerequisites - [ ] SSH access to relevant hosts - [ ] Cloudflare account access (if using Cloudflare) - [ ] Domain DNS control - [ ] Root/sudo privileges on hosts - [ ] Backup of current certificates ## Metadata - **Estimated Time**: 15-45 minutes - **Risk Level**: Medium (service downtime if misconfigured) - **Requires Downtime**: Minimal (few seconds during reload) - **Reversible**: Yes (can restore old certificates) - **Tested On**: 2026-02-14 ## Certificate Types in Homelab | Type | Used For | Renewal Method | Expiration | |------|----------|----------------|------------| | **Let's Encrypt** | Public-facing services | Certbot auto-renewal | 90 days | | **Cloudflare Origin** | Services behind Cloudflare Tunnel | Manual/Cloudflare dashboard | 15 years | | **Synology Certificates** | Synology DSM, services | Synology DSM auto-renewal | 90 days | | **Self-Signed** | Internal/dev services | Manual generation | As configured | ## Certificate Inventory Document your current certificates: ```bash # Check Let's Encrypt certificates (on Linux hosts) sudo certbot certificates # Check Synology certificates # DSM UI → Control Panel → Security → Certificate # Or SSH: sudo cat /usr/syno/etc/certificate/_archive/*/cert.pem | openssl x509 -text -noout # Check certificate expiration for any domain echo | openssl s_client -servername service.vish.gg -connect service.vish.gg:443 2>/dev/null | openssl x509 -noout -dates # Check all certificates at once for domain in st.vish.gg gf.vish.gg mx.vish.gg; do echo "=== $domain ===" echo | timeout 5 openssl s_client -servername $domain -connect $domain:443 2>/dev/null | openssl x509 -noout -dates echo done ``` Create inventory: ```markdown | Domain | Type | Expiry Date | Auto-Renew | Status | |--------|------|-------------|------------|--------| | vish.gg | Let's Encrypt | 2026-05-15 | ✅ Yes | ✅ Valid | | st.vish.gg | Let's Encrypt | 2026-05-15 | ✅ Yes | ✅ Valid | | gf.vish.gg | Let's Encrypt | 2026-05-15 | ✅ Yes | ✅ Valid | ``` ## Let's Encrypt Certificate Renewal ### Automatic Renewal (Certbot) Let's Encrypt certificates should auto-renew. Check the renewal setup: ```bash # Check certbot timer status (systemd) sudo systemctl status certbot.timer # Check cron job (if using cron) sudo crontab -l | grep certbot # Test renewal (dry-run, doesn't actually renew) sudo certbot renew --dry-run # Expected output: # Congratulations, all simulated renewals succeeded ``` ### Manual Renewal If auto-renewal fails or you need to renew manually: ```bash # Renew all certificates sudo certbot renew # Renew specific certificate sudo certbot renew --cert-name vish.gg # Force renewal (even if not expired) sudo certbot renew --force-renewal # Renew with verbose output for troubleshooting sudo certbot renew --verbose ``` After renewal, reload web servers: ```bash # Nginx sudo nginx -t # Test configuration sudo systemctl reload nginx # Apache sudo apachectl configtest sudo systemctl reload apache2 ``` ### Let's Encrypt with Nginx Proxy Manager If using Nginx Proxy Manager (NPM): 1. Open NPM UI (typically port 81) 2. Go to **SSL Certificates** tab 3. Certificates should auto-renew 30 days before expiry 4. To force renewal: - Click the certificate - Click **Renew** button 5. No service reload needed (NPM handles it) ## Synology Certificate Renewal ### Automatic Renewal on Synology NAS ```bash # SSH to Synology NAS (Atlantis or Calypso) ssh atlantis # or calypso # Check certificate status sudo /usr/syno/sbin/syno-letsencrypt list # Force renewal check sudo /usr/syno/sbin/syno-letsencrypt renew-all # Check renewal logs sudo cat /var/log/letsencrypt/letsencrypt.log # Verify certificate expiry sudo openssl x509 -in /usr/syno/etc/certificate/system/default/cert.pem -text -noout | grep "Not After" ``` ### Via Synology DSM UI 1. Log in to DSM 2. **Control Panel** → **Security** → **Certificate** 3. Select certificate → Click **Renew** 4. DSM will automatically renew and apply 5. No manual reload needed ### Synology Certificate Configuration Enable auto-renewal in DSM: 1. **Control Panel** → **Security** → **Certificate** 2. Click **Settings** button 3. Check **Auto-renew certificate** 4. Synology will renew 30 days before expiry ## Stoatchat Certificates (Gaming VPS) The Stoatchat gaming server uses Let's Encrypt with Certbot: ```bash # SSH to gaming VPS ssh root@gaming-vps # Check certificates sudo certbot certificates # Domains covered: # - st.vish.gg # - api.st.vish.gg # - events.st.vish.gg # - files.st.vish.gg # - proxy.st.vish.gg # - voice.st.vish.gg # Renew all sudo certbot renew # Reload Nginx sudo systemctl reload nginx ``` Auto-renewal cron: ```bash # Check certbot timer sudo systemctl status certbot.timer # Or check cron sudo crontab -l | grep certbot ``` ## Cloudflare Origin Certificates For services using Cloudflare Tunnel: ### Generate New Origin Certificate 1. Log in to Cloudflare Dashboard 2. Select domain (vish.gg) 3. **SSL/TLS** → **Origin Server** 4. Click **Create Certificate** 5. Configure: - **Private key type**: RSA (2048) - **Hostnames**: *.vish.gg, vish.gg - **Certificate validity**: 15 years 6. Copy certificate and private key 7. Save to secure location ### Install Origin Certificate ```bash # SSH to target host ssh [host] # Create certificate files sudo nano /etc/ssl/cloudflare/cert.pem # Paste certificate sudo nano /etc/ssl/cloudflare/key.pem # Paste private key # Set permissions sudo chmod 644 /etc/ssl/cloudflare/cert.pem sudo chmod 600 /etc/ssl/cloudflare/key.pem # Update Nginx configuration sudo nano /etc/nginx/sites-available/[service] # Use new certificate ssl_certificate /etc/ssl/cloudflare/cert.pem; ssl_certificate_key /etc/ssl/cloudflare/key.pem; # Test and reload sudo nginx -t sudo systemctl reload nginx ``` ## Self-Signed Certificates (Internal/Dev) For internal-only services not exposed publicly: ### Generate Self-Signed Certificate ```bash # Generate 10-year self-signed certificate sudo openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ -keyout /etc/ssl/private/selfsigned.key \ -out /etc/ssl/certs/selfsigned.crt \ -subj "/C=US/ST=State/L=City/O=Homelab/CN=internal.vish.local" # Generate with SAN (Subject Alternative Names) for multiple domains sudo openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ -keyout /etc/ssl/private/selfsigned.key \ -out /etc/ssl/certs/selfsigned.crt \ -subj "/C=US/ST=State/L=City/O=Homelab/CN=*.vish.local" \ -addext "subjectAltName=DNS:*.vish.local,DNS:vish.local" # Set permissions sudo chmod 600 /etc/ssl/private/selfsigned.key sudo chmod 644 /etc/ssl/certs/selfsigned.crt ``` ### Install in Services Update Docker Compose to mount certificates: ```yaml services: service: volumes: - /etc/ssl/certs/selfsigned.crt:/etc/ssl/certs/cert.pem:ro - /etc/ssl/private/selfsigned.key:/etc/ssl/private/key.pem:ro ``` ## Monitoring Certificate Expiration ### Set Up Expiration Alerts Create a certificate monitoring script: ```bash sudo nano /usr/local/bin/check-certificates.sh ``` ```bash #!/bin/bash # Certificate Expiration Monitoring Script DOMAINS=( "vish.gg" "st.vish.gg" "gf.vish.gg" "mx.vish.gg" ) ALERT_DAYS=30 # Alert if expiring within 30 days WEBHOOK_URL="https://ntfy.sh/REDACTED_TOPIC" # Your notification webhook for domain in "${DOMAINS[@]}"; do echo "Checking $domain..." # Get certificate expiration date expiry=$(echo | openssl s_client -servername $domain -connect $domain:443 2>/dev/null | \ openssl x509 -noout -dates | grep "notAfter" | cut -d= -f2) # Convert to epoch time expiry_epoch=$(date -d "$expiry" +%s) current_epoch=$(date +%s) days_left=$(( ($expiry_epoch - $current_epoch) / 86400 )) echo "$domain expires in $days_left days" if [ $days_left -lt $ALERT_DAYS ]; then # Send alert curl -H "Title: Certificate Expiring Soon" \ -H "Priority: high" \ -H "Tags: warning,certificate" \ -d "Certificate for $domain expires in $days_left days!" \ $WEBHOOK_URL echo "⚠️ Alert sent for $domain" fi echo done ``` Make executable and add to cron: ```bash sudo chmod +x /usr/local/bin/check-certificates.sh # Add to cron (daily at 9 AM) (crontab -l 2>/dev/null; echo "0 9 * * * /usr/local/bin/check-certificates.sh") | crontab - ``` ### Grafana Dashboard Add certificate monitoring to Grafana: ```bash # Install blackbox_exporter for HTTPS probing # Add to prometheus.yml: scrape_configs: - job_name: 'blackbox' metrics_path: /probe params: module: [http_2xx] static_configs: - targets: - https://vish.gg - https://st.vish.gg - https://gf.vish.gg relabel_configs: - source_labels: [__address__] target_label: __param_target - source_labels: [__param_target] target_label: instance - target_label: __address__ replacement: blackbox-exporter:9115 # Create alert rule: - alert: SSLCertificateExpiring expr: probe_ssl_earliest_cert_expiry - time() < 86400 * 30 labels: severity: warning annotations: summary: "SSL certificate expiring soon" description: "SSL certificate for {{ $labels.instance }} expires in {{ $value | REDACTED_APP_PASSWORD }}" ``` ## Troubleshooting ### Issue: Certbot Renewal Failing **Symptoms**: `certbot renew` fails with DNS or HTTP challenge errors **Solutions**: ```bash # Check detailed error logs sudo certbot renew --verbose # Common issues: # 1. Port 80/443 not accessible sudo ufw status # Check firewall sudo netstat -tlnp | grep :80 # Check if port is listening # 2. DNS not resolving correctly dig vish.gg # Verify DNS points to correct IP # 3. Rate limits hit # Let's Encrypt has rate limits: 50 certificates per domain per week # Wait 7 days or use --staging for testing # 4. Webroot path incorrect sudo certbot renew --webroot -w /var/www/html # 5. Try force renewal with different challenge sudo certbot renew --force-renewal --preferred-challenges dns ``` ### Issue: Certificate Valid But Browser Shows Warning **Symptoms**: Certificate is valid but browsers show security warning **Solutions**: ```bash # Check certificate chain openssl s_client -connect vish.gg:443 -showcerts # Ensure intermediate certificates are included # Nginx: Use fullchain.pem, not cert.pem ssl_certificate /etc/letsencrypt/live/vish.gg/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/vish.gg/privkey.pem; # Test SSL configuration curl -I https://vish.gg # Or use: https://www.ssllabs.com/ssltest/ ``` ### Issue: Synology Certificate Not Auto-Renewing **Symptoms**: DSM certificate expired or shows renewal error **Solutions**: ```bash # SSH to Synology ssh atlantis # Check renewal logs sudo cat /var/log/letsencrypt/letsencrypt.log # Common issues: # 1. Port 80 forwarding # Ensure port 80 is forwarded to NAS during renewal # 2. Domain validation # Check DNS points to correct external IP # 3. Force renewal sudo /usr/syno/sbin/syno-letsencrypt renew-all # 4. Restart certificate service sudo synosystemctl restart nginx ``` ### Issue: Nginx Won't Reload After Certificate Update **Symptoms**: `nginx -t` shows SSL errors **Solutions**: ```bash # Test Nginx configuration sudo nginx -t # Common errors: # 1. Certificate path incorrect # Fix: Update nginx config with correct path # 2. Certificate and key mismatch # Verify: sudo openssl x509 -noout -modulus -in cert.pem | openssl md5 sudo openssl rsa -noout -modulus -in key.pem | openssl md5 # MD5 sums should match # 3. Permission issues sudo chmod 644 /etc/ssl/certs/cert.pem sudo chmod 600 /etc/ssl/private/key.pem sudo chown root:root /etc/ssl/certs/cert.pem /etc/ssl/private/key.pem # 4. SELinux blocking (if enabled) sudo setsebool -P httpd_read_user_content 1 ``` ## Emergency Certificate Fix If a certificate expires and services are down: ### Quick Fix: Use Self-Signed Temporarily ```bash # Generate emergency self-signed certificate sudo openssl req -x509 -nodes -days 30 -newkey rsa:2048 \ -keyout /tmp/emergency.key \ -out /tmp/emergency.crt \ -subj "/CN=*.vish.gg" # Update Nginx to use emergency cert sudo nano /etc/nginx/sites-available/default ssl_certificate /tmp/emergency.crt; ssl_certificate_key /tmp/emergency.key; # Reload Nginx sudo nginx -t && sudo systemctl reload nginx # Services are now accessible (with browser warning) # Then fix proper certificate renewal ``` ### Restore from Backup ```bash # If certificates were backed up sudo cp /backup/letsencrypt/archive/vish.gg/* /etc/letsencrypt/archive/vish.gg/ # Update symlinks sudo certbot certificates # Shows current status sudo certbot install --cert-name vish.gg ``` ## Best Practices ### Renewal Schedule - Let's Encrypt certificates renew at 60 days (30 days before expiry) - Check certificates monthly - Set up expiration alerts - Test renewal process quarterly ### Backup Certificates ```bash # Backup Let's Encrypt certificates sudo tar czf ~/letsencrypt-backup-$(date +%Y%m%d).tar.gz /etc/letsencrypt/ # Backup Synology certificates # Done via Synology backup tasks # Store backups securely (encrypted, off-site) ``` ### Documentation - Document which certificates are used where - Keep inventory of expiration dates - Document renewal procedures - Note any special configurations ## Verification Checklist After certificate renewal: - [ ] Certificate renewed successfully - [ ] Certificate expiry date extended - [ ] Web servers reloaded without errors - [ ] All services accessible via HTTPS - [ ] No browser security warnings - [ ] Certificate chain complete - [ ] Auto-renewal still enabled - [ ] Monitoring updated (if needed) ## Related Documentation - [Infrastructure Overview](../infrastructure/INFRASTRUCTURE_OVERVIEW.md) - [Nginx Configuration](../infrastructure/networking.md) - [Cloudflare Tunnels Setup](../infrastructure/cloudflare-tunnels-setup.md) - [Emergency Access Guide](../troubleshooting/EMERGENCY_ACCESS_GUIDE.md) ## Change Log - 2026-02-14 - Initial creation - 2026-02-14 - Added monitoring and troubleshooting sections