**Note** from Bead: Home Port Forward Hosting · [canonical source](https://redfish.acequia.io/guerin/.agents/7049c694-e45b-4116-a887-49e64885ac8e/2026-06-13/notes/02-caddy-as-ingress-router.md) · session 2026-06-13 · discussion: Talk: Home Port Forward Hosting
**Date:** 2026-06-13. Prompted by Stephen: "consider caddy as an ingress router. how scriptable is it compared to .htaccess or cloudflare's ingress router." Connects to the router-as-bead question in [bead-as-webdav-handler §8 Q7](https://redfish.acequia.io/guerin/.agents/fe7fbaf5-4c47-43b6-be1b-2f6ca15e8bfa/2026-06-11/notes/bead-as-webdav-handler.md) and [01-origin-as-animator.md](01-origin-as-animator.md).
## The three, on the axes that matter | Axis | `.htaccess` (Apache) | **Caddy** | Cloudflare ingress | |---|---|---|---| | Config form | per-dir directives (mod_rewrite regex) | Caddyfile **or** native **JSON**; config adapters | Dashboard / API; Rules expression lang; Tunnel YAML; Workers code | | Runtime reconfig | reloaded per-request (no restart), but only Apache's directive vocabulary | **Admin REST API on :2019** — POST/PATCH/DELETE live config, zero-downtime, no restart | global control plane, eventually-consistent push | | Per-request programmability | none (declarative match→rewrite; can shell out to CGI) | declarative core (`matchers`, `map`, `vars`, `rewrite`); **plugins** add Lua / WASM / `exec` | **Workers = full JS/TS/WASM per request** (V8 isolates, KV/DO state) | | Extension model | Apache modules (C), heavyweight | **Go plugin modules**, compiled in — Turing-complete handlers; Caddy is also an **embeddable Go library** | Workers (sandboxed JS/WASM); Rules use Wirefilter expressions | | TLS | external (certbot etc.) | **automatic ACME built in** | automatic (their edge) | | Ownership | self-hosted, Apache-bound | **self-hosted, you own the runtime** | their infra, their control plane | | State | stateless | stateless core (state via plugins/upstreams) | KV / Durable Objects / cache |
## Raw scriptability ranking (per-request programmability) Cloudflare **Workers** (real code) ≫ Caddy **+ plugins** (Go / Lua / WASM) > Caddy **core** (declarative + live Admin API) > Cloudflare **Rules** (expression language) > `.htaccess` (static rewrite directives). - `.htaccess` is the floor: pattern→action rules re-read per request, no decisions beyond matching request attributes, no API, no state. Its one trick (shell out to CGI) is slow and awkward. - Cloudflare Workers is the ceiling for *pure scriptability* — it's literally a program per request, at the edge, globally. The cost is that it runs on someone else's control plane: not self-sovereign, eventual-consistency config, vendor lock. - **Caddy's distinctive strength isn't a per-request scripting language** (nginx-njs/OpenResty-Lua have more of that out of the box). It's the combination: **native JSON config + a live Admin API + a Go plugin/library model**. You drive routing *from your own process*, reconfigure live with no restart, and own the whole thing.
## The point for *this* architecture For the acequia / animator vision the relevant question isn't "which has the cleverest per-request DSL" — it's **"which lets my own code own the routing, self-sovereignly, and reconfigure live?"** On that axis Caddy wins over both: - `.htaccess` can't (static, Apache-bound, no API). - Cloudflare can (Workers), but on *their* control plane — the opposite of self-sovereign. - Caddy's **Admin API** means the **animator/router-bead can push routes into Caddy at runtime** as beads come and go — exactly the [hey-you-its-me](https://redfish.acequia.io/guerin/.agents/ec4d09d8-7f06-41d3-80fe-80aeaedba680/2026-06-11/notes/00-concepts.md) ingress-routing/lease story, driven by your code, not a vendor's. **The cleaner split (recommended): don't push the smarts *into* Caddy — keep Caddy as the dumb TLS edge and put the programmable router in the animator.** ``` Internet ──HTTPS──> Caddy (TLS edge, ACME, :443) ──plain HTTP──> Node animator/router-bead (:3500) │ reconfigured live via Admin API │ the real ingress logic: │ only when the hostname set changes │ acequia.json eval, per-caller namespace, │ lease/leader, dock vs sync lane, handler.mjs ``` This is the §8-Q7 "is the node's ingress router itself a bead?" answer in concrete terms: **Caddy terminates TLS; the router-bead does the thinking.** Caddy's scriptability is then used at exactly one seam — the Admin API call that adds/removes a `hostname → localhost:port` route when a bead is mounted/unmounted (the `start-bead <remote-url>` / Plan-9-`mount` operation). Everything Turing-complete lives in the animator, where it belongs (cognition ladder, scope-gated efferent writes, `names.json` resolution), not smeared into proxy config. So: Caddy is *more* scriptable than `.htaccess` by a wide margin (live API + plugins + library), and *less* of a per-request programming environment than Cloudflare Workers — but it's the only one of the three whose programmability is **self-owned and live-reconfigurable from your own process**, which is the property this ecology actually needs. Use Workers-grade programmability *inside* the animator (your own code), not at the edge.
## Concrete Caddy levers (for when we build the router-bead) - **Admin API** `http://localhost:2019/config/` — GET current config; `PATCH`/`POST`/`DELETE` route fragments live. (Already used in this bead to confirm the `stephenguerin.live` route is loaded.) - **JSON config** is the source of truth; Caddyfile is sugar that compiles to it. Drive the JSON from code for dynamic routing. - **`reverse_proxy`** dynamic upstreams, health checks, load balancing across mesh origins. - **Plugins** when needed: `caddy-l4` (TCP/UDP), Lua/Starlark/WASM handler plugins, `caddy-exec`. Add only if the animator-owns-routing split leaves a real gap. - **Embed Caddy as a Go library** if we ever want one binary that is both TLS edge and animator — though the Node-animator-behind-Caddy split is simpler and language-aligned with the rest of the stack.
## References (bead cross-links) - Bead: As Webdav Handler · [canonical](https://redfish.acequia.io/guerin/.agents/fe7fbaf5-4c47-43b6-be1b-2f6ca15e8bfa/) - Bead: Hey You Its Me · [canonical](https://redfish.acequia.io/guerin/.agents/ec4d09d8-7f06-41d3-80fe-80aeaedba680/)