Deploying Caddy
Introduction
Caddy is the ultimate HTTP/1.1, HTTP/2, and HTTP/3 web server with automatic HTTPS by default. Built in Go for memory safety and performance, Caddy serves as a powerful web server, reverse proxy, and API gateway that simplifies complex infrastructure into easy-to-manage configurations. With built-in support for automatic certificate provisioning, load balancing, and dynamic configuration, Caddy provides enterprise-grade capabilities while maintaining simplicity and ease of use.
Key Features
- Automatic HTTPS: All sites served over HTTPS by default with automatic Let’s Encrypt and ZeroSSL certificate management
- On-Demand TLS: Dynamic certificate provisioning for custom domains and multi-tenant applications
- HTTP/3 Support: Modern HTTP/3 (QUIC) protocol support alongside HTTP/1.1 and HTTP/2
- Reverse Proxy: Powerful reverse proxy with load balancing, health checks, and dynamic backends
- Caddyfile Configuration: Simple, human-readable configuration format for common use cases
- JSON Config API: Full RESTful API for dynamic configuration changes without downtime
- Config Adapters: Support for NGINX, YAML, TOML, and other configuration formats via adapters
- Load Balancing: Advanced load balancing with policy selection and circuit breaking
- Static File Server: Production-grade file serving with compression, range requests, and directory browsing
- Middleware Extensibility: Pluggable middleware system for request/response manipulation
- FastCGI Proxy: Support for PHP and FastCGI applications
- WebSocket Support: Full WebSocket proxying for real-time applications
- gRPC Proxying: Native gRPC protocol support for microservices
- Health Checks: Active and passive health checking for upstream servers
- Rate Limiting: Built-in request rate limiting and concurrency control
- Access Logging: Comprehensive JSON and plain text logging with configurable formats
- Automatic Redirects: HTTP to HTTPS and www redirection
- Virtual Hosts: Support for multiple domains and subdomains in a single configuration
- Request Matching: Advanced request matching for routing and conditional logic
- Response Headers: Easy manipulation of response headers and caching directives
- File Compression: On-the-fly Gzip and Zstandard compression for optimal bandwidth usage
- Graceful Reloads: Zero-downtime configuration updates and deployments
- TLS Customization: Full control over cipher suites, protocols, and certificate management
Prerequisites
Before deploying Caddy on Klutch.sh, ensure you have:
- A Klutch.sh account with access to the dashboard
- A GitHub repository for your Caddy deployment (public or private)
- Basic understanding of web server concepts and HTTP protocols
- (Optional) Custom domains for HTTPS certificate provisioning
Important Considerations
Deployment Steps
Create a Dockerfile
Create a
Dockerfilein the root of your repository with a Caddy image. This Dockerfile sets up Caddy with the latest version and necessary utilities:FROM caddy:latest# Copy Caddyfile configurationCOPY Caddyfile /etc/caddy/Caddyfile# Optionally copy additional files# COPY public /srv/public# Caddy will automatically manage TLS certificates# Expose HTTP and HTTPS portsEXPOSE 80 443# Start CaddyCMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile"]Create Caddyfile Configuration
Create a
Caddyfilein your repository root with basic web server or reverse proxy configuration:# Simple static file serverexample-app.klutch.sh {root * /srv/publicfile_serverencode gzip}# Or for reverse proxy to upstream service# example-app.klutch.sh {# reverse_proxy localhost:3000 {# header_up X-Forwarded-Proto {http.request.proto}# header_up X-Forwarded-For {http.request.remote}# }# }Create Configuration Files
Create additional configuration files for local development:
docker-compose.yml (for local testing only):
version: '3.8'services:caddy:image: caddy:latestcontainer_name: caddy-devports:- "80:80"- "443:443"- "2019:2019"volumes:- ./Caddyfile:/etc/caddy/Caddyfile- ./public:/srv/public- caddy_data:/data- caddy_config:/configenvironment:- CADDY_ADMIN=0.0.0.0:2019volumes:caddy_data:caddy_config:entrypoint.sh (optional startup script):
#!/bin/bashset -e# Wait for any upstream services if needed# sleep 5# Start Caddycaddy run --config /etc/caddy/CaddyfileCreate Environment Variables File
Create a
.env.examplefile for configuration documentation:# Caddy ConfigurationCADDY_ADMIN_ADDR=0.0.0.0:2019CADDY_LOG_LEVEL=info# Upstream Services (if using as reverse proxy)UPSTREAM_HOST=localhostUPSTREAM_PORT=3000# Domain ConfigurationDOMAIN=example-app.klutch.sh# TLS ConfigurationTLS_ISSUER=letsencryptTLS_EMAIL=admin@example.com# Performance SettingsCADDY_MAX_HEADER_SIZE=1048576CADDY_TIMEOUT=30sCreate .gitignore File
Create a
.gitignorefile to exclude sensitive files from version control:# Caddy certificate and key data/data//config/# Environment files.env.env.local# Certificate files*.crt*.key*.pem# Temporary files*.tmp*.log# OS files.DS_StoreThumbs.dbPush to GitHub
Push your repository to GitHub with all configuration files:
Terminal window git add Dockerfile Caddyfile docker-compose.yml .env.example .gitignoregit commit -m "Initial Caddy deployment configuration"git push origin mainDeploy on Klutch.sh
- Navigate to klutch.sh/app and log in to your dashboard
- Click Create New App
- Select Docker as the deployment method (detected automatically from your Dockerfile)
- Connect your GitHub repository containing the Caddy configuration
- Confirm the Dockerfile is detected from your root directory
- Click Deploy to start the deployment process
- Wait for the deployment to complete and your app to become active
Configure Traffic Rules
- In your app dashboard, navigate to Traffic settings
- Select HTTP as the traffic type
- Set the internal port to 80 (Caddy HTTP port)
- Save your traffic configuration
- Your Caddy server will be accessible at
example-app.klutch.sh - HTTPS is automatically enabled and managed by Caddy
Initial Setup and Configuration
After deploying Caddy on Klutch.sh, access your deployment and configure it for your specific use case.
Access Your Deployment
- Your Caddy server is now running at
http://example-app.klutch.sh - Caddy automatically upgrades HTTP connections to HTTPS
- You should see your configured content or reverse proxy working
- The admin API is available at
http://example-app.klutch.sh:2019/
Configure as a Reverse Proxy
To use Caddy as a reverse proxy for upstream services, update your Caddyfile:
example-app.klutch.sh { reverse_proxy localhost:3000 { # Preserve original request information header_up X-Forwarded-Proto {http.request.proto} header_up X-Forwarded-Host {http.request.host} header_up X-Forwarded-For {http.request.remote} header_up X-Real-IP {http.request.remote}
# Connection settings health_uri /health health_interval 10s health_timeout 5s }}Load Balancing Multiple Backends
Configure Caddy to load balance across multiple upstream servers:
example-app.klutch.sh { reverse_proxy localhost:3000 localhost:3001 localhost:3002 { # Load balancing policy (random, least_conn, round_robin, etc.) policy least_conn
# Health checks health_uri /health health_interval 10s
# Timeout settings fail_duration 30s try_duration 10s }}Static File Serving with Compression
Configure Caddy to serve static files with automatic compression:
example-app.klutch.sh { root * /srv/public
# Enable compression encode gzip zstd
# Serve precompressed files if available file_server { precompressed gzip zstd }
# Directory browsing (optional) # file_server browse}Admin API Management
The Caddy admin API allows dynamic configuration updates without downtime. Access it at:
curl http://example-app.klutch.sh:2019/config/Query current configuration:
curl http://example-app.klutch.sh:2019/config/apps/http/servers/srv0/routesUpdate configuration dynamically:
curl -X PATCH http://example-app.klutch.sh:2019/config/apps/http/servers/srv0/routes \ -H "Content-Type: application/json" \ -d '[{ "match": [{"host": ["example-app.klutch.sh"]}], "handle": [{ "handler": "reverse_proxy", "upstreams": [{"dial": "localhost:3000"}] }] }]'Environment Variables
Basic Configuration
Configure these environment variables through your deployment:
CADDY_ADMIN_ADDR=0.0.0.0:2019- Admin API address and portCADDY_LOG_LEVEL=info- Log level (debug, info, warn, error)CADDY_TIMEOUT=30s- Default timeout for requests
Production Environment Variables
For production deployments using Nixpacks, configure these environment variables:
# Admin API configurationCADDY_ADMIN_ADDR=0.0.0.0:2019CADDY_ADMIN_ENFORCE_ORIGIN=true
# Logging configurationCADDY_LOG_LEVEL=infoCADDY_LOG_FORMAT=json
# TLS and certificate managementCADDY_ACME_CA=https://acme-v02.api.letsencrypt.org/directoryCADDY_ACME_EMAIL=admin@example.comCADDY_EAB_KID=CADDY_EAB_HMAC=
# Performance tuningCADDY_GRACE_PERIOD=5sCADDY_HTTP_IDLE_TIMEOUT=5mCADDY_HTTP_READ_TIMEOUT=30sCADDY_HTTP_WRITE_TIMEOUT=30s
# TLS configurationCADDY_TLS_PREFER_CURVES=x25519,secp384r1,secp256r1CADDY_TLS_PROTOCOLS=tls1.2,tls1.3CADDY_TLS_SESSION_TIMEOUT=168h
# Certificate storageCADDY_STORAGE=file_systemCADDY_STORAGE_LOCK_TIMEOUT=5s
# HTTP/3 and QUICCADDY_QUIC_DISABLE=falseCADDY_QUIC_GEOIP_ASN_DB=CADDY_QUIC_GEOIP_CITY_DB=Code Examples
JavaScript - Caddy Admin API Client
// Caddy Admin API Client for configuration managementconst axios = require('axios');
class CaddyClient { constructor(host = 'example-app.klutch.sh', adminPort = 2019) { this.baseURL = `http://${host}:${adminPort}`; this.client = axios.create({ baseURL: this.baseURL, timeout: 30000, headers: { 'Content-Type': 'application/json' } }); }
// Get current config async getConfig() { try { const response = await this.client.get('/config/'); return response.data; } catch (error) { console.error('Error fetching config:', error.message); throw error; } }
// Update reverse proxy upstream async updateReverse Proxy(newUpstream) { try { const config = await this.getConfig();
// Update upstreams in HTTP app if (config.apps?.http?.servers?.srv0?.routes) { const routes = config.apps.http.servers.srv0.routes;
routes.forEach(route => { route.handle?.forEach(handler => { if (handler.handler === 'reverse_proxy') { handler.upstreams = newUpstream.map(url => ({ dial: url })); } }); }); }
await this.client.put('/config/', config); console.log('Configuration updated successfully'); return config; } catch (error) { console.error('Error updating configuration:', error.message); throw error; } }
// Get server stats async getStats() { try { const response = await this.client.get('/stats'); return response.data; } catch (error) { console.error('Error fetching stats:', error.message); throw error; } }
// Stop Caddy async stop() { try { const response = await this.client.post('/stop'); return response.data; } catch (error) { console.error('Error stopping Caddy:', error.message); throw error; } }
// Reload configuration async reload() { try { const config = await this.getConfig(); await this.client.post('/reload', config); console.log('Configuration reloaded'); } catch (error) { console.error('Error reloading configuration:', error.message); throw error; } }
// Add a new route async addRoute(matcher, upstream) { try { const config = await this.getConfig();
if (!config.apps.http.servers.srv0.routes) { config.apps.http.servers.srv0.routes = []; }
const newRoute = { match: [{ host: [matcher] }], handle: [{ handler: 'reverse_proxy', upstreams: [{ dial: upstream }] }] };
config.apps.http.servers.srv0.routes.push(newRoute); await this.client.put('/config/', config); console.log(`Route for ${matcher} added`); } catch (error) { console.error('Error adding route:', error.message); throw error; } }}
// Usage exampleasync function main() { const caddy = new CaddyClient('example-app.klutch.sh', 2019);
// Get current configuration const config = await caddy.getConfig(); console.log('Current config:', JSON.stringify(config, null, 2));
// Update upstreams await caddy.updateReverseProxy(['localhost:3000', 'localhost:3001']);
// Get server stats const stats = await caddy.getStats(); console.log('Server stats:', stats);}
main().catch(console.error);Bash - Caddy Configuration Management Script
#!/bin/bash
# Caddy Configuration Management Script# Requires: curl, jq for JSON processing
CADDY_HOST="example-app.klutch.sh"CADDY_ADMIN_PORT="2019"CADDY_API="http://${CADDY_HOST}:${CADDY_ADMIN_PORT}"
# Color outputRED='\033[0;31m'GREEN='\033[0;32m'YELLOW='\033[1;33m'BLUE='\033[0;34m'NC='\033[0m' # No Color
# Get current configurationget_config() { echo -e "${BLUE}Fetching current configuration...${NC}" curl -s -X GET "${CADDY_API}/config/" | jq . || echo -e "${RED}Failed to fetch config${NC}"}
# Get configuration from a specific pathget_config_path() { local path=$1 echo -e "${BLUE}Fetching config from path: $path${NC}" curl -s -X GET "${CADDY_API}/config/${path}" | jq . || echo -e "${RED}Failed to fetch config at $path${NC}"}
# Reload configurationreload_config() { echo -e "${YELLOW}Reloading configuration...${NC}" curl -s -X POST "${CADDY_API}/reload" \ -H "Content-Type: application/json" \ -d "{}" && echo -e "${GREEN}Configuration reloaded${NC}" || echo -e "${RED}Failed to reload${NC}"}
# List routeslist_routes() { echo -e "${BLUE}Current routes:${NC}" curl -s -X GET "${CADDY_API}/config/apps/http/servers/srv0/routes" | jq '.[] | {matcher: .match, handler: .handle[0].handler}'}
# Add reverse proxy routeadd_reverse_proxy() { local hostname=$1 local upstream=$2
if [ -z "$hostname" ] || [ -z "$upstream" ]; then echo -e "${RED}Usage: add_reverse_proxy <hostname> <upstream>${NC}" echo "Example: add_reverse_proxy api.klutch.sh localhost:3000" return 1 fi
echo -e "${YELLOW}Adding reverse proxy: $hostname -> $upstream${NC}"
local route="{ \"match\": [{\"host\": [\"$hostname\"]}], \"handle\": [{ \"handler\": \"reverse_proxy\", \"upstreams\": [{\"dial\": \"$upstream\"}], \"headers\": { \"request\": { \"set\": { \"X-Forwarded-For\": [\"{http.request.remote}\"], \"X-Forwarded-Proto\": [\"{http.request.proto}\"], \"X-Forwarded-Host\": [\"{http.request.host}\"] } } } }] }"
curl -s -X POST "${CADDY_API}/config/apps/http/servers/srv0/routes" \ -H "Content-Type: application/json" \ -d "$route" && echo -e "${GREEN}Route added${NC}" || echo -e "${RED}Failed to add route${NC}"}
# Get server IDget_id() { echo -e "${BLUE}Server ID:${NC}" curl -s -X GET "${CADDY_API}/id"}
# Get version and build infoget_version() { echo -e "${BLUE}Caddy version and build info:${NC}" curl -s -X GET "${CADDY_API}/version"}
# Check admin API statuscheck_health() { echo -e "${YELLOW}Checking admin API status...${NC}" if curl -s -f "${CADDY_API}/config/" > /dev/null 2>&1; then echo -e "${GREEN}Admin API is healthy${NC}" return 0 else echo -e "${RED}Admin API is not responding${NC}" return 1 fi}
# Monitor request metricsmonitor_metrics() { echo -e "${YELLOW}Monitoring Caddy metrics (press Ctrl+C to stop)...${NC}" while true; do clear echo -e "${BLUE}=== Caddy Metrics ===${NC}" curl -s -X GET "${CADDY_API}/metrics" | grep http_requests | head -20 sleep 5 done}
# Main menucase "${1:-help}" in get) get_config ;; get-path) get_config_path "$2" ;; reload) reload_config ;; routes) list_routes ;; add-proxy) add_reverse_proxy "$2" "$3" ;; id) get_id ;; version) get_version ;; health) check_health ;; monitor) monitor_metrics ;; *) echo -e "${YELLOW}Caddy Configuration Management${NC}" echo "Usage: $0 {get|get-path|reload|routes|add-proxy|id|version|health|monitor} [args]" echo "" echo "Commands:" echo " get - Get entire configuration" echo " get-path <path> - Get config from specific path" echo " reload - Reload configuration" echo " routes - List all routes" echo " add-proxy <host> <upstream> - Add reverse proxy route" echo " id - Get server ID" echo " version - Get version info" echo " health - Check admin API health" echo " monitor - Monitor metrics" ;;esacShell - Caddy Server Management Script
#!/bin/sh
# Caddy Server Management Script# Requires: curl
CADDY_HOST="example-app.klutch.sh"CADDY_ADMIN_PORT="2019"CADDY_API="http://${CADDY_HOST}:${CADDY_ADMIN_PORT}"
echo "=== Caddy Server Management ==="echo "Admin API: $CADDY_API"echo ""
# Get configuration summaryecho "Configuration Summary:"curl -s -X GET "${CADDY_API}/config/" | grep -o '"matcher"' | wc -l | xargs echo "Routes found:"
# Get running timeecho ""echo "Server Status:"curl -s -X GET "${CADDY_API}/id" | head -5
# Show HTTP listener statusecho ""echo "Checking HTTP listeners..."curl -s -X GET "${CADDY_API}/config/apps/http/servers" | head -20
# Get load balance policiesecho ""echo "Load Balancer Status:"curl -s -X GET "${CADDY_API}/config/apps/http/servers/srv0/routes" | \ grep -A 5 '"policy"' || echo "No load balancing policies configured"
# Display reverse proxy upstreamsecho ""echo "Reverse Proxy Upstreams:"curl -s -X GET "${CADDY_API}/config/apps/http/servers/srv0/routes" | \ grep -o '"dial":[^}]*' | head -10
# Check certificate statusecho ""echo "TLS Certificate Status:"curl -s -X GET "${CADDY_API}/config/apps/tls" | head -20
# Get metrics if availableecho ""echo "Request Metrics (last 10):"curl -s -X GET "${CADDY_API}/metrics" 2>/dev/null | grep http_requests | head -10 || echo "Metrics not available"
# Validate configurationecho ""echo "Configuration Validation:"curl -s -X POST "${CADDY_API}/validate/config" \ -H "Content-Type: application/json" \ -d "$(curl -s -X GET "${CADDY_API}/config/")" && \ echo "✓ Configuration is valid" || \ echo "✗ Configuration has errors"Best Practices
- Use Caddyfile for Simple Configs: For straightforward deployments, write configs in Caddyfile format which is more readable and maintainable
- Implement Health Checks: Always configure health checks for upstream servers to ensure requests only go to healthy backends
- Monitor Admin API: Regularly check admin API endpoints to monitor server status and configuration changes
- Version Control Configuration: Keep Caddyfile and configuration in version control for easy rollbacks and auditing
- Use Environment Variables: Leverage Nixpacks environment variables for dynamic configuration across environments
- Implement Rate Limiting: Use Caddy’s rate limiting directives to protect upstream services from abuse
- Compress Responses: Enable automatic compression (gzip/zstd) for text-based content to reduce bandwidth
- Set Proper Timeouts: Configure appropriate timeouts for your use case to prevent hanging connections
- Enable Access Logging: Configure JSON logging for better analysis and debugging of requests
- Use Graceful Reloads: Leverage admin API for configuration updates to avoid downtime
- Certificate Management: Monitor certificate renewal status and ensure ACME challenge endpoints are accessible
- TLS Customization: Review and customize TLS protocols and cipher suites for your security requirements
Troubleshooting
Issue: 502 Bad Gateway errors when proxying
- Verify upstream service is accessible from the container: test with curl from inside container
- Check upstream address format: should be
host:portfor TCP connections - Review health check configuration: ensure health_uri endpoint returns 200 OK
- Check container networking: verify upstream service is reachable on specified port
- Review Caddy logs: check admin API logs for connection errors
Issue: HTTPS certificate not provisioning automatically
- Verify DNS is properly configured: domain must resolve to deployment IP
- Check outbound connectivity: ensure container can reach ACME challenge endpoints
- Review certificate logs: check
/config/for certificate status and errors - Verify port accessibility: ensure ports 80 and 443 are properly exposed and accessible
- Check domain validation: confirm DNS records or ACME challenge responses are working
Issue: Admin API not responding
- Verify admin port mapping: ensure port 2019 is accessible from your location
- Check firewall rules: confirm no firewall is blocking admin API connections
- Review authentication: if authentication is enabled, verify credentials are correct
- Monitor admin process: restart container if admin API becomes unresponsive
- Check logs: review deployment logs for admin API startup errors
Issue: High memory usage
- Review loaded modules: disable unused modules if custom builds are used
- Monitor certificate storage: large numbers of certificates can increase memory usage
- Check logging level: reduce log level from debug to info for production
- Monitor connection count: high concurrent connections increase memory usage
- Review configuration: ensure no infinite loops or problematic middleware stacks
Issue: Slow performance or timeouts
- Check upstream health: verify upstream services are responding quickly
- Review network latency: measure network latency between Caddy and upstreams
- Optimize TLS settings: consider adjusting TLS session timeout and cache settings
- Monitor CPU usage: high CPU usage indicates load or configuration issues
- Check request logging: review slow requests in access logs to identify bottlenecks
Update Instructions
To update Caddy to the latest version:
- Update Dockerfile: Modify your Dockerfile to use the latest Caddy image tag
- Test Locally: Build and test the updated image locally with docker-compose
- Push Changes: Commit and push the updated Dockerfile to GitHub
- Redeploy: In the Klutch.sh dashboard, trigger a manual redeploy or wait for automatic updates
- Verify: After deployment, test critical endpoints and monitor logs for any issues
Example Dockerfile update:
# From current versionFROM caddy:latest
# To specific version (recommended for production)FROM caddy:2.10.2-alpineUse Cases
- API Gateway: Central entry point for microservices with load balancing and routing
- Reverse Proxy: Front-end for legacy applications with automatic HTTPS and modern protocols
- Static Site Hosting: Serve static sites with compression and caching headers
- SaaS Custom Domains: Provision SSL certificates on-demand for customer-owned domains
- Load Balancer: Distribute traffic across multiple backend servers with health checking
- FastCGI Frontend: Serve PHP applications with Caddy as FastCGI reverse proxy
- WebSocket Proxy: Forward WebSocket connections for real-time applications
- gRPC Gateway: Bridge between gRPC services and HTTP clients
- API Rate Limiting: Protect backend services from overwhelming request traffic
- Multi-Tenant Hosting: Host multiple applications with automatic certificate management per domain
Additional Resources
- Caddy Official Website - Comprehensive documentation and guides
- Caddyfile Documentation - Caddyfile syntax and directives
- Caddy Admin API - RESTful API reference for dynamic configuration
- JSON Config Reference - Complete JSON configuration structure
- Caddy Plugins - List of available plugins and extensions
- Config Adapters - Using alternative configuration formats
- Reverse Proxy Documentation - Detailed reverse proxy configuration
- GitHub Repository - Source code and issue tracker
- Caddy Community Forum - Community support and discussions
- Let’s Encrypt Documentation - Certificate provisioning information
Conclusion
Deploying Caddy on Klutch.sh provides a modern, secure, and easy-to-manage HTTP infrastructure with automatic HTTPS, powerful reverse proxy capabilities, and dynamic configuration. By following this guide, you’ve set up a production-ready web server with best-in-class security defaults and flexible configuration options.
Your Caddy deployment is now ready to serve your applications with automatic certificate management, load balancing, and advanced routing capabilities. Whether you’re using it as a static file server, reverse proxy, or API gateway, Caddy’s simple configuration and powerful features make it an ideal choice for modern web infrastructure.
For advanced configurations, monitoring requirements, or troubleshooting assistance, refer to the official Caddy documentation and community resources linked above. Regular monitoring and maintenance of your deployment will ensure optimal performance, security, and reliability for your applications.