1. Quick Start
Caddy is an extensible server platform written in Go that serves HTTP, manages TLS certificates automatically, and acts as a reverse proxy. It eliminates the complexity of HTTPS configuration by obtaining and renewing certificates from Let's Encrypt and ZeroSSL without any manual setup.
Key features:
- Automatic HTTPS — TLS certificates obtained and renewed automatically via ACME
- Caddyfile — human-readable configuration format that adapts to native JSON
- Reverse proxy — built-in load balancing, health checks, retries, and streaming
- File server — static file serving with directory browsing
- HTTP/1.1, HTTP/2, and HTTP/3 (QUIC) support
- Admin API — live configuration changes without downtime via REST API on
localhost:2019 - Modular architecture — extend with plugins at compile time
- Cross-platform — Linux, macOS, Windows, FreeBSD (amd64, arm64, arm, s390x, ppc64le, riscv64)
Prerequisites: None for pre-built binaries. Go 1.25+ for building from source.
Install and run in 30 seconds:
# Install (Debian/Ubuntu)
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
Minimal Caddyfile (serve current directory over HTTPS):
localhost {
file_server browse
}
caddy run
... INFO serving initial configuration
Visit https://localhost — Caddy automatically provisions a locally-trusted TLS certificate.
Tip
For the fastest start, just run
caddy file-server --browsein any directory — no config file needed. Caddy will serve files from the current directory on port 2015.
What to try next:
- Set up a reverse proxy: see "Reverse Proxy" in Core Usage
- Configure automatic HTTPS for a domain: see "Automatic HTTPS" in Core Usage
- Learn the Caddyfile syntax: see Configuration
- See the full Admin Guide for architecture, deployment, and security details
2. Installation
System Requirements
| Requirement | Minimum | Recommended |
|---|---|---|
| OS | Linux, macOS, Windows, FreeBSD | Linux (systemd) |
| Architecture | amd64, arm64, armv5/v6/v7, s390x, ppc64le, riscv64 | amd64 or arm64 |
| Go (source builds) | 1.25.0 | Latest stable |
| Memory | 64 MB | 256 MB+ |
| Ports | 80 (HTTP), 443 (HTTPS) for automatic TLS | Also 443/UDP for HTTP/3 |
Installation Methods
Debian/Ubuntu (APT)
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
Fedora/RHEL (DNF)
dnf install 'dnf-command(copr)'
dnf copr enable @caddy/caddy
dnf install caddy
macOS (Homebrew)
brew install caddy
Windows (Scoop)
scoop install caddy
Docker
docker run -d -p 80:80 -p 443:443 -p 443:443/udp \
-v caddy_data:/data -v caddy_config:/config \
caddy:latest
From Source
git clone https://github.com/caddyserver/caddy.git
cd caddy/cmd/caddy
go build
Important
On Linux, Caddy needs permission to bind to ports 80 and 443. If not running as root, use:
sudo setcap cap_net_bind_service=+ep $(which caddy). Remember to re-run this after upgrading the binary.
Post-Install Verification
caddy version
v2.11.1 h1:abc123...
caddy environ
caddy.HomeDir=/root
caddy.AppDataDir=/root/.local/share/caddy
caddy.AppConfigDir=/root/.config/caddy
caddy.ConfigAutosavePath=/root/.config/caddy/autosave.json
caddy.Version=v2.11.1
runtime.GOOS=linux
runtime.GOARCH=amd64
...
Tip
The
caddy environcommand shows exactly which directories Caddy uses for data and configuration on your system. This is invaluable for debugging path issues.
Building with Plugins (xcaddy)
Caddy's standard distribution covers common use cases, but you can add third-party modules using xcaddy:
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy build --with github.com/caddy-dns/cloudflare
The caddy add-package and caddy remove-package commands can also add/remove modules to an existing binary without xcaddy:
caddy add-package github.com/caddy-dns/cloudflare
caddy remove-package github.com/caddy-dns/cloudflare
Warning
The
upgrade,add-package, andremove-packagecommands are experimental and replace the running binary. Always keep a backup. Use--keep-backupto preserve the old binary automatically.
3. Configuration
Caddy accepts configuration in two formats: Caddyfile (human-friendly) and native JSON (machine-friendly). The Caddyfile is automatically adapted to JSON before being applied.
Caddyfile Basics
A Caddyfile consists of site blocks that define how to handle requests for specific addresses:
# Serve static files for example.com
example.com {
root * /var/www/html
file_server
}
# Reverse proxy for api.example.com
api.example.com {
reverse_proxy localhost:8080
}
Key syntax rules:
| Element | Syntax | Example |
|---|---|---|
| Site address | Domain, IP, or port | example.com, localhost:8080, :443 |
| Directive | Keyword inside site block | file_server, reverse_proxy, respond |
| Matcher | @name definition or inline path |
@api path /api/* |
| Placeholder | {http.request.*} or shorthand {host} |
respond "Host: {host}" |
| Global options | Block at the top with { } |
{ admin off } |
| Snippet | (name) { ... } |
(common) { encode gzip } |
| Import | import <file_or_snippet> |
import common |
| Environment variable | {env.VAR} or {$VAR} |
{env.SITE_ROOT} |
| Comment | # |
# This is a comment |
| Heredoc | <<TAG ... TAG |
Multi-line values |
Global Options Block
The global options block must appear first in the Caddyfile (before any site blocks) and has no site address:
{
# TLS email for ACME account
email [email removed]
# Admin API settings
admin localhost:2019
# Default bind address
default_bind 0.0.0.0
# Logging
debug
# ACME settings
acme_dns <provider> ...
# Skip installing the CA root in system trust store
skip_install_trust
# HTTP and HTTPS ports
http_port 80
https_port 443
# Graceful shutdown period
grace_period 10s
# Server options for specific listeners
servers :443 {
protocols h1 h2 h3
}
}
Note
Not all options are listed above. See the Admin Guide for a complete configuration reference of all global options and server settings.
Using JSON Configuration
Caddy's native format is JSON. You can view the JSON equivalent of any Caddyfile with caddy adapt:
caddy adapt --config Caddyfile --pretty
You can also configure Caddy entirely via the admin API:
curl localhost:2019/load \
-H "Content-Type: application/json" \
-d @caddy.json
Environment Files
Caddy can load environment variables from .env files using the --envfile flag:
caddy run --envfile .env
The env file uses KEY=VALUE format. Lines starting with # are comments. Existing environment variables are not overwritten.
4. Core Usage
Running Caddy
Foreground (Recommended)
caddy run --config Caddyfile
Caddy runs in the foreground, blocking the terminal. Use Ctrl+C to stop. This is the recommended way to run Caddy, especially with service managers like systemd.
Background
caddy start --config Caddyfile
Successfully started Caddy (pid=12345) - Caddy is running in the background
caddy stop
Warning
On Windows,
caddy startkeeps the child process attached to the terminal. Closing the terminal forcefully stops Caddy. Prefercaddy runwith a service manager instead.
Reloading Configuration
caddy reload --config Caddyfile
This sends the new configuration to the running instance via the admin API. The reload is graceful — active connections are not dropped.
Use --force to force a reload even if the config hasn't changed:
caddy reload --config Caddyfile --force
Resuming Previous Configuration
caddy run --resume
Caddy automatically saves its current configuration to autosave.json in the config directory. The --resume flag loads this file on startup instead of the specified config file.
Watching for Config Changes
caddy run --config Caddyfile --watch
Caution
The
--watchflag polls the config file every second and automatically reloads on changes. This is intended for local development only — it can cause unintended config changes in production.
Automatic HTTPS
Caddy automatically obtains and renews TLS certificates for any site address that looks like a hostname:
example.com {
respond "Hello, HTTPS!"
}
When Caddy starts with this config, it will:
- Obtain a certificate from Let's Encrypt (or ZeroSSL) via ACME
- Set up HTTP → HTTPS redirects on port 80
- Enable HTTP/2 and HTTP/3
- Renew the certificate before it expires
No extra configuration needed — this is the default behavior.
Note
Caddy automatically agrees to the ACME CA's terms of service when using automatic HTTPS. For domains using ACME, ports 80 and 443 must be publicly reachable (or use DNS challenge for private networks).
Disable automatic HTTPS per-site:
http://example.com {
respond "Plain HTTP only"
}
Prefixing with http:// disables automatic HTTPS for that site. You can also disable it globally:
{
auto_https off
}
Using local certificates for development:
localhost {
respond "Hello from localhost!"
}
For localhost and other local addresses, Caddy generates a self-signed certificate from its own internal CA and installs it in the system trust store (unless skip_install_trust is set).
Reverse Proxy
example.com {
reverse_proxy localhost:8080
}
Multiple backends with load balancing:
example.com {
reverse_proxy node1:8080 node2:8080 node3:8080 {
lb_policy round_robin
}
}
With health checks:
example.com {
reverse_proxy backend:8080 {
health_uri /health
health_interval 30s
health_timeout 5s
}
}
Proxying to Unix sockets:
example.com {
reverse_proxy unix//var/run/app.sock
}
Header manipulation:
example.com {
reverse_proxy backend:8080 {
header_up Host {upstream_hostport}
header_up X-Real-IP {remote_host}
header_down -Server
}
}
Tip
Caddy automatically adds
X-Forwarded-For,X-Forwarded-Proto, andX-Forwarded-Hostheaders to proxied requests. You don't need to configure these manually.
File Server
Serve static files:
example.com {
root * /var/www/html
file_server
}
Enable directory browsing:
example.com {
root * /var/www/files
file_server browse
}
Quick file server from the command line:
caddy file-server --root /var/www/html --listen :8080 --browse
Note
The
caddy file-serverconvenience command disables the admin API since it configures entirely from command-line flags.
Serve with SPA fallback (try_files):
example.com {
root * /var/www/app
try_files {path} /index.html
file_server
}
Respond (Quick Testing)
For quick responses without a file server or proxy:
example.com {
respond "Hello, world!" 200
}
From the command line:
caddy respond --listen :8080 "Service is up" 200
Request Matchers
Matchers control which requests a directive applies to:
example.com {
# Named matcher
@api {
path /api/*
method GET POST
}
reverse_proxy @api backend:8080
# Path matcher shorthand
reverse_proxy /ws/* websocket-server:9090
# All other requests
file_server
}
Common matcher types:
| Matcher | Syntax | Description |
|---|---|---|
path |
path /api/* |
Match request path |
method |
method GET POST |
Match HTTP method |
host |
host example.com |
Match hostname |
header |
header Content-Type application/json |
Match request header |
remote_ip |
remote_ip 192.168.1.0/24 |
Match client IP |
protocol |
protocol https |
Match protocol |
expression |
expression {http.request.uri}.contains("/api") |
CEL expression |
file |
file {path} |
Match if file exists |
not |
not path /secret/* |
Negate a matcher |
Request and Response Headers
example.com {
header {
# Add/set headers
X-Frame-Options DENY
X-Content-Type-Options nosniff
Strict-Transport-Security "max-age=31536000; includeSubDomains"
# Remove headers
-Server
-X-Powered-By
}
file_server
}
Encoding (Compression)
example.com {
encode gzip zstd
file_server
}
Caddy supports gzip, zstd (Zstandard), and brotli compression. It also supports serving pre-compressed files (.gz, .br, .zst extensions).
Rewrite and Redirect
example.com {
# Internal rewrite (URL changes internally, not visible to client)
rewrite /old-path /new-path
# External redirect (client sees 301/302)
redir /old-page /new-page 301
# URI manipulation
uri strip_prefix /api
uri replace /v1/ /v2/
}
Templates
Caddy can process templates in served files:
example.com {
templates
file_server
}
Templates use Go's text/template syntax and have access to request information, Markdown rendering (via Goldmark), and more.
Logging
example.com {
log {
output file /var/log/caddy/access.log {
roll_size 100MiB
roll_keep 5
roll_keep_for 720h
}
format json
level INFO
}
file_server
}
TLS Configuration
For fine-grained TLS control beyond automatic HTTPS:
example.com {
tls cert.pem key.pem
}
Using internal (self-signed) certificates:
internal.example.com {
tls internal
}
Client certificate authentication:
example.com {
tls {
client_auth {
mode require_and_verify
trust_pool file {
pem_file /path/to/client-ca.pem
}
}
}
}
Basic Authentication
example.com {
basicauth /admin/* {
admin $2a$14$hashOfPassword
}
file_server
}
Generate password hashes with:
caddy hash-password
Important
The
hash-passwordcommand is provided by thecaddyauthmodule included in the standard distribution. Passwords are hashed using bcrypt by default.
PHP with FastCGI
example.com {
root * /var/www/html
php_fastcgi localhost:9000
}
The php_fastcgi directive is a shortcut that configures try_files, reverse_proxy with FastCGI transport, and appropriate index handling for PHP.
HTTP/3 (QUIC)
HTTP/3 is enabled by default when HTTPS is active. No additional configuration is needed. Caddy listens on UDP port 443 for QUIC connections and advertises it via the Alt-Svc header.
Metrics (Prometheus)
{
servers {
metrics
}
}
Caddy exports Prometheus-compatible metrics at the admin API endpoint /metrics.
5. Common Tasks
Task: Serve a Static Website with HTTPS
Goal: Serve a static site at example.com with automatic HTTPS.
Prerequisites: DNS A record pointing example.com to your server IP; ports 80 and 443 open.
Steps:
-
Create the Caddyfile:
example.com { root * /var/www/example.com file_server } -
Start Caddy:
caddy run --config Caddyfile -
Verify:
curl -I https://example.com
If it fails: Check that DNS resolves correctly and that ports 80/443 are not blocked by a firewall.
Task: Set Up a Reverse Proxy with Load Balancing
Goal: Distribute traffic across multiple backend servers.
Steps:
-
Create the Caddyfile:
example.com { reverse_proxy node1:3000 node2:3000 { lb_policy least_conn health_uri /health health_interval 10s fail_duration 30s } } -
Reload:
caddy reload --config Caddyfile
Task: Redirect www to Non-www
www.example.com {
redir https://example.com{uri} permanent
}
example.com {
respond "Hello!"
}
Task: Serve Multiple Sites
site1.example.com {
root * /var/www/site1
file_server
}
site2.example.com {
root * /var/www/site2
file_server
}
Each site automatically gets its own TLS certificate.
Task: Restrict Access by IP
example.com {
@blocked remote_ip 10.0.0.0/8
respond @blocked "Forbidden" 403
reverse_proxy backend:8080
}
Task: Use Caddy as a Pure HTTPS Terminator
example.com {
reverse_proxy http://internal-service:8080
}
Caddy handles TLS termination and proxies plaintext HTTP to the backend.
Task: Use the Admin API to Change Config at Runtime
# View current configuration
curl localhost:2019/config/
# Update a specific path
curl -X PATCH localhost:2019/config/apps/http/servers/srv0/routes \
-H "Content-Type: application/json" \
-d '[{"handle":[{"handler":"static_response","body":"Updated!"}]}]'
# Load an entirely new configuration
curl localhost:2019/load \
-H "Content-Type: application/json" \
-d @new-config.json
# Stop Caddy
curl -X POST localhost:2019/stop
Tip
The admin API supports
GET,POST,PUT,PATCH, andDELETEmethods on/config/paths, allowing fine-grained updates to any part of the running configuration without full reloads.
Task: Export and Import TLS Storage
# Export certificates and keys
caddy storage export --config Caddyfile --output backup.tar
# Import to a new instance
caddy storage import --config Caddyfile --input backup.tar
# Pipe between instances
caddy storage export --config old.json --output - | \
caddy storage import --config new.json --input -
Warning
The
storagecommand is experimental. When using file system storage, run the command as the user that owns the storage directory.
Task: Validate Your Configuration
caddy validate --config Caddyfile
Valid configuration
Task: Format Your Caddyfile
# Preview formatted output
caddy fmt Caddyfile
# Overwrite in place
caddy fmt --overwrite Caddyfile
# Show diff
caddy fmt --diff Caddyfile
Task: Set Up a Development Server with Auto-Reload
caddy run --config Caddyfile --watch
Caddy polls the Caddyfile every second and reloads automatically on changes.
Task: Run Caddy as a systemd Service
After installing via the package manager, Caddy comes with systemd units:
sudo systemctl enable caddy
sudo systemctl start caddy
Edit your Caddyfile at /etc/caddy/Caddyfile, then reload:
sudo systemctl reload caddy
Task: List Installed Modules
caddy list-modules
admin.api.load
admin.api.metrics
caddy.adapters.caddyfile
...
Standard modules: 100
Use --json for machine-readable output, or --skip-standard to show only plugins:
caddy list-modules --skip-standard --versions
6. Troubleshooting
Diagnostic Decision Tree
Caddy won't start
├── Port already in use? → Check with `ss -tlnp | grep :443`
├── Permission denied on port 80/443? → Use setcap or run as root
├── Config error? → Run `caddy validate --config Caddyfile`
└── Certificate error? → Check DNS, firewall, and ACME account
HTTPS not working
├── Certificate not issued? → Check ACME logs, DNS, port 80 reachability
├── HTTP redirect not working? → Check `auto_https` setting
└── Mixed content warnings? → Ensure all assets use HTTPS URLs
Reverse proxy errors
├── 502 Bad Gateway? → Backend is down or unreachable
├── Timeout? → Adjust timeout settings in reverse_proxy
└── Wrong headers? → Check header_up/header_down configuration
Error: permission denied when binding to port 80 or 443
Cause: Linux requires elevated privileges to bind to ports below 1024.
Solution:
sudo setcap cap_net_bind_service=+ep $(which caddy)
Prevention: Use the package manager installation, which configures this automatically via systemd.
Error: config is not valid JSON
Cause: You passed a Caddyfile or other non-JSON config without specifying an adapter.
Solution:
caddy run --config Caddyfile --adapter caddyfile
Or simply name your file Caddyfile — Caddy detects it automatically.
Prevention: Caddy auto-detects Caddyfile format when the file is named Caddyfile or has a .caddyfile extension.
Error: TLS certificate not obtained
Cause: ACME challenge failed. Usually a DNS or firewall issue.
Solution:
- Ensure ports 80 and 443 are publicly reachable
- Verify DNS A/AAAA records resolve to your server
- Check Caddy logs for ACME-specific errors
- Consider using DNS challenge for private networks
Prevention: Test with caddy run in foreground to see real-time ACME logs.
Error: caddy responded with error: HTTP 400
Cause: Invalid configuration sent to the admin API during reload.
Solution:
caddy validate --config Caddyfile
Fix any validation errors, then reload again.
Error: socket activation problems with ACME (community issue #7525)
Cause: Socket activation (systemd fd passing) can conflict with ACME certificate acquisition in certain configurations.
Solution: If using socket activation, ensure the ACME challenge ports are not also passed as activated sockets. Consider falling back to direct port binding for ACME.
Error: ACME broken for .internal addresses (community issue #7147)
Cause: .internal TLDs are not publicly resolvable and cannot use standard ACME HTTP/TLS-ALPN challenges.
Solution: Use tls internal to generate self-signed certificates for .internal addresses, or configure a DNS challenge provider for private ACME servers.
Error: {block} ignored when imported from a separate file (community issue #7557)
Cause: Regression in Caddyfile import handling for block arguments in snippets loaded from external files.
Solution: If affected, inline the snippet in the main Caddyfile as a workaround, or upgrade to a version with the fix.
Error: neither HOME nor USERPROFILE environment variables are set
Cause: Caddy cannot determine where to store data (certificates, config) without a home directory.
Solution: Set the HOME (Linux/macOS), USERPROFILE (Windows), or the XDG variables (XDG_DATA_HOME, XDG_CONFIG_HOME, XDG_CACHE_HOME).
Error: auto_https redirects not working with multiple bind addresses (community issue #6226)
Cause: When multiple bind addresses are specified, automatic HTTP → HTTPS redirects may not be created for all addresses.
Solution: Explicitly configure redirect behavior or ensure bind addresses are consistent across HTTP and HTTPS.
Error: HTTP/3 connection drops (ERR_QUIC_PROTOCOL_ERROR) in Chrome
Cause: Various QUIC protocol edge cases can trigger connection resets in some browsers (community issue #7556).
Solution: If persistent, disable HTTP/3 for the affected server:
{
servers {
protocols h1 h2
}
}
Prevention: Keep Caddy updated — HTTP/3 stability improves with each release.
Error: Excessive memory growth with reverse_proxy
Cause: TLS failures to upstream backends may accumulate state (community issue #7665).
Solution: Ensure upstream TLS certificates are valid. Monitor memory usage and restart if needed.
7. FAQ
Q: How does Caddy handle HTTPS automatically?
Caddy uses the ACME protocol to obtain certificates from Let's Encrypt and ZeroSSL. It handles HTTP-01 and TLS-ALPN-01 challenges automatically, sets up HTTP → HTTPS redirects, and renews certificates before expiry. By using Caddy, you automatically agree to the CA's terms of service — this is intentional since Caddy often runs non-interactively.
Q: Can I use Caddy without automatic HTTPS?
Yes. Use http:// prefix in your site address to disable HTTPS for that site, or set auto_https off in the global options block.
Q: What is the admin API and how do I use it?
The admin API listens on localhost:2019 by default and allows you to view, modify, and reload configuration at runtime via REST requests. You can change the listen address with the CADDY_ADMIN environment variable or the admin global option. To disable it entirely: { admin off }.
Q: How do I use Caddy with Docker?
Mount a volume for /data (certificates/keys) and /config (autosave), expose ports 80, 443, and 443/udp (HTTP/3), and provide your Caddyfile. See the Docker Hub page for official images.
Q: What are placeholders and how do I use them?
Placeholders like {host}, {uri}, {remote_host}, {env.VAR} are dynamic values that Caddy resolves at request time. Use them in Caddyfile directives, headers, and responses. The full list is available in the replacer.go source module.
Q: How do I add third-party modules/plugins?
Use xcaddy build --with <module> to build a custom binary, or use the experimental caddy add-package <module> command to add modules to an existing binary.
Q: What is the difference between caddy run and caddy start?
caddy run runs in the foreground and blocks until stopped. caddy start spawns a background process and returns immediately. Use caddy stop to stop a backgrounded instance.
Q: How do I generate shell completions?
caddy completion bash > /etc/bash_completion.d/caddy # Bash
caddy completion zsh > "${fpath[1]}/_caddy" # Zsh
caddy completion fish > ~/.config/fish/completions/caddy.fish # Fish
caddy completion powershell > caddy.ps1 # PowerShell
Q: Does Caddy support WebSocket proxying?
Yes. reverse_proxy transparently handles WebSocket upgrade requests. No extra configuration is needed.
Q: Can Caddy act as its own certificate authority?
Yes. Caddy has a built-in PKI app that can issue certificates from a local CA. Use tls internal to use internally-issued certificates. You can manage the local CA with caddy trust (install root CA into system trust store) and caddy untrust (remove it).
Q: How do I use environment variables in my Caddyfile?
Use {env.VARIABLE_NAME} or {$VARIABLE_NAME} syntax. Load additional env vars from files with --envfile .env.
Q: What happens to active connections during a config reload?
Caddy performs graceful reloads. Active HTTP connections complete normally. Active WebSocket connections are maintained across configuration reloads for unrelated routes, though this behavior has known edge cases (community issue #7222).
Q: Where does Caddy store certificates?
By default, in $XDG_DATA_HOME/caddy (or ~/.local/share/caddy on Linux, ~/Library/Application Support/Caddy on macOS, %AppData%\Caddy on Windows). You can change this with the storage configuration.
Q: What is the NO_COLOR environment variable?
Setting NO_COLOR to any value disables color output in Caddy's console log encoder.