--- type: reference title: Caddyfile Syntax description: Caddy Caddyfile reference — directive and matcher syntax, named matchers, directive sorting, and the handle/route order rule. Grounded in caddyserver.com (fetched 2026-08-28). resource: https://caddyserver.com/docs/caddyfile tags: - caddy - caddyfile - reverse-proxy verified: 2026-08-28 --- # Caddyfile Syntax A Caddyfile defines sites. Each site block starts with an address (a host or a port). Directives inside the block control how Caddy serves requests. Matchers classify requests. This page records the syntax that trips people up, grounded in the official docs. Sources: [caddyfile-tutorial](https://caddyserver.com/docs/caddyfile-tutorial), [matchers](https://caddyserver.com/docs/caddyfile/matchers), [rewrite](https://caddyserver.com/docs/caddyfile/directives/rewrite), [handle](https://caddyserver.com/docs/caddyfile/directives/handle), [forward_auth](https://caddyserver.com/docs/caddyfile/directives/forward_auth), [reverse_proxy](https://caddyserver.com/docs/caddyfile/directives/reverse_proxy), [concepts](https://caddyserver.com/docs/caddyfile/concepts). ## Directive and matcher syntax A directive takes an optional matcher as its **first argument**: ``` directive [] ``` The matcher token is one of three forms: * `*` — the wildcard (matches all requests). * `/path` — a path (must start with `/`). * `@name` — a named matcher. The matcher follows the directive name. A leading `@name` before a directive name is parsed as a **matcher definition**, not a use (see below). ## Named matchers A named matcher is defined outside any directive and reused: ``` @name { } ``` If the set has one matcher, a one-liner works: ``` @post method POST ``` Use the matcher as the first argument of a directive: ``` reverse_proxy @post localhost:6001 ``` **Pitfall:** `@name ` (the `@name` before the directive) is parsed as a one-liner **definition** of `@name`. Writing it twice re-defines the matcher and Caddy rejects the config with `matcher is defined more than once`. Always put the matcher after the directive: `rewrite @name /to`, not `@name rewrite /to`. ## Path matching Path matching is an **exact** match by default. Append `*` for a prefix match: * `/foo*` matches `/foo`, `/foo/`, and `/foobar`. * `/foo/*` matches `/foo/` and `/foo/anything`, but not `/foo` or `/foobar`. A `handle` inline matcher is a **single token**. To match several paths, use a named matcher: ``` @data path /a.json /b.js handle @data { ... } ``` ## Directive sorting (the key rule) Caddy **sorts** top-level directives by a fixed canonical order. It does not run them in file order. The relevant order (partial): `vars`, `root`, `redir`, then `rewrite`, `try_files`, `uri`, then `basic_auth`, then `file_server`. **Consequence:** a top-level `rewrite` runs **before** a top-level `basic_auth`. If the rewrite changes the path, the path-based auth matcher sees the **rewritten** path, not the original. This silently applies the wrong auth block. **Fix:** wrap the ordered directives in `handle` or `route`. These blocks run their contents in **file order** (no re-sort). ## handle vs route * `handle [] { ... }` — mutually exclusive. Only the **first** matching `handle` runs. A `handle` with no matcher is the catch-all. * `route [] { ... }` — runs its contents as one unit, in file order, and the request continues to later top-level directives. Use `handle` for mutually exclusive branches (for example, per-path pages). Use `route` when several directives must all run in order. ## rewrite ``` rewrite [] ``` `` is the new URI (path or query). If `` starts with `/`, Caddy 2.8.0+ parses it directly. Before 2.8.0, a leading `/` was confused with a path matcher, so a `*` disambiguator was needed: `rewrite * /index.html`. `try_files ` rewrites to the first listed file that exists. It is the clean way to serve a named file for a path prefix: ``` try_files /admin.html file_server ``` ## file_server and root ``` root * /srv/site file_server ``` `root` sets the site root. `file_server` serves files from the root by appending the request path. The `*` is the wildcard matcher (it disambiguates the path argument). ## basic_auth ``` basic_auth [] { ... } ``` The default hash algorithm is bcrypt. A block may list several users; any of them is accepted. `basicauth` is deprecated in favor of `basic_auth`, but both work. ## forward_auth `forward_auth` is Caddy's auth gate for an external identity provider (OIDC, Authelia, Authentik, Tailscale, ...). It clones the incoming request, sends it to an auth upstream, and only proxies to the app if the upstream answers `2xx`. ``` forward_auth [] [] { uri copy_headers trusted_proxies [private_ranges] } ``` * `` — the auth gateway address (positional, required). * `uri ` — the path the cloned request hits on the gateway (the verification endpoint, e.g. `/outpost.goauthentik.io/auth/caddy`). * `copy_headers ` — header fields to copy from the gateway's `2xx` response onto the proxied request (e.g. `X-Authentik-Username`). * All `reverse_proxy` subdirectives pass through, including `trusted_proxies`. * `trusted_proxies` accepts CIDRs or the keyword `private_ranges` (all private ranges). It controls which sources Caddy trusts for `X-Forwarded-*`. **Pitfall — the outpost path:** an embedded outpost (Authentik) serves its own login UI at a path (e.g. `/outpost.goauthentik.io/*`). That path must be proxied to the gateway **before** `forward_auth` runs, or the login page is itself gated. Wrap both in a `route` (file order), outpost first: ``` example.com { route { reverse_proxy /outpost.goauthentik.io/* authentik:9000 forward_auth authentik:9000 { uri /outpost.goauthentik.io/auth/caddy copy_headers X-Authentik-Username X-Authentik-Groups trusted_proxies private_ranges } reverse_proxy app:8080 } } ``` The `reverse_proxy` on the outpost path is terminal: a matched request proxies to the gateway and the route stops. Non-outpost paths fall through to `forward_auth`, then the app. ## Worked example: per-path auth with shared data Serve `/admin/` (operator only) and `/` (family), where both pages fetch the same data files. The browser reuses the page's credential for the data fetches, so the data block must accept **both** users. ``` example.com { root * /srv/site # Operator-only page. Auth is checked on the original path, before any # rewrite, so the path matcher is reliable. handle /admin* { basic_auth { frank $2a$14$ } try_files /admin.html file_server } # Shared data + assets: the family OR the operator. @data path /services.json /status.json /app.js /style.css handle @data { basic_auth { dank $2a$14$ frank $2a$14$ } file_server } # Everything else: the family. handle { basic_auth { dank $2a$14$ } try_files /index.html file_server } } ``` `handle` blocks run in file order and are mutually exclusive (first match wins). Put the specific `handle /admin*` before the catch-all `handle`. ## Validate before you deploy Run `caddy validate --config --adapter caddyfile` before loading a Caddyfile. A placeholder hash (not a real bcrypt hash) fails validation with `base64-decoding password`. Render the real hashes first, then validate.