Configuration
Proxy settings
Configure Caddy reverse proxy for automatic HTTPS and load balancing.
Overview
Odysseus uses Caddy as a reverse proxy for web roles. Caddy provides:
- Automatic HTTPS with Let's Encrypt
- HTTP/2 and HTTP/3 support
- Dynamic configuration via admin API
- Zero-downtime routing updates
Basic configuration
proxy:
hosts:
- myapp.example.com
app_port: 3000
ssl: true
ssl_email: admin@example.com
hosts
Domain names that route to your application. Caddy will obtain SSL certificates for each domain.
proxy:
hosts:
- myapp.example.com
- www.myapp.example.com
- api.myapp.example.com
app_port
The port your application listens on inside the container.
proxy:
app_port: 3000
Common ports:
- Rails:
3000 - Node.js:
3000or8080 - Python/Gunicorn:
8000 - Go:
8080
SSL configuration
Automatic HTTPS
Enable automatic SSL with Let's Encrypt:
proxy:
hosts:
- myapp.example.com
ssl: true
ssl_email: admin@example.com
DNS configuration
Your domain must point to your server's IP address before deploying. Let's Encrypt validates domain ownership via HTTP.
Without SSL
For internal services or development:
proxy:
hosts:
- internal.local
ssl: false
Health checks
Configure HTTP health checks for your application.
proxy:
hosts:
- myapp.example.com
app_port: 3000
healthcheck:
path: /health
interval: 10
timeout: 5
expect_status: 200 # Optional: specific status code or range
path
The endpoint Caddy checks to verify your app is healthy.
interval
Seconds between health checks. Default: 10.
timeout
Seconds to wait for a response. Default: 5.
expect_status
The HTTP status code expected from the health check. Default: any 2xx status.
You can specify:
- A specific status code:
200,301,204 - A status range:
"2xx","3xx"
This is useful for applications that return non-200 responses on their health endpoints:
# Ghost CMS redirects root to /ghost/
healthcheck:
path: /
expect_status: 301
# API that returns 204 No Content
healthcheck:
path: /health
expect_status: 204
# Accept any 2xx status (default behavior)
healthcheck:
path: /health
expect_status: "2xx"
Health check endpoint
Your application should expose a health check endpoint:
# Rails example
get '/health', to: proc { [200, {}, ['OK']] }
// Express example
app.get('/health', (req, res) => res.send('OK'))
How routing works
During deployment
- New container starts
- Odysseus waits for container health check
- New container added to Caddy upstream
- Traffic flows to both old and new containers
- Old container removed from upstream
- Old container gracefully drained
- Old container stopped
Load balancing
With multiple web servers:
servers:
web:
hosts:
- app1.example.com
- app2.example.com
Caddy load balances across all healthy containers using round-robin.
Advanced configuration
Multiple domains
Route different domains to the same application:
proxy:
hosts:
- myapp.example.com
- www.myapp.example.com
- app.myapp.example.com
All domains receive SSL certificates and route to the same containers.
Internal services
For services that don't need public access (like job workers), don't configure a proxy:
servers:
web:
hosts:
- app.example.com
jobs:
hosts:
- worker.example.com
cmd: bundle exec sidekiq
proxy:
hosts:
- myapp.example.com
app_port: 3000
Only the web role gets proxy configuration. The jobs role runs without public access.
Caddy management
Odysseus automatically deploys and manages Caddy as a Docker container (odysseus-caddy). You don't need to install Caddy manually on your servers.
On your first deploy, Odysseus will:
- Create the
odysseusDocker network - Start the Caddy container with ports 80, 443, and 2019 (admin API)
- Mount
/var/lib/odysseus/caddyfor persistent SSL certificates
Verify Caddy
Check if the Caddy container is running:
ssh your-server "docker ps | grep odysseus-caddy"
ssh your-server "curl -s localhost:2019/config/"
Certificate persistence
SSL certificates are stored in /var/lib/odysseus/caddy on the host. This ensures certificates persist across container restarts and aren't re-requested from Let's Encrypt.