Skip to main content

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 TypeNameValueTTL
Asearch.zol.beYOUR_SERVER_IP300

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):

  1. The app container listens on port 80 (HTTP only)
  2. The external proxy terminates TLS and forwards to port 80
  3. Ensure the proxy sets these headers:
    • X-Forwarded-Proto: https
    • X-Forwarded-For: <client-ip>
  4. Update CORS_ORIGINS in .env.prod to 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 →