# Caddy — Web Server with Automatic HTTPS

> Practical guide to Caddy — automatic TLS certificates, Caddyfile syntax, reverse proxy, static file server and CLI, with examples for everyday use.

Source: https://www.jpkc.com/db/en/cheatsheets/web-servers/caddy/

<!-- PROSE:intro -->
Caddy is a modern web server written in Go that takes the most tedious part of running a website off your hands: it provisions and renews TLS certificates through Let's Encrypt or ZeroSSL fully automatically, with no certbot and no cron jobs to babysit. HTTP/2 and HTTP/3 are on by default, and the readable Caddyfile syntax gets you from an empty file to a working configuration in just a few lines. Whether you need a static file server, a reverse proxy or a PHP backend, Caddy covers the common tasks with clear directives. This guide walks you through the CLI commands and Caddyfile building blocks you reach for every day.
<!-- PROSE:intro:end -->

## Service Management (systemctl)

`systemctl start caddy` — Start the Caddy web server.

```bash
systemctl start caddy
```

`systemctl stop caddy` — Stop the Caddy web server.

```bash
systemctl stop caddy
```

`systemctl restart caddy` — Restart Caddy (briefly drops connections). Use reload for zero-downtime config changes.

```bash
systemctl restart caddy
```

`systemctl reload caddy` — Reload Caddy configuration with zero downtime. Caddy re-reads /etc/caddy/Caddyfile.

```bash
systemctl reload caddy
```

`systemctl status caddy` — Show Caddy service status, PID, and recent log output.

```bash
systemctl status caddy
```

`systemctl enable caddy` — Enable Caddy to start automatically on system boot.

```bash
systemctl enable caddy
```

`systemctl disable caddy` — Disable Caddy from starting automatically on boot.

```bash
systemctl disable caddy
```

## caddy CLI Commands

`caddy run` — Start Caddy in the foreground using Caddyfile in the current directory. Logs go to stdout.

```bash
caddy run
```

`caddy run --config <file>` — Start Caddy in the foreground with a specific config file.

```bash
caddy run --config /etc/caddy/Caddyfile
```

`caddy start` — Start Caddy as a background daemon.

```bash
caddy start
```

`caddy start --config <file>` — Start Caddy as a background daemon with a specific config file.

```bash
caddy start --config /etc/caddy/Caddyfile
```

`caddy stop` — Stop the running Caddy background daemon.

```bash
caddy stop
```

`caddy reload` — Reload the Caddy configuration with zero downtime. Applies changes from the Caddyfile.

```bash
caddy reload
```

`caddy reload --config <file>` — Reload Caddy with a specific config file.

```bash
caddy reload --config /etc/caddy/Caddyfile
```

`caddy validate --config <file>` — Validate a Caddyfile for syntax and configuration errors without starting the server.

```bash
caddy validate --config /etc/caddy/Caddyfile
```

`caddy fmt <file>` — Format (pretty-print) a Caddyfile in place according to canonical style.

```bash
caddy fmt /etc/caddy/Caddyfile
```

`caddy fmt --overwrite <file>` — Format and overwrite a Caddyfile in place.

```bash
caddy fmt --overwrite /etc/caddy/Caddyfile
```

`caddy version` — Show the installed Caddy version.

```bash
caddy version
```

`caddy list-modules` — List all available Caddy modules (directives, handlers, matchers, etc.).

```bash
caddy list-modules
```

`caddy upgrade` — Upgrade Caddy to the latest release in-place (replaces the binary).

```bash
caddy upgrade
```

`caddy adapt --config <file>` — Convert a Caddyfile to Caddy's native JSON configuration format.

```bash
caddy adapt --config Caddyfile
```

`caddy environ` — Print the environment variables Caddy will use at runtime.

```bash
caddy environ
```

## Caddyfile: Basic Structure

Minimal site block. Caddy automatically provisions a TLS certificate for the domain.

```bash
example.com {
    respond "Hello, World!"
}
```

