Certificate Management
SSL/TLS certificate management for collabrains.eu infrastructure.
Overview
- Provider: Let's Encrypt (automated)
- Proxy: Traefik v3 (handles renewal)
- Storage:
/data/coolify/proxy/acme.json - Renewal: Automatic (60 days before expiry)
- Supported domains: All collabrains.eu subdomains
Certificate Status
Check All Certificates
# Quick check of all domains
for domain in docs.collabrains.eu grafana.collabrains.eu auth.collabrains.eu \
photos.collabrains.eu n8n.collabrains.eu jellyfin.collabrains.eu \
jitsi.collabrains.eu grist.collabrains.eu webui.collabrains.eu \
budget.collabrains.eu pocket.collabrains.eu; do
echo "=== $domain ==="
openssl s_client -connect $domain:443 -servername $domain </dev/null 2>&1 | \
grep -E "subject=|CN=|notAfter"
done
Check Single Certificate
openssl s_client -connect docs.collabrains.eu:443 -servername docs.collabrains.eu \
</dev/null 2>&1 | grep -E "subject=|CN=|notAfter"
# Example output:
# subject=CN = docs.collabrains.eu
# notAfter=Jun 4 12:00:00 2026 GMT
View Certificate Details
# Full certificate info
openssl s_client -connect docs.collabrains.eu:443 -servername docs.collabrains.eu \
</dev/null 2>&1 | openssl x509 -text -noout
Automatic Renewal
How It Works
- Traefik ACME client monitors certificate expiry
- 60 days before expiry: Sends renewal request to Let's Encrypt
- ACME challenge: Proves domain ownership via HTTP-01
- New certificate: Issued and stored in acme.json
- Traefik reload: Automatically uses new certificate
Verify Auto-Renewal
# Check Traefik logs for ACME activity
docker logs traefik --tail 50 | grep -i "acme\|certificate"
# Look for:
# - "acme - [...] Obtaining Let's Encrypt certificate"
# - "acme - [...] Certificate received"
# - "acme - [...] Renewing certificate"
Manual Certificate Renewal
Force Renewal
# 1. Backup current certificate
cp /data/coolify/proxy/acme.json /data/coolify/proxy/acme.json.backup-$(date +%Y%m%d)
# 2. Remove old certificates to force renewal
rm /data/coolify/proxy/acme.json
# 3. Restart Traefik
docker restart traefik
# 4. Wait for certificate generation (30-60 seconds)
sleep 60
# 5. Verify new certificate
openssl s_client -connect docs.collabrains.eu:443 -servername docs.collabrains.eu \
</dev/null 2>&1 | grep "notAfter"
# 6. Check Traefik logs
docker logs traefik --tail 20 | grep -i certificate
Adding New Domain
Process
- DNS setup: Ensure domain points to server IP
- Service config: Add domain to Traefik labels in docker-compose.yml
- Certificate: Traefik auto-generates on first request
- Verify: Check certificate status
Example: Add new.collabrains.eu
1. Update docker-compose.yml:
services:
service_name:
labels:
- 'traefik.http.routers.https-service.rule=Host(`new.collabrains.eu`) && PathPrefix(`/`)'
- 'traefik.http.routers.https-service.tls.certresolver=letsencrypt'
2. Restart service:
docker compose restart service_name
3. Wait for certificate:
# Monitor Traefik logs
docker logs traefik -f | grep -i acme
4. Verify:
curl -I https://new.collabrains.eu
Troubleshooting
Certificate Not Renewing
Symptoms: Certificate expires, HTTPS errors
Check Traefik logs:
docker logs traefik --tail 100 | grep -i "error\|acme"
Common causes:
- DNS resolution fails: Check nslookup collabrains.eu
- Port 443 blocked: Verify UFW allows 443
- Let's Encrypt rate limit: Wait 1 hour before retry
- acme.json corrupted: See "Restore from Backup"
Solution:
# 1. Verify DNS
nslookup docs.collabrains.eu
dig docs.collabrains.eu
# 2. Check firewall
ufw status | grep 443
# 3. Force renewal
cp /data/coolify/proxy/acme.json /data/coolify/proxy/acme.json.backup
rm /data/coolify/proxy/acme.json
docker restart traefik
sleep 60
docker logs traefik --tail 20
Certificate Mismatch Error
Symptoms: Browser shows "certificate mismatch" or "name mismatch"
Cause: Service using wrong domain or certificate not found
Check:
# What domain is being served?
openssl s_client -connect ACTUAL_DOMAIN:443 -servername ACTUAL_DOMAIN </dev/null 2>&1 | grep "subject="
# What domain is expected?
echo "Expected domain: docs.collabrains.eu"
# If different, check:
cat /data/coolify/services/SERVICE_ID/docker-compose.yml | grep "traefik.http.routers"
Fix:
# Update domain in docker-compose.yml and restart
nano /data/coolify/services/SERVICE_ID/docker-compose.yml
# Change domain
docker compose restart
ACME Challenge Failure
Symptoms: Traefik logs show "Challenge failed"
Causes: - HTTP-01 challenge needs HTTP access (port 80) - Firewall blocking port 80 - Traefik not accessible from internet
Check:
# Verify port 80 is open
curl -I http://collabrains.eu
# Check UFW
ufw status | grep 80
# Check Traefik config
docker logs traefik --tail 50 | grep "acme\|http-01"
Certificate Backup
Backup acme.json
# Automated (part of daily backup)
/usr/local/bin/backup-collabrains.sh
# Manual backup
cp /data/coolify/proxy/acme.json /data/coolify/proxy/acme.json.backup-$(date +%Y%m%d-%H%M%S)
# Verify
ls -lh /data/coolify/proxy/acme.json*
Restore acme.json
# 1. From recent backup
ls -lh /backups/*/acme.json
# 2. If not in backup, restore from daily backup
# Assuming backup script includes coolify config
cp /backups/YYYY-MM-DD/coolify-acme.json /data/coolify/proxy/acme.json
# 3. Verify permissions
chmod 600 /data/coolify/proxy/acme.json
chown root:root /data/coolify/proxy/acme.json
# 4. Restart Traefik
docker restart traefik
# 5. Verify
docker logs traefik --tail 10
acme.json Structure
The acme.json file contains:
{
"letsencrypt": {
"Account": { ... },
"Certificates": [
{
"domain": {
"main": "docs.collabrains.eu",
"sans": []
},
"certificate": "-----BEGIN CERTIFICATE-----...",
"key": "-----BEGIN RSA PRIVATE KEY-----...",
"Store": "letsencrypt"
}
]
}
}
⚠️ Keep acme.json secure - it contains private keys!
Let's Encrypt Rate Limits
Be aware of: - 50 certificates per domain per 7 days - 50 certificates per registered domain per 7 days
If rate limited: 1. Wait 7 days before retry 2. Or request rate limit reset: https://letsencrypt.org/docs/rate-limits/#overrides
Certificate Monitoring
Monthly Check
# Add to crontab for monthly reminder
0 0 1 * * root /usr/local/bin/check-certs.sh
Script (/usr/local/bin/check-certs.sh):
#!/bin/bash
echo "Certificate Status Report" > /tmp/cert-report.txt
for domain in docs.collabrains.eu grafana.collabrains.eu auth.collabrains.eu; do
echo "=== $domain ===" >> /tmp/cert-report.txt
openssl s_client -connect $domain:443 -servername $domain \
</dev/null 2>&1 | grep "notAfter" >> /tmp/cert-report.txt
done
# Send report via email if needed
Related Documentation
- Troubleshooting — HTTPS issues
- Common Commands — Network debugging