LFCE 54 ๐ง Web Server โ Caddy Basics
Caddy is a modern web server and reverse proxy whose defining feature is automatic HTTPS. Where Nginx and Apache require the administrator to obtain certificates, configure the TLS directives, and set up a renewal timer, Caddy does all of this without configuration. A site block with a public hostname is enough: Caddy obtains a certificate from Let’s Encrypt or ZeroSSL, serves the site over HTTPS, redirects HTTP to HTTPS, and renews the certificate before it expires . The previous chapters covered Nginx and Apache, both of which require explicit TLS configuration; this chapter covers the server that treats TLS as the default rather than an add-on.
The configuration language is the Caddyfile, a compact format built from site blocks and directives. It is simpler than the Nginx server/location model and the Apache <VirtualHost>/<Directory> containers, but it is not less capable: the reverse_proxy, file_server, and root directives cover the common deployment patterns, and the handle and route blocks provide path-based routing . The service is managed through systemctl, and the configuration is validated with caddy validate before every reload.
Key point: A site address with a public hostname triggers automatic HTTPS. Caddy obtains the certificate, configures TLS, and redirects HTTP to HTTPS without any additional directives. For local development or internal hosts, the automatic HTTPS uses Caddy’s internal CA, or the http:// scheme disables it explicitly . Always run caddy validate --config /etc/caddy/Caddyfile before reloading; a syntax error prevents the new configuration from being applied.
Why Caddy replaces explicit TLS configuration
The certificate-management problem.
Nginx and Apache separate the certificate acquisition from the server configuration. The administrator runs Certbot, obtains the certificate, writes the ssl_certificate directives, and relies on a systemd timer for renewal. Caddy integrates all of these steps. When a site block contains a hostname like example.com, Caddy registers the domain with an ACME certificate authority, completes the HTTP-01 or TLS-ALPN challenge, installs the certificate, and schedules the renewal . The administrator does not write a single TLS directive.
The configuration-simplicity problem.
The Caddyfile is designed to be read and written by humans. A static file server is two lines: the site address and the root and file_server directives. A reverse proxy is two lines: the site address and the reverse_proxy directive. There are no separate listen, server_name, and location blocks to coordinate. The directive order is handled by Caddy’s built-in ordering, and the handle and route blocks provide explicit control when the default order is not what the application needs .
The HTTP-to-HTTPS-redirect problem.
Nginx and Apache require an explicit redirect block on port 80. Caddy performs the redirect automatically. When a site is configured with a hostname, Caddy listens on both port 80 and port 443, serves the site over HTTPS, and redirects HTTP requests to the HTTPS URL. The redirect is part of the automatic HTTPS behavior, not a separate configuration . The administrator does not need to write a return 301 directive or a separate server block.
a. The site block and static file serving
A Caddyfile consists of one or more site blocks. Each block begins with a site address and contains directives in curly braces. The simplest static file server uses the root directive to set the document root and the file_server directive to enable static file serving .
# /etc/caddy/Caddyfile
example.com {
root * /var/www/example
file_server
}
The root * /var/www/example directive sets the document root for all requests. The * is a matcher that matches every request; it can be omitted when the next argument is a path. The file_server directive enables the static file server, which serves the files from the root path . The site address example.com triggers automatic HTTPS: Caddy obtains a certificate for the domain and serves the site over HTTPS, redirecting HTTP requests to the HTTPS URL.
For local development, the site address can be localhost or a raw IP. Caddy uses its internal certificate authority for these addresses, and the root certificate is installed into the system trust store on first use . The http:// scheme disables automatic HTTPS entirely, which is useful for testing behind a load balancer that already terminates TLS.
# Local development with internal CA
localhost {
root * /var/www/dev
file_server
}
# Explicit HTTP, no TLS
http://internal.local {
reverse_proxy 127.0.0.1:3000
}
The tls internal directive forces the internal CA for a site that would otherwise use a public certificate. The tls directive can also be used to specify custom certificate files or to configure DNS challenge for wildcard certificates .
b. The reverse proxy directive
The reverse_proxy directive forwards requests to a backend server. It is the directive that turns Caddy into an application gateway, and it is the most common use case after static file serving.
# Proxy all requests to a backend
api.example.com {
reverse_proxy localhost:5000
}
# Proxy only /api/* and serve static files for everything else
example.com {
root * /var/www
reverse_proxy /api/* localhost:5000
file_server
}
The first block proxies every request to the backend on port 5000. The second block uses a path matcher to proxy only requests that begin with /api/, and serves the rest from the static file root . The order of the directives matters: reverse_proxy is evaluated before file_server in Caddy’s default directive order, so the /api/* requests are handled by the proxy and the remaining requests fall through to the file server .
The handle_path directive strips a path prefix before proxying, which is useful when the backend does not expect the prefix. The handle directive preserves the prefix, which is correct when the application is configured to serve from a subpath .
example.com {
handle_path /app1/* {
reverse_proxy localhost:5001
}
handle_path /app2/* {
reverse_proxy localhost:5002
}
handle {
respond "Not found" 404
}
}
The handle_path blocks strip /app1/ and /app2/ before forwarding to the respective backends. The final handle block catches any request that does not match the earlier prefixes and returns a 404 response .
c. Validation, reload, and the admin API
The caddy validate command parses the Caddyfile and reports syntax errors without affecting the running server. The caddy reload command applies the configuration without dropping connections. The systemctl reload caddy command is the systemd equivalent .
# Validate the configuration
sudo caddy validate --config /etc/caddy/Caddyfile
# Output: Valid configuration
# Reload without dropping connections
sudo systemctl reload caddy
# Or: sudo caddy reload --config /etc/caddy/Caddyfile
The caddy fmt --overwrite /etc/caddy/Caddyfile command formats the file consistently, which catches indentation and brace errors early . The caddy adapt command shows the JSON configuration that the Caddyfile produces, which is useful for debugging routing mistakes .
Caddy also exposes an admin API on port 2019. The API can be used to load a JSON configuration at runtime, but mixing the API and the Caddyfile is a common source of confusion. A reload from the Caddyfile overwrites whatever the API applied, and the change appears to disappear without an error . The recommendation is to choose one source of truth per environment: the Caddyfile for file-based configuration, or the API for dynamic configuration, but not both.
Complete Example Session
# ============================================
# PART 1: INSTALL CADDY
# ============================================
# Debian / Ubuntu: add the official repository, then install
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
# ============================================
# PART 2: CREATE THE SITE DIRECTORY
# ============================================
sudo mkdir -p /var/www/example
echo "<h1>Hello from Caddy</h1>" | sudo tee /var/www/example/index.html
# ============================================
# PART 3: WRITE THE CADDYFILE
# ============================================
sudo tee /etc/caddy/Caddyfile > /dev/null <<'EOF'
example.com {
root * /var/www/example
file_server
}
EOF
# ============================================
# PART 4: VALIDATE THE CONFIGURATION
# ============================================
sudo caddy validate --config /etc/caddy/Caddyfile
# Output: Valid configuration
# ============================================
# PART 5: RELOAD CADDY
# ============================================
sudo systemctl reload caddy
# ============================================
# PART 6: CHECK THE SERVICE STATUS
# ============================================
sudo systemctl status caddy
# Output: active (running)
# ============================================
# PART 7: TEST THE SITE
# ============================================
curl -I https://example.com
# Output: HTTP/2 200
# The certificate is automatically provisioned by Let's Encrypt
# ============================================
# PART 8: ADD A REVERSE PROXY
# ============================================
sudo tee -a /etc/caddy/Caddyfile > /dev/null <<'EOF'
api.example.com {
reverse_proxy localhost:5000
}
EOF
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
# ============================================
# PART 9: CHECK THE LOGS
# ============================================
sudo journalctl -u caddy -n 20
# Output shows certificate provisioning and request handling
# ============================================
# PART 10: FORMAT THE CADDYFILE
# ============================================
sudo caddy fmt --overwrite /etc/caddy/Caddyfile
# The file is rewritten with consistent indentation
The ten parts demonstrate the complete Caddy setup: installation, content creation, Caddyfile configuration, validation, reload, service verification, HTTPS testing, reverse proxy addition, log inspection, and formatting. The critical step is Part 4: caddy validate catches syntax errors before the reload, and the running configuration remains in effect if the validation fails.
Quick Reference
Caddyfile Directives
| Directive | Purpose | Example |
|---|---|---|
root | Set the document root | root * /var/www |
file_server | Serve static files | file_server |
reverse_proxy | Forward requests to a backend | reverse_proxy localhost:5000 |
redir | HTTP redirect | redir https://example.com{uri} |
handle | Path-based routing (preserves prefix) | handle /api/* { ... } |
handle_path | Path-based routing (strips prefix) | handle_path /app/* { ... } |
tls | Override automatic TLS | tls internal |
encode | Compression | encode gzip |
Service Commands
| Command | Purpose |
|---|---|
caddy validate --config /etc/caddy/Caddyfile | Validate syntax |
caddy fmt --overwrite /etc/caddy/Caddyfile | Format the file |
caddy reload --config /etc/caddy/Caddyfile | Reload without systemd |
systemctl reload caddy | systemd reload |
systemctl status caddy | Check service state |
journalctl -u caddy -f | Follow logs |
Automatic HTTPS Behavior
| Site Address | TLS Behavior |
|---|---|
example.com | Automatic public certificate |
localhost | Internal CA certificate |
http://example.com | No TLS |
*.example.com | Wildcard certificate (requires DNS challenge) |
Best Practices
โ Do This:
# Use a public hostname for automatic HTTPS
example.com {
root * /var/www
file_server
} # โ
# Use handle_path to strip prefixes for backends
handle_path /api/* {
reverse_proxy localhost:5000
} # โ
# Use http:// for local development behind a TLS terminator
http://internal.local {
reverse_proxy 127.0.0.1:3000
} # โ
# Validate before reloading
caddy validate --config /etc/caddy/Caddyfile # โ
# Format the file for consistency
caddy fmt --overwrite /etc/caddy/Caddyfile # โ
โ Don’t Do This:
# Forget to validate
systemctl reload caddy # Syntax error prevents reload # โ
# Mix the admin API and the Caddyfile
# The Caddyfile reload overwrites the API config # โ
# Use a public hostname without DNS pointing to the server
# The ACME challenge fails and the certificate is not issued # โ
# Expect a wildcard certificate without DNS challenge
# Let's Encrypt requires DNS-01 for wildcards # โ
# Use handle instead of handle_path when the backend expects no prefix
# The backend receives the full path and may 404 # โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Certificate not issued | DNS does not point to the server, or ports 80/443 blocked | Verify DNS and firewall rules |
| 502 Bad Gateway | Backend not running or wrong address | curl the backend directly |
| 404 on valid paths | Wrong site block or root path | Check the site address and root |
| Redirect loop | Backend also redirects to HTTPS | Check the backend configuration |
| Configuration not applied | Validation failed or API/Caddyfile conflict | caddy validate; choose one config source |
| Wildcard certificate fails | DNS challenge not configured | Add the DNS plugin and tls { dns ... } |
Real-World Examples
1. Static file server
example.com {
root * /var/www
file_server
}
2. Reverse proxy
api.example.com {
reverse_proxy localhost:5000
}
3. Path-based routing
example.com {
handle_path /api/* {
reverse_proxy localhost:5000
}
handle {
root * /var/www
file_server
}
}
4. Local development
localhost {
root * /var/www/dev
file_server
}
5. HTTP-only behind a load balancer
http://internal.local {
reverse_proxy 127.0.0.1:3000
}
6. Redirect www to apex
www.example.com {
redir https://example.com{uri}
}
7. Validate configuration
caddy validate --config /etc/caddy/Caddyfile
8. Format the Caddyfile
caddy fmt --overwrite /etc/caddy/Caddyfile
9. Reload the service
systemctl reload caddy
10. Check logs
journalctl -u caddy -n 50
Visual
Automatic HTTPS Flow
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ A HOSTNAME IN THE SITE BLOCK TRIGGERS AUTOMATIC HTTPS โ
โ โ
โ example.com { โ
โ root * /var/www โ
โ file_server โ
โ } โ
โ โ โ
โ โผ โ
โ Caddy detects a public hostname โ
โ โ โ
โ โโโ Obtains certificate from Let's Encrypt/ZeroSSL โ
โ โโโ Serves the site over HTTPS โ
โ โโโ Redirects HTTP to HTTPS โ
โ โโโ Renews before expiry โ
โ โ
โ No tls directives required. โ
โ No Certbot timer. โ
โ No manual renewal. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Caddyfile Structure
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ SITE BLOCKS AND DIRECTIVES โ
โ โ
โ example.com { โ Site address โ
โ root * /var/www โ Directive with matcher โ
โ file_server โ Directive โ
โ } โ
โ โ
โ api.example.com { โ Second site block โ
โ reverse_proxy localhost:5000 โ
โ } โ
โ โ
โ The address determines the hostname and the TLS behavior. โ
โ The directives inside the block handle the requests. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
handle vs handle_path
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ PREFIX PRESERVATION โ
โ โ
โ handle_path /api/* { โ
โ reverse_proxy localhost:5000 โ
โ } โ
โ Request: /api/users โ backend receives /users โ
โ (prefix stripped) โ
โ โ
โ handle /api/* { โ
โ reverse_proxy localhost:5000 โ
โ } โ
โ Request: /api/users โ backend receives /api/users โ
โ (prefix preserved) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Configuration file | /etc/caddy/Caddyfile |
| Automatic HTTPS | Triggered by a public hostname |
| Certificate authority | Let’s Encrypt, ZeroSSL |
| Local development | Internal CA for localhost or raw IP |
| Static file serving | root + file_server |
| Reverse proxy | reverse_proxy |
| Path routing | handle (preserves), handle_path (strips) |
| Validation | caddy validate --config /etc/caddy/Caddyfile |
| Reload | systemctl reload caddy |
| Formatting | caddy fmt --overwrite |
| Admin API port | 2019 |
Key takeaways:
- Caddy’s automatic HTTPS eliminates the TLS configuration step. A site block with a public hostname obtains a certificate, serves HTTPS, redirects HTTP, and renews automatically. No
ssl_certificate, no Certbot timer, no renewal configuration . - The Caddyfile is a compact, directive-based format. A site address followed by a block of directives.
rootandfile_serverfor static files,reverse_proxyfor backends,handleandhandle_pathfor path routing . handle_pathstrips the prefix;handlepreserves it. The choice depends on whether the backend expects to receive the path prefix. Most applications that are not designed for a subpath requirehandle_path.- Validate before reloading.
caddy validatecatches syntax errors before the configuration is applied. Thecaddy fmtcommand catches indentation and brace errors early . - The admin API and the Caddyfile are separate configuration sources. Mixing them causes changes to disappear silently. Choose one source of truth per environment .
- Wildcard certificates require DNS challenge. Let’s Encrypt requires the DNS-01 challenge for wildcard domains. The DNS plugin for the provider must be installed, and the
tls { dns ... }directive configures it .
Remember: Caddy is the web server that treats HTTPS as the default. A site block with a hostname is enough to obtain a certificate, serve the site, and redirect HTTP. The Caddyfile is simpler than the Nginx and Apache equivalents, but it covers the same deployment patterns: static files, reverse proxy, and path routing. Validate with caddy validate, format with caddy fmt, reload with systemctl reload caddy, and test with curl. The certificate is managed automatically; the administrator’s job is to point the DNS, open the firewall, and write the directives that route the requests.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!