Skip to main content
better-openclaw supports automatic reverse proxy configuration with either Caddy or Traefik. Both proxies provide automatic HTTPS via Let’s Encrypt and route services to subdomains.

Choosing a reverse proxy

Select your reverse proxy when generating a stack:
npx create-better-openclaw
# Select proxy: caddy or traefik

Caddy configuration

Caddy provides zero-config HTTPS with automatic certificate management.

Generated files

When you select Caddy, the generator creates:
  • Caddyfile - Reverse proxy routes for all exposed services
  • docker-compose.yml - Caddy service with port mappings

Caddyfile structure

Each service with exposed ports gets a subdomain route:
# PostgreSQL
# Relational database with full SQL support
postgresql.example.com {
    reverse_proxy postgresql:5432 {
        header_up Host {host}
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-For {remote_host}
        header_up X-Forwarded-Proto {scheme}
    }
}

# Root domain serves OpenClaw gateway
example.com {
    reverse_proxy openclaw:18789 {
        header_up Host {host}
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-For {remote_host}
        header_up X-Forwarded-Proto {scheme}
    }
}

Global options

The Caddyfile includes automatic HTTPS configuration:
{
    email [email protected]
    acme_ca https://acme-v02.api.letsencrypt.org/directory
}

Custom ports

Override default HTTP (80) and HTTPS (443) ports:
npx create-better-openclaw \
  --proxy caddy \
  --proxy-http-port 8080 \
  --proxy-https-port 8443 \
  --domain example.com
This automatically updates the Caddy service port mappings in docker-compose.yml.

Environment variables

.env
# Domain for service routing
OPENCLAW_DOMAIN=example.com

# Custom Caddy ports (optional)
CADDY_EXTERNAL_PORT=80
CADDY_EXTERNAL_PORT_1=443

Traefik configuration

Traefik uses Docker labels for automatic service discovery and routing.

Generated files

  • traefik/traefik.yml - Static configuration
  • docker-compose.yml - Services with Traefik labels

Static configuration

traefik/traefik.yml
api:
  dashboard: true
  insecure: true

entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    network: openclaw-network

certificatesResolvers:
  letsencrypt:
    acme:
      email: [email protected]
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web

log:
  level: INFO

Docker labels

Traefik discovers services via Docker labels. Each exposed service gets:
labels:
  traefik.enable: "true"
  # HTTPS router
  traefik.http.routers.postgresql.rule: "Host(`postgresql.example.com`)"
  traefik.http.routers.postgresql.entrypoints: "websecure"
  traefik.http.routers.postgresql.tls.certresolver: "letsencrypt"
  traefik.http.services.postgresql.loadbalancer.server.port: "5432"
  # HTTP → HTTPS redirect
  traefik.http.routers.postgresql-http.rule: "Host(`postgresql.example.com`)"
  traefik.http.routers.postgresql-http.entrypoints: "web"
  traefik.http.routers.postgresql-http.middlewares: "redirect-to-https"

Traefik dashboard

Access the Traefik dashboard at http://localhost:8080 (insecure mode for local development).
For production, secure the dashboard:
  1. Set api.insecure: false in traefik.yml
  2. Add HTTP basic auth middleware
  3. Use a dedicated subdomain with TLS

Custom ports

npx create-better-openclaw \
  --proxy traefik \
  --proxy-http-port 8080 \
  --proxy-https-port 8443 \
  --domain example.com

No reverse proxy

Services are exposed directly on their host ports:
npx create-better-openclaw --proxy none
Access services at:
  • http://localhost:18789 - OpenClaw gateway
  • http://localhost:5432 - PostgreSQL
  • http://localhost:6379 - Redis

DNS configuration

Before deploying with a reverse proxy, configure DNS A records for your domain and all service subdomains.
Create a wildcard A record pointing to your server:
*.example.com    A    YOUR_SERVER_IP
example.com      A    YOUR_SERVER_IP
This routes all subdomains (postgresql.example.com, grafana.example.com, etc.) to your server.

Individual records

Alternatively, create an A record for each service:
example.com              A    YOUR_SERVER_IP
postgresql.example.com   A    YOUR_SERVER_IP
redis.example.com        A    YOUR_SERVER_IP
grafana.example.com      A    YOUR_SERVER_IP

Firewall configuration

Open required ports on your server:
# Allow HTTP and HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Or custom ports
sudo ufw allow 8080/tcp
sudo ufw allow 8443/tcp

Troubleshooting

Certificate errors

Caddy:
# Check Caddy logs
docker compose logs caddy

# Verify ACME challenge is reachable
curl http://example.com/.well-known/acme-challenge/test
Traefik:
# Check certificate resolver
docker compose logs traefik | grep letsencrypt

# Inspect acme.json
docker compose exec traefik cat /letsencrypt/acme.json

Service not accessible

  1. Verify DNS resolution:
    nslookup postgresql.example.com
    
  2. Check service health:
    docker compose ps
    
  3. Test proxy routing:
    # Caddy
    docker compose exec caddy caddy validate --config /etc/caddy/Caddyfile
    
    # Traefik
    curl http://localhost:8080/api/http/routers
    

Port conflicts

better-openclaw automatically detects port conflicts and suggests available ports during generation. If you encounter conflicts after deployment:
# Check port usage
sudo netstat -tulpn | grep :80

# Update ports in .env
CADDY_EXTERNAL_PORT=8080
CADDY_EXTERNAL_PORT_1=8443

# Restart stack
docker compose up -d

Build docs developers (and LLMs) love