Files
homelab-optimized/docs/runbooks/certificate-renewal.md
Gitea Mirror Bot e7652c8dab
Some checks failed
Documentation / Build Docusaurus (push) Failing after 5m3s
Documentation / Deploy to GitHub Pages (push) Has been skipped
Sanitized mirror from private repository - 2026-04-20 01:32:01 UTC
2026-04-20 01:32:01 +00:00

571 lines
14 KiB
Markdown

# 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