Serve multiple domains from the same block (comma-separated).

```bash
example.com, www.example.com {
    # shared config
}
```

Global options block (must be the first block). Sets the ACME email, on-demand TLS, and other global settings.

```bash
{
    email admin@example.com
    on_demand_tls {
        ask http://localhost:9000/check
    }
}
```

Define a reusable config snippet and import it in site blocks.

```bash
(common) {
    encode gzip zstd
    header X-Frame-Options DENY
}
example.com {
    import common
}
```

Listen on a specific port without HTTPS (no TLS when using plain port addresses).

```bash
:8080 {
    respond "dev server"
}
```

Use Caddy's built-in internal CA for a locally-trusted TLS certificate (no internet required).

```bash
localhost {
    tls internal
    respond "local dev"
}
```

## Static File Server

Serve static files from a directory. The * matches all requests.

```bash
example.com {
    root * /var/www/html
    file_server
}
```

Serve static files with directory listing enabled.

```bash
example.com {
    root * /var/www/html
    file_server browse
}
```

SPA-mode: try the exact path, fall back to index.html for client-side routing.

```bash
example.com {
    root * /var/www/html
    try_files {path} /index.html
    file_server
}
```

Hide specific files or directories from being served.

```bash
file_server {
    hide .git
    hide *.secret
}
```

`caddy file-server --root ./public --listen :8080` — Instantly serve a local directory from the command line without a Caddyfile.

```bash
caddy file-server --root ./dist --listen :3000
```

## Reverse Proxy

Proxy all requests for a domain to a local backend on port 3000.

```bash
example.com {
    reverse_proxy localhost:3000
}
```

Proxy only requests matching a path prefix to a specific backend.

```bash
example.com {
    reverse_proxy /api/* localhost:4000
}
```

Load-balance across multiple backends using round-robin by default.

```bash
example.com {
    reverse_proxy backend1:8080 backend2:8080
}
```

Load-balance using the least-connections policy.

```bash
reverse_proxy localhost:3000 {
    lb_policy least_conn
}
```

Pass original host and client IP headers to the upstream backend.

```bash
reverse_proxy localhost:3000 {
    header_up Host {upstream_hostport}
    header_up X-Real-IP {remote_host}
    header_up X-Forwarded-For {remote_host}
}
```

Configure active health checks on the upstream backend.

```bash
reverse_proxy localhost:3000 {
    health_uri /healthz
    health_interval 10s
    health_timeout 2s
}
```

Proxy to an HTTPS upstream while skipping TLS verification (for internal self-signed certs).

```bash
reverse_proxy https://upstream.example.com {
    transport http {
        tls_insecure_skip_verify
    }
}
```

## Redirects & Rewrites

Permanently redirect www to the apex domain, preserving the request URI.

```bash
www.example.com {
    redir https://example.com{uri} permanent
}
```

Redirect all HTTP traffic to HTTPS. Caddy normally handles this automatically.

```bash
http://example.com {
    redir https://example.com{uri}
}
```

`redir /old-path /new-path permanent` — Permanently redirect a specific path to a new location (301).

```bash
redir /blog /news permanent
```

`redir /old-path /new-path temporary` — Temporarily redirect a path (302).

```bash
redir /sale /deals temporary
```

`rewrite /app/* /index.html` — Internally rewrite requests matching a path to another path (no browser redirect).

```bash
rewrite /app/* /index.html
```

`rewrite * /index.php{query}` — Rewrite all requests to index.php, preserving the query string (useful for PHP apps).

```bash
rewrite * /index.php{query}
```

## TLS / HTTPS

Caddy provisions a Let's Encrypt certificate automatically when given a public domain. No extra config required.

```bash
example.com {
    # TLS is automatic — no config needed
}
```

`tls admin@example.com` — Explicitly set the ACME registration email for this site block (overrides global setting).

```bash
tls admin@example.com
```

`tls /path/to/cert.pem /path/to/key.pem` — Use a custom (manually managed) TLS certificate and private key.

