
What Is Caddy and Why It Is Easier Than Nginx/Apache
Caddy is a modern web server written in Go, designed with simplicity as a first-class priority. Its defining feature is automatic SSL certificate provisioning via Let's Encrypt and ZeroSSL — no Certbot installation, no cron jobs, no manual renewal steps required. Caddy handles all of this silently in the background.
Setting up Nginx with SSL involves multiple steps: installing Certbot, running certbot --nginx, verifying renewal with cron, and maintaining separate server blocks for HTTP and HTTPS. Caddy collapses all of this into a single, human-readable configuration file called the Caddyfile. A complete HTTPS-enabled site can be configured in under five lines.
- Automatic HTTPS — Obtains and renews SSL certificates via ACME without any additional tools
- Simple configuration — Caddyfile syntax is far more readable than nginx.conf directives
- HTTP/2 and HTTP/3 out of the box — No extra configuration needed to enable modern protocols
- Built-in reverse proxy — Route traffic to Node.js, PHP, or Python backends in a single line
- Zero-downtime reloads — Apply configuration changes without dropping active connections
Prerequisites Before Installation
Before installing Caddy, ensure the following conditions are met on your VPS:
- Ubuntu VPS running 20.04 LTS or 22.04 LTS (22.04 recommended), with at least 512 MB RAM
- Ports 80 and 443 open in your firewall — Caddy requires port 80 for the ACME HTTP challenge
- A domain pointing to your VPS IP — Verify with
dig your-domain.comornslookup your-domain.com - Root or sudo access on the server
- If using UFW, open the necessary ports first:
sudo ufw allow 80 && sudo ufw allow 443
This last point is critical: Caddy cannot issue an SSL certificate unless Let's Encrypt can reach your server on port 80. If DNS has not fully propagated yet, or if another service is already occupying port 80, the ACME challenge will fail.
Installing Caddy on Ubuntu via the Official APT Repository
Caddy's recommended installation method on Debian/Ubuntu is through its official APT repository, which ensures you receive automatic updates when new versions are released. Run the following commands in order:
# 1. Install required dependencies
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
# 2. Add Caddy's GPG signing key
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
| sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
# 3. Add the Caddy APT repository
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
| sudo tee /etc/apt/sources.list.d/caddy-stable.list
# 4. Update and install Caddy
sudo apt update
sudo apt install caddy
After installation, Caddy is automatically registered as a systemd service. Verify it is running with:
sudo systemctl status caddy
Basic Caddyfile Structure
Caddy's configuration file, the Caddyfile, lives at /etc/caddy/Caddyfile by default. The structure is block-based: each domain gets its own block containing directives. Here is a minimal example:
# /etc/caddy/Caddyfile
# Serve a static site with automatic HTTPS
example.com {
root * /var/www/html
file_server
}
# Redirect www to non-www
www.example.com {
redir https://example.com{uri} permanent
}
That is all that is needed. Caddy will automatically obtain an SSL certificate for example.com, configure TLS, and redirect all HTTP traffic to HTTPS. There is no need for separate listen 80 and listen 443 blocks as in Nginx.
Caddyfile Examples: Static Sites and Reverse Proxy
Static Website with Compression and Logging
# /etc/caddy/Caddyfile
mywebsite.com {
root * /var/www/mywebsite
file_server
encode gzip
log {
output file /var/log/caddy/mywebsite.log
}
}
Reverse Proxy to a Node.js Application (Port 3000)
app.mywebsite.com {
reverse_proxy localhost:3000
}
Reverse Proxy to PHP-FPM
php.mywebsite.com {
root * /var/www/phpapp
php_fastcgi unix//run/php/php8.2-fpm.sock
file_server
}
Reverse Proxy to a Python/Flask Application (Port 5000)
api.mywebsite.com {
reverse_proxy localhost:5000 {
header_up Host {host}
header_up X-Real-IP {remote}
}
}
Multiple domains can be served from a single Caddyfile by stacking blocks. Caddy issues a separate SSL certificate for each domain automatically, making it straightforward to host multiple applications on a single VPS.
Caddy vs Nginx vs Apache: Side-by-Side Comparison
Choosing a web server comes down to trade-offs between ease of configuration, raw performance, and ecosystem maturity. The table below summarises the key differences:
| Feature | Caddy | Nginx | Apache |
|---|---|---|---|
| Automatic SSL | Built-in | Requires Certbot | Requires Certbot |
| Config Complexity | Very simple | Moderate | Moderate–complex |
| HTTP/3 (QUIC) | Built-in | Extra configuration | Extra module |
| Raw Performance | Good | Excellent | Good |
| Plugin Ecosystem | Limited | Extensive | Largest |
| Beginner Friendly | Best | Moderate | Moderate |
In summary: choose Caddy when you want HTTPS working immediately with minimal configuration. Choose Nginx when raw throughput is a priority and your team is comfortable with server administration. Choose Apache when you need the broadest compatibility or rely heavily on .htaccess rules.
Advanced Caddyfile Configuration: Headers, Auth, and Logging
Beyond serving files and proxying traffic, Caddy provides built-in directives for security hardening and observability that require no additional modules.
Adding Security Headers
mywebsite.com {
root * /var/www/mywebsite
file_server
header {
# Prevent clickjacking
X-Frame-Options "SAMEORIGIN"
# Enforce HTTPS for one year
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# Stop MIME-type sniffing
X-Content-Type-Options "nosniff"
# Remove server fingerprint
-Server
}
}
Protecting an Admin Area with Basic Authentication
First generate a bcrypt password hash using caddy hash-password, then reference it in the Caddyfile:
admin.mywebsite.com {
reverse_proxy localhost:8080
basicauth {
# username: admin, hash from: caddy hash-password
admin JDJhJDEyJGF....(hash)
}
}
Structured JSON Logging for Production
{
log {
format json
output file /var/log/caddy/access.log {
roll_size 100mb
roll_keep 5
}
}
}
mywebsite.com {
root * /var/www/mywebsite
file_server
}
The global block (the braces before any site block) applies settings across all sites in the Caddyfile. The roll_size and roll_keep options rotate logs automatically to prevent disk exhaustion in production.
Monitoring Caddy and Diagnosing Common Issues
Maintaining a Caddy installation in production involves periodically checking certificate health and understanding the log output when errors occur.
Checking Certificate Status
# List all certificates managed by Caddy
sudo caddy list-certificates
# Validate Caddyfile syntax before applying changes
sudo caddy validate --config /etc/caddy/Caddyfile
Key Diagnostic Commands
- ACME errors — Run
sudo journalctl -u caddy -n 100 | grep -i acmeto find the exact reason SSL issuance failed - Port conflicts — Run
sudo ss -tlnp | grep -E ':80|:443'to identify any process already occupying those ports - Permission errors — Run
sudo journalctl -u caddy -p errto see file access failures - Config syntax errors — Run
sudo caddy validatebefore every reload to catch mistakes early
Forcing Certificate Renewal Manually
# Stop Caddy to release port 80, then force renewal
sudo systemctl stop caddy
sudo caddy run --config /etc/caddy/Caddyfile &
# Caddy will attempt to renew all certificates on startup
# Then return to normal service mode
sudo systemctl start caddy
Manual renewal is rarely necessary since Caddy handles renewal automatically 30 days before expiry. The above steps are useful if a certificate has already expired and the automatic process has been interrupted by an extended service outage.
Managing Caddy with systemctl
Since Caddy runs as a systemd service, all management is done with standard systemctl commands:
# Start Caddy
sudo systemctl start caddy
# Stop Caddy
sudo systemctl stop caddy
# Reload configuration after editing the Caddyfile (no restart needed)
sudo systemctl reload caddy
# Check service status and recent logs
sudo systemctl status caddy
# Stream logs in real time
sudo journalctl -u caddy -f
# Enable Caddy to start automatically on server reboot
sudo systemctl enable caddy
The most frequently used command is sudo systemctl reload caddy after editing the Caddyfile. Unlike a full restart, a reload applies the new configuration gracefully without terminating existing connections, making it safe to use in production environments.
Important: Domain Must Point to Your VPS Before SSL Can Be Issued
Caddy's automatic HTTPS relies on the ACME HTTP-01 challenge, which requires Let's Encrypt to make an HTTP request to port 80 of your server. SSL issuance will fail if any of the following conditions exist:
- The domain's DNS record does not yet point to the VPS IP address (DNS propagation may take up to 48 hours)
- Port 80 or 443 is blocked by the VPS firewall or security group
- Another web server (such as a pre-existing Apache installation) is already bound to port 80
- The domain name in the Caddyfile does not exactly match the registered domain
If you encounter ACME-related errors, inspect the Caddy logs with sudo journalctl -u caddy -n 50 to identify the root cause, fix the issue, then reload Caddy to retry certificate issuance.
Key advantage: Caddy automatically renews SSL certificates every 60 days. No cron jobs, no Certbot commands, no manual intervention. As long as the Caddy service is running and the domain remains reachable, certificates will be renewed before expiry — removing one of the most common sources of unexpected HTTPS outages.
Deploy Caddy on AsiaGB VPS
AsiaGB VPS plans start from 500 THB/month with a dedicated public IP, Ubuntu support, and open ports 80/443 — everything you need to run Caddy and serve your applications over HTTPS immediately.
View VPS Plans