6.1 KiB
6.1 KiB
SSL/TLS Certificate Management
Managing SSL certificates for the homelab infrastructure
Overview
The homelab uses Nginx Proxy Manager (NPM) as the primary certificate authority, with Let's Encrypt providing free SSL certificates.
Certificate Authorities
Primary: Let's Encrypt
- Provider: Let's Encrypt
- Validation: HTTP-01 (automatic via NPM)
- Renewal: Automatic at 90 days
- Domains: *.vish.local, *.vish.gg
Secondary: Self-Signed
- Use: Internal services (non-public)
- Tool: OpenSSL
- Regeneration: As needed
Certificate Locations
Nginx Proxy Manager
/opt/docker/npm/data/
├── letsencrypt/
│ └── accounts/
│ └── acme-v02.api.letsencrypt.org/
└── ssl/
└── <domain>/
├── fullchain.pem
├── privkey.pem
└── bundle.crt
Services with Own Certs
- Authentik:
/opt/authentik/ssl/ - Matrix:
/etc/matrix-synapse/ssl/ - PostgreSQL:
/etc/ssl/private/
Adding New Certificates
Via NPM UI (Recommended)
- Access NPM:
http://calypso.vish.local:81 - Navigate to SSL Certificates → Add SSL Certificate
- Enter domain names:
service.vish.local(internal)service.vish.gg(public)
- Enable Force SSL
- Click Save
Via CLI (Automation)
# Using certbot directly
certbot certonly --webroot \
-w /var/www/html \
-d service.vish.local \
--agree-tos \
--email admin@vish.local
Certificate Renewal
Automatic (Default)
- NPM auto-renews 7 days before expiration
- No action required
- Check logs: NPM → Logs
Manual Renewal
# Force renewal via NPM
docker exec nginx-proxy-manager npm --root /etc/npm \
force-renew
# Or via API
curl -X POST http://npm/api/nginx/certificates/<id>/renew
Ansible Playbook
ansible-playbook ansible/automation/playbooks/certificate_renewal.yml
Certificate Status
Check Expiration
# Via NPM
# Navigate to SSL Certificates tab
# Via openssl
echo | openssl s_client -connect service.vish.local:443 2>/dev/null | openssl x509 -noout -dates
# Via script
cd /opt/npm/letsencrypt/live/
for cert in */; do
echo "$cert: $(openssl x509 -enddate -noout -in "$cert/cert.pem" | cut -d= -f2)"
done
Certificate Dashboard
| Domain | Expiry | Status | Renews |
|---|---|---|---|
| vish.gg | +85 days | ✅ Active | Auto |
| *.vish.local | +85 days | ✅ Active | Auto |
Common Issues
Rate Limiting
Problem: Too many certificate requests
Solution:
- Wait 1 hour (Let's Encrypt limit)
- Use staging environment for testing
- Request multiple domains in one cert
DNS Validation Failure
Problem: ACME challenge fails
Solution:
- Verify DNS A record points to public IP
- Check firewall allows port 80
- Ensure no CNAME conflicts
Mixed Content Warnings
Problem: HTTP resources on HTTPS page
Solution:
- Update service config to use HTTPS URLs
- For internal services, use HTTP (NPM handles SSL)
- Check browser console for details
Certificate Mismatch
Problem: Wrong certificate served
Solution:
- Check NPM proxy host settings
- Verify certificate is assigned
- Clear browser cache
- Check for multiple certificates
Internal Services (Self-Signed)
Creating Self-Signed Cert
# Create directory
mkdir -p /opt/service/ssl
# Generate certificate
openssl req -x509 -nodes -days 365 \
-newkey rsa:2048 \
-keyout /opt/service/ssl/key.pem \
-out /opt/service/ssl/cert.pem \
-addext "subjectAltName=DNS:service.local,DNS:service"
# Set permissions
chmod 600 /opt/service/ssl/key.pem
Adding to Trust Store
# Linux (Ubuntu/Debian)
sudo cp /opt/service/ssl/cert.pem /usr/local/share/ca-certificates/service.crt
sudo update-ca-certificates
# macOS
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /opt/service/ssl/cert.pem
Matrix/Synapse Certificates
Custom Certificate Setup
# docker-compose.yml
services:
synapse:
environment:
- SYNAPSE_TLS_CERT_FILE=/ssl/tls.crt
- SYNAPSE_TLS_KEY_FILE=/ssl/tls.key
volumes:
- ./ssl:/ssl:ro
Federation Certificates
# Add to TLS certificates
/usr/local/bin/REDACTED_APP_PASSWORD \
--server-name vish.local \
--tls-cert /opt/npm/ssl/vish.gg/fullchain.pem \
--tls-key /opt/npm/ssl/vish.gg/privkey.pem
Security Best Practices
Key Permissions
# Private keys should be readable only by root
chmod 600 /path/to/privkey.pem
chown root:root /path/to/privkey.pem
Cipher Suites
Configure in NPM under Settings → SSL → Advanced:
ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA256
HSTS
Enable in NPM:
- Settings → SSL → Force HSTS
- Preload recommended
Backup
Backup Certificates
# Backup NPM certificates
tar -czf backups/ssl-$(date +%Y%m%d).tar.gz \
/opt/docker/npm/data/letsencrypt/ \
/opt/docker/npm/data/ssl/
Restore
# Restore
tar -xzf backups/ssl-20240101.tar.gz -C /
# Restart NPM
docker-compose -f /opt/docker/npm/docker-compose.yml restart
Monitoring
Expiration Alerts
Configure in Prometheus/Alertmanager:
groups:
- name: certificates
rules:
- alert: REDACTED_APP_PASSWORD
expr: (certify_not_after - time()) < (86400 * 30)
for: 1h
labels:
severity: warning
annotations:
summary: "Certificate expiring soon"
Useful Commands
# Check all certificates
docker exec nginx-proxy-manager npm --root /etc/npm list
# Force renewal
docker exec nginx-proxy-manager npm --root /etc/npm force-renew
# Manual ACME challenge
docker exec -it nginx-proxy-manager sh
cd /etc/letsencrypt/renewal-hooks/deploy/
# Verify certificate
openssl s_client -connect vish.gg:443 -servername vish.gg