Caddy Web Server: Complete Configuration Guide
In this tutorial, you'll learn about Caddy Web Server: Complete Configuration Guide. We cover key concepts, practical examples, and best practices.
Caddy is a modern web server written in Go that automatically provisions and renews HTTPS certificates via Let's Encrypt. Its simple Caddyfile syntax and zero-configuration TLS make it the fastest way to deploy a secure production web server.
In this tutorial, you will learn to configure Caddy using the Caddyfile syntax, set up automatic HTTPS, configure reverse proxies to backend services, serve static files, integrate PHP-FPM, and deploy Caddy in production. DodaTech uses Caddy for internal dashboards and Doda Browser telemetry endpoints where quick HTTPS setup is critical.
What You'll Learn
By the end of this guide, you will deploy Caddy as a static file server and reverse proxy, configure automatic HTTPS with Let's Encrypt, enable PHP-FPM for dynamic sites, use Caddy API for dynamic configuration, and harden your Caddy deployment for production.
Why Caddy Matters
Caddy is the only web server that enables HTTPS by default. Every site you configure gets automatic certificates with no extra commands. Its Caddyfile syntax is dramatically simpler than NGINX or Apache configuration. For teams that value developer experience and security, Caddy is the best choice. It works well in DevOps pipelines and integrates with Linux systemd for production deployments.
Caddy Learning Path
flowchart LR
A[Installation] --> B[Static File Serving]
B --> C[Reverse Proxy]
C --> D[PHP-FPM]
D --> E[Caddy API]
E --> F{You Are Here}
style F fill:#f90,color:#fff
Installation
# Install Caddy from the official repository
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
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
sudo apt update && sudo apt install caddy -y
# Verify
sudo systemctl start caddy
sudo systemctl enable caddy
caddy version
Expected output
v2.8.4 h1:... (version number may vary)
Static File Serving
Caddy serves static files with zero configuration beyond a domain name. Create a Caddyfile:
dodatech.com {
root * /var/www/dodatech/public
encode gzip
file_server
}
Start Caddy:
sudo caddy reload --config /etc/caddy/Caddyfile
Expected behavior
curl -I https://dodatech.com/index.html
# HTTP/2 200
# content-type: text/html
# content-encoding: gzip
Caddy automatically obtains a Let's Encrypt certificate for dodatech.com, redirects HTTP to HTTPS, and compresses responses with gzip. No additional commands needed.
Reverse Proxy
Proxy requests to a backend application server:
api.dodatech.com {
reverse_proxy 127.0.0.1:3000 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
# Rate limiting
rate_limit {
zone api_rate {
key {remote_host}
events 100
window 1m
}
}
encode gzip
}
For load balancing across multiple backends:
app.dodatech.com {
reverse_proxy 10.0.1.1:8080 10.0.1.2:8080 10.0.1.3:8080 {
lb_policy least_conn
health_uri /health
health_interval 30s
health_timeout 5s
}
}
Testing the proxy
# Start a test backend
python3 -m http.server 3000 &
# Test proxied request
curl -H "Host: api.dodatech.com" http://localhost
# Response from the backend on port 3000
PHP-FPM Integration
Serve PHP applications with Caddy's php_fastcgi directive:
blog.dodatech.com {
root * /var/www/blog/public
encode gzip
# PHP-FPM on the default socket
php_fastcgi unix//run/php/php8.3-fpm.sock
file_server
}
For WordPress or Laravel-style setups:
shop.dodatech.com {
root * /var/www/shop/public
encode gzip
# Try files, then pass to PHP-FPM
try_files {path} /index.php?{query}
php_fastcgi unix//run/php/php8.3-fpm.sock {
env APP_ENV production
env DB_HOST localhost
}
file_server
}
Expected behavior
curl -I https://shop.dodatech.com
# HTTP/2 200
# Powered-By: PHP/8.3
Caddy API for Dynamic Configuration
Caddy's API allows configuration changes without editing files:
# Load a configuration via JSON
curl -X PUT "http://localhost:2019/config/" \
-H "Content-Type: application/json" \
-d '{
"apps": {
"http": {
"servers": {
"example": {
"listen": [":443"],
"routes": [
{
"handle": [
{"handler": "static_response", "body": "Hello from Caddy API"}
]
}
]
}
}
}
}
}'
# Add a route dynamically
curl -X POST "http://localhost:2019/config/apps/http/servers/example/routes" \
-H "Content-Type: application/json" \
-d '{
"handle": [{
"handler": "reverse_proxy",
"upstreams": [{"dial": "127.0.0.1:3000"}]
}],
"match": [{"host": ["api.dodatech.com"]}]
}'
Expected behavior
curl https://api.dodatech.com/status
# {"status":"ok"} (from the backend on port 3000)
Security and Best Practices
dodatech.com {
# Directory listing disabled by default
root * /var/www/dodatech/public
# Security headers
header {
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
Permissions-Policy "geolocation=(), microphone=(), camera=()"
}
# Request logging
log {
output file /var/log/caddy/access.log
format json
}
# Enforce security
@blocked {
path /wp-admin/*
path /admin/*
path /\.env
}
respond @blocked 404
encode gzip
file_server
}
Common Errors
1. Certificate Renewal Fails
Caddy renews certificates automatically, but port 80 must be reachable for the HTTP challenge. Ensure your firewall allows inbound traffic on port 80.
2. Permission Denied on Socket
Caddy runs as the caddy user. PHP-FPM sockets must be readable by the caddy user. Add listen.owner = caddy and listen.group = caddy in www.conf.
3. Reverse Proxy 502 Bad Gateway
The backend is not running or unreachable. Check sudo systemctl status your-backend and verify the upstream address in the Caddyfile.
4. Caddyfile Syntax Error
Use sudo caddy validate --config /etc/caddy/Caddyfile to check syntax before reloading. Caddy reports the exact line number of the error.
5. Port 80 Already in Use
Apache or NGINX may already be binding port 80. Stop the conflicting service with sudo systemctl stop apache2 before starting Caddy.
6. Logs Not Writing
The log directory must be writable by the caddy user. Create the directory: sudo mkdir -p /var/log/caddy && sudo chown caddy:caddy /var/log/caddy.
Practice Questions
1. How does Caddy automatically get HTTPS certificates? Caddy uses the ACME protocol to automatically obtain and renew Let's Encrypt certificates. When you specify a domain in the Caddyfile, Caddy handles the entire certificate lifecycle without manual intervention.
2. What is the difference between reverse_proxy and php_fastcgi?
reverse_proxy forwards HTTP requests to a backend server (Node.js, Go, Python). php_fastcgi communicates with PHP-FPM using the FastCGI protocol, specifically for PHP applications.
3. How do you reload Caddy configuration without downtime?
Use sudo caddy reload --config /etc/caddy/Caddyfile. Caddy gracefully reloads the configuration while serving existing connections. Alternatively, use the Caddy API endpoint POST /load.
4. Challenge: Multi-service Caddy deployment
Configure Caddy to serve three services under one domain:
dodatech.com/serves static files from/var/www/maindodatech.com/api/*proxies tolocalhost:3000dodatech.com/blog/*proxies tolocalhost:8080
Use path-based routing with Caddy's route directive.
Mini Project: Production Caddy Stack
Deploy Caddy as the primary web server for a multi-service architecture:
- Install Caddy on a Linux server
- Configure a Caddyfile with:
- Static file server for
www.dodatech.comwith gzip and security headers - Reverse proxy for
api.dodatech.comwith load balancing across two Node.js instances - PHP-FPM for
blog.dodatech.comrunning WordPress
- Static file server for
- Verify automatic HTTPS certificates
- Configure JSON-format access logs to
/var/log/caddy/ - Test the rate limiting by sending rapid requests
# Validate Caddyfile
sudo caddy validate --config /etc/caddy/Caddyfile
# Reload configuration
sudo caddy reload --config /etc/caddy/Caddyfile
# Test endpoints
curl -I https://www.dodatech.com/
curl https://api.dodatech.com/health
curl -I https://blog.dodatech.com/
This deployment pattern is used by DodaTech for internal microservices and Doda Browser telemetry endpoints that require quick, secure HTTPS.
FAQ
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro