Skip to main content

Caddy Setup

Caddy provides automatic HTTPS with Let's Encrypt certificates, making it ideal for UAPK Gateway deployments.

Why Caddy?​

FeatureBenefit
Automatic HTTPSLet's Encrypt integration
Zero config TLSSecure defaults
Simple syntaxEasy to maintain
HTTP/2 & HTTP/3Modern performance
Automatic renewalNo certificate expiry

Basic Configuration​

Caddyfile​

gateway.yourdomain.com {
reverse_proxy localhost:8000
}

That's it! Caddy will automatically:

  • Obtain a TLS certificate from Let's Encrypt
  • Configure HTTPS with secure defaults
  • Redirect HTTP to HTTPS
  • Renew certificates automatically

Production Configuration​

Full Caddyfile​

{
email [email protected]
acme_ca https://acme-v02.api.letsencrypt.org/directory
}

gateway.yourdomain.com {
# Proxy to gateway
reverse_proxy localhost:8000 {
# Health check
health_uri /api/v1/gateway/health
health_interval 30s
health_timeout 5s

# Headers
header_up X-Real-IP {remote_host}
header_up X-Forwarded-Proto {scheme}
}

# Security headers
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
X-XSS-Protection "1; mode=block"
Referrer-Policy "strict-origin-when-cross-origin"
Content-Security-Policy "default-src 'self'"
-Server
}

# Rate limiting
rate_limit {
zone api {
match path /api/*
rate 100r/m
}
}

# Logging
log {
output file /var/log/caddy/access.log {
roll_size 100mb
roll_keep 10
}
format json
}

# Compression
encode gzip zstd
}

# Redirect www to non-www
www.gateway.yourdomain.com {
redir https://gateway.yourdomain.com{uri} permanent
}

Docker Compose Integration​

docker-compose.yml​

version: '3.8'

services:
gateway:
image: ghcr.io/uapk/gateway:latest
restart: unless-stopped
environment:
DATABASE_URL: postgresql://uapk:${DB_PASSWORD}@db:5432/uapk_gateway
SECRET_KEY: ${SECRET_KEY}
expose:
- "8000"

caddy:
image: caddy:2-alpine
restart: unless-stopped
depends_on:
- gateway
ports:
- "80:80"
- "443:443"
- "443:443/udp" # HTTP/3
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
- /var/log/caddy:/var/log/caddy

volumes:
caddy_data:
caddy_config:

Multiple Domains​

API and Dashboard​

# API endpoint
api.yourdomain.com {
reverse_proxy localhost:8000

header {
Access-Control-Allow-Origin "*"
Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Access-Control-Allow-Headers "Authorization, Content-Type, X-API-Key"
}
}

# Dashboard
dashboard.yourdomain.com {
reverse_proxy localhost:8000 {
# Only allow UI paths
header_up X-Dashboard "true"
}

# Stricter CSP for dashboard
header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'"
}

Load Balancing​

Multiple Gateway Instances​

gateway.yourdomain.com {
reverse_proxy gateway1:8000 gateway2:8000 gateway3:8000 {
lb_policy round_robin
health_uri /api/v1/gateway/health
health_interval 10s

# Sticky sessions (if needed)
# lb_policy cookie
}
}

TLS Configuration​

Custom Certificates​

gateway.yourdomain.com {
tls /path/to/cert.pem /path/to/key.pem

reverse_proxy localhost:8000
}

Internal CA​

{
# Use internal CA for testing
local_certs
}

gateway.internal {
reverse_proxy localhost:8000
}

Client Certificates (mTLS)​

gateway.yourdomain.com {
tls {
client_auth {
mode require_and_verify
trusted_ca_cert_file /path/to/ca.pem
}
}

reverse_proxy localhost:8000
}

Security Hardening​

Rate Limiting Plugin​

# Install rate limit plugin
xcaddy build --with github.com/mholt/caddy-ratelimit
{
order rate_limit before reverse_proxy
}

gateway.yourdomain.com {
rate_limit {
zone api {
match path /api/*
rate 100r/m
}
zone auth {
match path /api/v1/auth/*
rate 10r/m
}
}

reverse_proxy localhost:8000
}

IP Filtering​

gateway.yourdomain.com {
@blocked {
remote_ip 10.0.0.0/8
}
respond @blocked "Forbidden" 403

reverse_proxy localhost:8000
}

Basic Auth (for staging)​

staging.yourdomain.com {
basicauth * {
admin $2a$14$... # bcrypt hash
}

reverse_proxy localhost:8000
}

Monitoring​

Metrics Endpoint​

{
servers {
metrics
}
}

:9180 {
metrics /metrics
}

gateway.yourdomain.com {
reverse_proxy localhost:8000
}

Prometheus Integration​

# prometheus.yml
scrape_configs:
- job_name: 'caddy'
static_configs:
- targets: ['caddy:9180']

Troubleshooting​

Check Certificate Status​

# View certificate details
curl -vI https://gateway.yourdomain.com 2>&1 | grep -A 10 "SSL certificate"

# Check certificate expiry
echo | openssl s_client -servername gateway.yourdomain.com -connect gateway.yourdomain.com:443 2>/dev/null | openssl x509 -noout -dates

Force Certificate Renewal​

# Remove old certificate
docker compose exec caddy rm -rf /data/caddy/certificates

# Reload Caddy
docker compose exec caddy caddy reload

Debug Mode​

{
debug
}

gateway.yourdomain.com {
reverse_proxy localhost:8000
}

View Logs​

# Caddy logs
docker compose logs caddy

# Access logs
tail -f /var/log/caddy/access.log | jq

Common Issues​

IssueSolution
Certificate not issuedCheck DNS, firewall ports 80/443
502 Bad GatewayCheck gateway service is running
Slow HTTPSEnable HTTP/2 and compression
Rate limit too strictAdjust rate_limit configuration