Skip to content

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

  1. Traefik ACME client monitors certificate expiry
  2. 60 days before expiry: Sends renewal request to Let's Encrypt
  3. ACME challenge: Proves domain ownership via HTTP-01
  4. New certificate: Issued and stored in acme.json
  5. 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

  1. DNS setup: Ensure domain points to server IP
  2. Service config: Add domain to Traefik labels in docker-compose.yml
  3. Certificate: Traefik auto-generates on first request
  4. 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