Caddy Web Server Setup: Automatic HTTPS and Reverse Proxy
Caddy web server installation, Caddyfile configuration, automatic Let's Encrypt SSL, reverse proxy, static site hosting, PHP-FPM integration, and performance settings.
Table of Contents
Caddy Web Server Setup: Automatic HTTPS and Reverse Proxy
Caddy is a modern web server that stands out with automatic HTTPS certificate management. Thanks to its Let's Encrypt integration, it automatically obtains, renews, and configures SSL certificates.
What is Caddy?
Caddy's standout features:
- Automatic HTTPS: Certificate management with Let's Encrypt is fully automatic
- Simple configuration: Caddyfile syntax is easy to read
- HTTP/2 and HTTP/3: Enabled by default
- Reverse proxy: Built-in load balancing and health checks
- Zero downtime reload: No interruption on configuration changes
Caddy requires root privileges to use ports 80 and 443. This is automatically managed when running as a systemd service.
Caddy Installation
Installation via apt (Ubuntu/Debian)
# Add Caddy GPG key and repository
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
# Install Caddy
sudo apt update
sudo apt install caddy -y
# Check service status
sudo systemctl status caddy
Binary Installation
# Download latest version
VERSION=$(curl -s https://api.github.com/repos/caddyserver/caddy/releases/latest | grep tag_name | cut -d '"' -f 4)
curl -L "https://github.com/caddyserver/caddy/releases/download/${VERSION}/caddy_${VERSION#v}_linux_amd64.tar.gz" -o caddy.tar.gz
# Extract and install
tar -xzf caddy.tar.gz
sudo mv caddy /usr/local/bin/
sudo chmod +x /usr/local/bin/caddy
# Check version
caddy version
Custom Build with xcaddy
# Install xcaddy
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
# Build with additional modules
xcaddy build \
--with github.com/caddy-dns/cloudflare \
--with github.com/greenpau/caddy-security
Caddyfile Syntax
Basic Structure
# Single site
example.com {
root * /var/www/html
file_server
}
# Multiple sites
example.com {
respond "Hello example.com!"
}
api.example.com {
reverse_proxy localhost:3000
}
Global Options
# Global settings at the top of Caddyfile
{
# Admin API port
admin localhost:2019
# Email (for Let's Encrypt)
email admin@example.com
# ACME server (staging for testing)
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
# HTTP port
http_port 80
# HTTPS port
https_port 443
# Log format
log {
output file /var/log/caddy/access.log
format json
}
}
Automatic HTTPS
Caddy automatically obtains a Let's Encrypt certificate for your domain:
# Just write the domain name - HTTPS is automatic!
example.com {
root * /var/www/html
file_server
encode gzip
}
# www redirect
www.example.com {
redir https://example.com{uri} permanent
}
# Wildcard certificate (requires DNS challenge)
*.example.com {
tls {
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}
@api host api.example.com
handle @api {
reverse_proxy localhost:3000
}
}
Caddy automatically renews certificates. No manual intervention required. Certificates are stored in ~/.local/share/caddy/ or /var/lib/caddy/.
Reverse Proxy Configuration
Basic Reverse Proxy
app.example.com {
reverse_proxy localhost:3000
}
# Multiple upstreams (load balancing)
api.example.com {
reverse_proxy {
to localhost:3001 localhost:3002 localhost:3003
lb_policy round_robin
health_uri /health
health_interval 30s
health_timeout 5s
}
}
Advanced Reverse Proxy
app.example.com {
reverse_proxy localhost:3000 {
# Forward headers
header_up Host {upstream_hostport}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
# Timeouts
transport http {
dial_timeout 5s
response_header_timeout 30s
}
# Retry
lb_retries 3
lb_try_duration 5s
}
# Security headers
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options nosniff
X-Frame-Options DENY
Referrer-Policy strict-origin-when-cross-origin
-Server
}
}
Static File Serving
static.example.com {
root * /var/www/static
file_server {
# Directory listing
browse
# Hide hidden files
hide .git .env
}
encode gzip zstd
# Cache headers
header /assets/* Cache-Control "public, max-age=31536000, immutable"
header /*.html Cache-Control "no-cache"
# Error pages
handle_errors {
rewrite * /errors/{err.status_code}.html
file_server
}
}
# SPA (Single Page Application)
app.example.com {
root * /var/www/app
encode gzip
try_files {path} /index.html
file_server
}
PHP-FPM Integration
php.example.com {
root * /var/www/php
encode gzip
# Route PHP files to PHP-FPM
php_fastcgi unix//run/php/php8.2-fpm.sock
# Or via TCP
# php_fastcgi localhost:9000
file_server
# For WordPress
@notStatic {
not path /wp-content/* /wp-includes/*
not file
}
rewrite @notStatic /index.php
}
# Full WordPress configuration
wordpress.example.com {
root * /var/www/wordpress
encode gzip
php_fastcgi unix//run/php/php8.2-fpm.sock
@disallowed path /xmlrpc.php /wp-admin/install.php
respond @disallowed 403
@static path /wp-content/* /wp-includes/*
handle @static {
file_server
}
handle {
try_files {path} /index.php
php_fastcgi unix//run/php/php8.2-fpm.sock
}
}
Caddy with Docker
# docker-compose.yml
services:
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp" # HTTP/3
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
- ./html:/var/www/html:ro
environment:
- CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}
app:
build: ./app
expose:
- "3000"
volumes:
caddy_data:
caddy_config:
# Docker Caddyfile
example.com {
reverse_proxy app:3000
}
Management via API
Caddy supports dynamic configuration via REST API:
# View current configuration
curl http://localhost:2019/config/
# Update configuration
curl -X POST http://localhost:2019/load \
-H "Content-Type: application/json" \
-d @caddy.json
# Reload Caddy (zero downtime)
caddy reload --config /etc/caddy/Caddyfile
# Validate configuration
caddy validate --config /etc/caddy/Caddyfile
# Format Caddyfile
caddy fmt --overwrite /etc/caddy/Caddyfile
Performance Settings
{
servers {
max_header_size 1MB
timeouts {
read_body 10s
read_header 10s
write 30s
idle 2m
}
}
}
example.com {
# Compression
encode {
gzip 6
zstd
minimum_length 1024
}
reverse_proxy localhost:3000
}
Conclusion
Caddy meets modern web server needs with automatic HTTPS management and simple configuration. It requires much less configuration compared to Nginx or Apache while offering all the features needed for production environments. It's an ideal choice especially for those who want to automate Let's Encrypt certificate management.
Related Articles
Docker Compose Guide: Multi-Container Application Management
Define and manage multi-container applications with Docker Compose: service dependencies, environment variables, health checks, scaling, and production deployment. Comprehensive guide.
Automated Deployment with GitHub Actions: CI/CD Pipeline Setup
Build CI/CD pipelines with GitHub Actions: Docker build and push, SSH server deployment, test automation, secrets management, and workflow optimization.
Server Automation with Ansible: Infrastructure as Code
Ansible installation, inventory file, playbook writing, role structure, variables, encrypted vault, ad-hoc commands, and server configuration automation. Step-by-step guide.