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 admin@yourdomain.com
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