SSL & DNS Configuration
Set up TLS certificates and DNS for production HTTPS access.
Checklist
- DNS A record pointing to the server IP
- Choose TLS strategy: Let's Encrypt (self-managed) or external proxy
- Obtain and install certificate
- Verify HTTPS access
- Set up automatic certificate renewal
DNS Configuration
Create an A record pointing your domain to the server's public IP:
| Record Type | Name | Value | TTL |
|---|---|---|---|
| A | search.zol.be | YOUR_SERVER_IP | 300 |
Verify DNS propagation:
dig search.zol.be +short
# Should return your server's IP address
# Or use nslookup
nslookup search.zol.be
TLS Strategy
Option A: Let's Encrypt (Self-Managed)
Best when the server handles its own TLS termination.
# Install certbot
sudo apt install -y certbot
# Stop nginx temporarily (certbot needs port 80)
docker compose -f docker/docker-compose.app.yml down
# Get a certificate
sudo certbot certonly --standalone -d search.zol.be
# Restart the app
APP_IMAGE=zol-rag-app:${GIT_SHA} \
docker compose -f docker/docker-compose.app.yml --env-file .env.prod up -d
Mount Certificates into Container
Edit docker/docker-compose.app.yml to mount the certificates:
services:
app:
volumes:
- /etc/letsencrypt/live/search.zol.be:/etc/nginx/ssl:ro
ports:
- "80:80"
- "443:443"
Then restart:
APP_IMAGE=zol-rag-app:${GIT_SHA} \
docker compose -f docker/docker-compose.app.yml --env-file .env.prod up -d
The nginx configuration in the app image includes commented TLS blocks ready to activate.
Automatic Renewal
Let's Encrypt certificates expire after 90 days. Set up auto-renewal:
# Test renewal (dry run)
sudo certbot renew --dry-run
# Add cron job for automatic renewal
sudo crontab -e
# Add this line:
# 0 3 * * * certbot renew --quiet --deploy-hook "docker restart zol-app"
Option B: External Reverse Proxy
If Novation or ZOL IT provides a reverse proxy (Traefik, Caddy, or a load balancer):
- The app container listens on port 80 (HTTP only)
- The external proxy terminates TLS and forwards to port 80
- Ensure the proxy sets these headers:
X-Forwarded-Proto: httpsX-Forwarded-For: <client-ip>
- Update
CORS_ORIGINSin.env.prodto match the external domain
No certificate management is needed on the ZOL RAG server in this case.
Verify HTTPS
# Check certificate
curl -vI https://search.zol.be 2>&1 | grep -E "subject:|expire date:|issuer:"
# Health check over HTTPS
curl -s https://search.zol.be/health | python3 -m json.tool
# Check frontend loads
curl -s -o /dev/null -w "%{http_code}" https://search.zol.be/
# Expected: 200
Security Headers
The nginx configuration includes production security headers:
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Update CORS Origins
After setting up your domain, update .env.prod:
CORS_ORIGINS=["https://search.zol.be","https://zol.be","https://www.zol.be"]
Then restart the app:
APP_IMAGE=zol-rag-app:${GIT_SHA} \
docker compose -f docker/docker-compose.app.yml --env-file .env.prod up -d
Next: User Management →