```bash
tls /etc/ssl/certs/example.crt /etc/ssl/private/example.key
```

`tls internal` — Use Caddy's built-in local CA to issue a certificate. Trusted automatically on the local machine.

```bash
localhost { tls internal }
```

Restrict TLS protocol versions and cipher suites.

```bash
tls {
    protocols tls1.2 tls1.3
    ciphers TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
}
```

Use DNS-01 ACME challenge (required for wildcard certs or servers not publicly accessible).

```bash
tls {
    dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}
```

`caddy trust` — Install Caddy's local root CA into the system and browser trust stores (for localhost TLS).

```bash
caddy trust
```

`caddy untrust` — Remove Caddy's local root CA from trust stores.

```bash
caddy untrust
```

## Headers & Compression

`encode gzip zstd` — Enable gzip and zstd response compression. Caddy chooses the best format the client supports.

```bash
encode gzip zstd
```

`encode zstd gzip` — Enable compression, preferring zstd over gzip.

```bash
encode zstd gzip
```

`header X-Frame-Options DENY` — Set a custom response header.

```bash
header X-Frame-Options DENY
```

`header -Server` — Remove a response header. The minus prefix deletes the header.

```bash
header -Server
```

Set multiple security headers and remove the Server header in one block.

```bash
header {
    X-Content-Type-Options nosniff
    X-Frame-Options DENY
    Referrer-Policy strict-origin-when-cross-origin
    -Server
}
```

`header Cache-Control "public, max-age=31536000, immutable"` — Set long-term caching headers for static assets.

```bash
header Cache-Control "public, max-age=31536000"
```

`header Access-Control-Allow-Origin *` — Allow all origins for CORS (Cross-Origin Resource Sharing).

```bash
header Access-Control-Allow-Origin *
```

## PHP & FastCGI

Serve a PHP application via PHP-FPM using a Unix socket. Combines FastCGI, try_files, and file_server.

```bash
example.com {
    root * /var/www/html
    php_fastcgi unix//run/php/php8.3-fpm.sock
    file_server
}
```

`php_fastcgi 127.0.0.1:9000` — Connect to PHP-FPM via TCP instead of a Unix socket.

```bash
php_fastcgi 127.0.0.1:9000
```

Complete WordPress setup with PHP-FPM, static files, and compression.

```bash
example.com {
    root * /var/www/wordpress
    php_fastcgi unix//run/php/php8.3-fpm.sock
    file_server
    encode gzip zstd
}
```

## Matchers & Routing

