Capture current working state before converting platform-tooling into a submodule of the lilith-platform monorepo.
4.5 KiB
4.5 KiB
SSL Certificate Management
Overview
We use Let's Encrypt certificates with DNS-01 validation via PowerDNS.
This approach works for:
- VPN-only domains (no HTTP access from internet)
- Wildcard certificates
- Multi-domain SAN certificates
Architecture
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ acme.sh │────▶│ PowerDNS API │────▶│ Let's Encrypt │
│ (on host) │ │ (ns1/ns2) │ │ (ACME) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ nginx ssl/ │
│ (certs) │
└─────────────────┘
Quick Reference
Issue Certificate
# From lilith-platform/infrastructure directory
./scripts/security/issue-letsencrypt-cert.sh <host> <cert-name> <domain1> [domain2] ...
Examples
# Staging: atlilith.com domains on black
./scripts/security/issue-letsencrypt-cert.sh black next.atlilith.com \
next.atlilith.com next.status.atlilith.com next.www.atlilith.com
# Staging: trustedmeet.com domains on black
./scripts/security/issue-letsencrypt-cert.sh black next.trustedmeet.com \
next.trustedmeet.com next.www.trustedmeet.com
# Production: atlilith.com domains on 0
./scripts/security/issue-letsencrypt-cert.sh 0 atlilith.com \
atlilith.com www.atlilith.com status.atlilith.com api.atlilith.com
# Production: trustedmeet.com domains on 0
./scripts/security/issue-letsencrypt-cert.sh 0 trustedmeet.com \
trustedmeet.com www.trustedmeet.com
Current Certificates
black (Staging)
| Certificate | Domains | Nginx Config |
|---|---|---|
next.atlilith.com |
next.atlilith.com, next.www.atlilith.com, next.status.atlilith.com | /bigdisk/forgejo/ssl/next.atlilith.com.{crt,key} |
0 (Production)
| Certificate | Domains | Nginx Config |
|---|---|---|
atlilith.com |
(to be issued) | /etc/nginx/ssl/atlilith.com.{crt,key} |
Certificate Locations
| Host | SSL Directory | Reload Command |
|---|---|---|
| black | /bigdisk/forgejo/ssl/ |
docker exec forgejo-nginx nginx -s reload |
| 0 | /etc/nginx/ssl/ |
systemctl reload nginx |
Renewal
acme.sh automatically configures a cron job for renewal. Certificates renew ~30 days before expiry.
Check renewal status:
ssh black "~/.acme.sh/acme.sh --list"
ssh 0 "~/.acme.sh/acme.sh --list"
Force renewal:
ssh black "~/.acme.sh/acme.sh --renew -d next.atlilith.com --force"
Prerequisites
First-time Setup (per host)
# SSH to target host
ssh black # or ssh 0
# Install acme.sh
curl https://get.acme.sh | sh -s email=admin@atlilith.com
source ~/.bashrc
PowerDNS API Access
The script reads PowerDNS API credentials from:
vault/dns-servers-powerdns.txt
PowerDNS API must be accessible from the target host via VPN (10.0.0.11:8081).
Nginx Configuration
After issuing a certificate, update nginx config:
server {
listen 443 ssl;
server_name example.atlilith.com;
ssl_certificate /path/to/ssl/cert-name.crt;
ssl_certificate_key /path/to/ssl/cert-name.key;
# Modern SSL settings
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:...;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
# ... rest of config
}
Troubleshooting
DNS propagation issues
# Check if TXT record was created
dig +short TXT _acme-challenge.example.atlilith.com @ns1.nasty.sh
PowerDNS API connectivity
# Test API from target host
ssh black "curl -H 'X-API-Key: <key>' http://10.0.0.11:8081/api/v1/servers"
Certificate verification
# Check certificate details
ssh black "openssl x509 -in /bigdisk/forgejo/ssl/next.atlilith.com.crt -noout -text"
# Check expiry
ssh black "openssl x509 -in /bigdisk/forgejo/ssl/next.atlilith.com.crt -noout -enddate"
Related Files
infrastructure/docker/forgejo/nginx.conf- Nginx config for black (Docker)infrastructure/nginx/- Nginx configs for productionvault/dns-servers-powerdns.txt- PowerDNS API credentials