docs: rebuild KB in OKF v0.1 with live-verified facts
Copy of the legacy ~/.pi/agent/docs tree rebuilt as the command-center knowledge base: concept files promoted to path identity, index listings stripped of frontmatter, all wikilinks/relative links resolved, every checkable claim verified against the live fleet (Dankserver = Ryzen 7 7700X + RTX 3090; Strix Halo is Dankclaw), unverifiable claims tagged. kb-lint exits 0.
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
---
|
||||
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 [<matcher>] <args...>
|
||||
```
|
||||
|
||||
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 {
|
||||
<matchers...>
|
||||
}
|
||||
```
|
||||
|
||||
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 <directive>` (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 [<matcher>] { ... }` — mutually exclusive. Only the **first** matching
|
||||
`handle` runs. A `handle` with no matcher is the catch-all.
|
||||
* `route [<matcher>] { ... }` — 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 [<matcher>] <to>
|
||||
```
|
||||
|
||||
`<to>` is the new URI (path or query). If `<to>` 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 <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 [<matcher>] {
|
||||
<username> <bcrypt_hash>
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
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 [<matcher>] [<upstreams...>] {
|
||||
uri <to>
|
||||
copy_headers <fields...>
|
||||
trusted_proxies [private_ranges] <ranges...>
|
||||
}
|
||||
```
|
||||
|
||||
* `<upstreams...>` — the auth gateway address (positional, required).
|
||||
* `uri <to>` — the path the cloned request hits on the gateway (the
|
||||
verification endpoint, e.g. `/outpost.goauthentik.io/auth/caddy`).
|
||||
* `copy_headers <fields...>` — 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$<operator_hash>
|
||||
}
|
||||
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$<family_hash>
|
||||
frank $2a$14$<operator_hash>
|
||||
}
|
||||
file_server
|
||||
}
|
||||
|
||||
# Everything else: the family.
|
||||
handle {
|
||||
basic_auth {
|
||||
dank $2a$14$<family_hash>
|
||||
}
|
||||
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 <file> --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.
|
||||
@@ -0,0 +1,30 @@
|
||||
# Caddy
|
||||
|
||||
Caddy is the reverse proxy and TLS terminator for the homelab. A single caddy
|
||||
container on dankclaw (192.168.0.143) fronts every service on both hosts. It
|
||||
runs Caddy v2.11.4 from the custom `franksdank/caddy-porkbun` image
|
||||
(xcaddy-built with the Porkbun DNS-01 module, built locally on dankclaw).
|
||||
Since 2026-08-26 it is managed as a Podman Quadlet unit (`caddy.container`)
|
||||
installed to `~/.config/containers/systemd/`, providing boot-persistent,
|
||||
auto-restart service management.
|
||||
|
||||
* [[caddyfile|caddyfile]] — directive and matcher syntax, named matchers,
|
||||
directive sorting, and the `handle`/`route` order rule.
|
||||
|
||||
The Caddyfile lives in the serverAppConfigs repo at
|
||||
`dankclaw/Caddyfile`. `deploy.sh` renders basicauth hashes from the host `.env`
|
||||
(`REPLACE_WITH_HASH` → real bcrypt) into
|
||||
`/home/dank/Containers/caddy/Caddyfile`, which the container mounts read-only;
|
||||
Caddy's config file watcher (admin API, not disabled) hot-reloads on change.
|
||||
The Caddyfile joins the `dankclaw_default` compose network so container-name
|
||||
upstreams (authentik-server, grafana, etc.) still resolve.
|
||||
|
||||
TLS: Let's Encrypt wildcard certs via Porkbun DNS-01. The `acme_dns` block in
|
||||
the Caddyfile reads API keys from `/home/dank/Containers/caddy/env` (deployed
|
||||
from the repo's git-ignored `.env`). No inbound port 80/443 reachability is
|
||||
needed from the internet — DNS-01 works entirely from the LAN, where Pi-hole
|
||||
resolves `*.franksdank.cloud` names to dankclaw.
|
||||
|
||||
The porkbun DNS module source: [caddy-dns/porkbun](https://github.com/caddy-dns/porkbun).
|
||||
|
||||
* [Caddyfile Syntax](caddyfile.md) — 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).
|
||||
Reference in New Issue
Block a user