Route /api/* to a backend and everything else to the file server.

```bash
handle /api/* {
    reverse_proxy localhost:3000
}
handle {
    file_server
}
```

Like handle, but strips the matched path prefix before passing to the inner block.

```bash
handle_path /static/* {
    root * /var/www/static
    file_server
}
```

Define a named matcher with @name and reference it in a directive.

```bash
@api path /api/*
reverse_proxy @api localhost:3000
```

Match requests by header value and return a 403 for bots.

```bash
@bot header User-Agent *bot*
respond @bot 403
```

Combine matchers: apply basic auth only to /admin/* on a specific host.

```bash
@secure {
    host example.com
    path /admin/*
}
basicauth @secure {
    admin JDJhJDE0JG...
}
```

Use the 'not' matcher to redirect everyone except www to the www domain.

```bash
@notwww not host www.example.com
redir @notwww https://www.example.com{uri}
```

## Logging

`log` — Enable access logging with defaults (JSON format to stderr).

```bash
log
```

Write access logs to a file.

```bash
log {
    output file /var/log/caddy/access.log
}
```

Access log with automatic log rotation: max 100 MB per file, keep 5 files, max 30 days.

```bash
log {
    output file /var/log/caddy/access.log {
        roll_size 100mb
        roll_keep 5
        roll_keep_for 720h
    }
}
```

Use human-readable console format instead of JSON for log output.

```bash
log {
    format console
}
```

Log to stdout in JSON format with debug level (useful in containers).

```bash
log {
    output stdout
    format json
    level DEBUG
}
```

`journalctl -u caddy -f` — Follow Caddy's live log output via systemd journal.

```bash
journalctl -u caddy -f
```

`journalctl -u caddy --since "1 hour ago"` — Show Caddy logs from the last hour.

```bash
journalctl -u caddy --since "1 hour ago"
```

## Admin API

`curl -s http://localhost:2019/config/` — Get the current Caddy configuration as JSON via the admin API (default listen: localhost:2019).

```bash
curl -s http://localhost:2019/config/ | jq '.'
```

`curl -X POST http://localhost:2019/load -H 'Content-Type: text/caddyfile' --data-binary @Caddyfile` — Load a Caddyfile via the admin API (zero-downtime reload).

```bash
curl -X POST http://localhost:2019/load -H 'Content-Type: text/caddyfile' --data-binary @Caddyfile
```

`curl -X DELETE http://localhost:2019/config/` — Clear the current Caddy configuration via the admin API.

```bash
curl -X DELETE http://localhost:2019/config/
```

`curl -s http://localhost:2019/reverse_proxy/upstreams` — List all reverse proxy upstream backends and their health status.

```bash
curl -s http://localhost:2019/reverse_proxy/upstreams | jq '.'
```

Disable the admin API entirely in the global options block (for production hardening).

```bash
{
    admin off
}
```

Change the admin API listen address in the global options block.

```bash
{
    admin localhost:2020
}
```

## Practical Examples

Production-ready static site with compression and security headers.

```bash
example.com {
    root * /var/www/html
    encode gzip zstd
    file_server
    header -Server
    header X-Frame-Options DENY
}
```

Reverse proxy to a Node.js or other backend app with compression and hardened headers.

```bash
app.example.com {
    encode gzip zstd
    reverse_proxy localhost:3000
    header -Server
}
```

Standard PHP site with PHP-FPM, static file serving, and gzip.

```bash
example.com {
    root * /var/www/html
    encode gzip
    php_fastcgi unix//run/php/php8.3-fpm.sock
    file_server
}
```

Wildcard certificate via DNS challenge, routing subdomains to different backends.

```bash
*.example.com {
    tls {
        dns cloudflare {env.CF_TOKEN}
    }
    @app host app.example.com
    handle @app {
        reverse_proxy localhost:3000
    }
}
```

`caddy reverse-proxy --from :80 --to localhost:3000` — Instantly run a reverse proxy from the command line without a Caddyfile.

```bash
caddy reverse-proxy --from :443 --to localhost:3000
```

Return a static 200 for a health check endpoint, proxy everything else.

```bash
example.com {
    respond /healthz 200
    reverse_proxy localhost:3000
}
```

<!-- PROSE:outro -->
## Conclusion

Caddy raises the bar for comfortable web hosting: what means manual certificate management and pages of configuration on traditional servers, Caddy handles with sensible defaults and automatic HTTPS. To keep a new setup running smoothly, follow one simple routine — validate every change with `caddy validate` and tidy it with `caddy fmt` before you `reload`. Automatic HTTPS also needs ports 80 and 443 open and the domain publicly reachable, otherwise Caddy cannot complete the ACME challenge.

## Further Reading

- [Caddy – official documentation](https://caddyserver.com/docs/) – reference and manual
- [Caddy on GitHub](https://github.com/caddyserver/caddy) – source code and releases
- [Caddy (web server) – Wikipedia](https://en.wikipedia.org/wiki/Caddy_(web_server)) – background and history
<!-- PROSE:outro:end -->

## Related Commands

- [apache](https://www.jpkc.com/db/en/cheatsheets/web-servers/apache/) – the established, modular web server classic
- [certbot](https://www.jpkc.com/db/en/cheatsheets/web-servers/certbot/) – manage TLS certificates manually via Let's Encrypt
- [ferron](https://www.jpkc.com/db/en/cheatsheets/web-servers/ferron/) – a modern, lightweight web server alternative

