The Caddyfile is not Caddy's native configuration format. What is, how does the Caddyfile relate to it, and how does a running Caddy instance receive a new configuration?
answer
- JSON is native, the Caddyfile is sugar
- an adapter does the conversion at load
- adapt the file to see what it became
- reload is an API call, not a signal
- the endpoint is loopback and unauthenticated
basics
~20 sCaddy's native config is JSON. The Caddyfile is human-friendly input that a config adapter converts to that JSON before loading. A running instance takes new config through its admin API, which listens on localhost:2019 and applies changes gracefully without restarting.
solid answer
~50 sCaddy's real configuration is a JSON document describing apps, servers, routes and handlers. The Caddyfile is sugar: a config adapter converts it to that JSON at load time, and `caddy adapt --config Caddyfile --pretty` prints exactly what you would get — the single most useful debugging step when a Caddyfile does not behave as you expect. The running server exposes an **admin API**, by default on `localhost:2019`, and that API is how config changes actually land: `caddy reload` adapts your file and posts the result to `POST /load`, which provisions the new config and swaps it in gracefully, with no process restart and no dropped connections. You can also read and mutate parts of the config directly under `/config/...`, and tag objects with `@id` to address one piece without resending the whole document. The API has no authentication — it is protected only by being bound to loopback — so never expose it, and remember that `admin off` also disables `caddy reload`.
code
bash · 5 lines# See the JSON a Caddyfile actually becomes
caddy adapt --config Caddyfile --pretty
# Read the live configuration from the running server
curl -s localhost:2019/config/go deeper
Know that you normally write a Caddyfile, that Caddy converts it to JSON internally, and that caddy reload applies changes without stopping the server.
Explain the adapter step, name caddy adapt as the way to see the generated JSON, and describe the admin endpoint on localhost:2019 as what reload actually talks to.
Treat the admin API as a privileged control plane: know that it is unauthenticated behind a loopback binding, that failed provisioning leaves the old config serving, and when admin off is the right hardening.
Decide the change model — file-driven reloads versus a control plane patching config over the API — and set the boundary, authentication and audit story before anything is allowed to mutate a live edge tier.
## Two layers, and why the distinction matters Most web servers have exactly one configuration language, and it is the one you write. Caddy has two: 1. **JSON is the native config.** It is a structured document — an `apps` object containing, for HTTP, servers with listen addresses, routes, matchers and handlers. Every knob Caddy has is reachable there, because it is what the modules are actually configured from. 2. **The Caddyfile is an adapter's input.** A *config adapter* is a module that turns some other format into that JSON. The Caddyfile adapter is the default and by far the most used, but it is one of several, and it is not privileged in the runtime. The practical consequence is that the Caddyfile is deliberately lossy in the direction of brevity: it makes the common cases short by expanding one directive into several JSON constructs. `reverse_proxy` alone produces a route, a matcher, a handler and an upstream list. ## Seeing the conversion ```bash caddy adapt --config Caddyfile --pretty ``` This is the debugging move that separates people who use Caddy from people who have operated it. When a Caddyfile does something surprising — a matcher wider than you meant, directives ordered differently than written, a site block that swallowed a path — the adapted JSON shows the actual structure rather than your intent. Two companion commands live in the same family: `caddy validate` loads a config to check it without serving, and `caddy fmt` normalises Caddyfile whitespace. ## The admin API is the real control plane A running Caddy binds an administration endpoint, by default `localhost:2019`. It speaks a small REST-shaped API over the config document: - `POST /load` replaces the entire configuration. - `GET /config/...` reads the live config, or any subtree of it. - `POST`, `PUT`, `PATCH` and `DELETE` under `/config/...` mutate a subtree in place. - Objects can carry an `@id` field, which makes them addressable under `/id/...` so automation can target one upstream or one route without knowing its index. `caddy reload --config Caddyfile` is not a signal to the process. It runs the adapter and posts the resulting JSON to `/load` on the admin endpoint. That is why reloading a Caddy you started some other way still works, and why disabling the admin endpoint takes `caddy reload` away with it. ## What "graceful" means here On a config change Caddy provisions the new configuration, starts what it needs, and swaps over; listeners that are unchanged are kept rather than rebound, and connections being served under the old config are not severed. There is no process restart, so no dropped listeners and no cold start. If the new config fails to provision, the load fails and the old config keeps running — a broken config is a rejected API call, not an outage. ```bash curl -s localhost:2019/config/ | head curl -X POST localhost:2019/load \ -H 'Content-Type: application/json' \ -d @caddy.json ``` ## The security posture you must state The admin API is **unauthenticated by default**. Its protection is that it binds the loopback interface. Anything that can reach it can replace your entire server configuration — including where traffic is proxied to and what certificates are served. So: - never bind it to a routable address casually; if you must, put authenticated transport in front of it and treat it as a privileged control plane; - remember that in a container, "localhost" is the container's own loopback, so publishing port 2019 is the mistake to avoid; - `admin off` in the global options disables it entirely, which is a defensible hardening choice for an immutable-image deployment — at the cost of losing `caddy reload` and live introspection. ## When you would write JSON directly Hand-writing JSON for a small site would be perverse. But it is the right layer when a control plane generates config programmatically, when you need something the Caddyfile adapter does not express, or when automation should patch one field rather than regenerate a whole file. Caddy also autosaves the last loaded config so a restart can resume it — in the container image that lives on the `/config` volume, which is why that path exists separately from the certificate store on `/data`. ## The framing an interviewer wants The Caddyfile's brevity is real, and it is the product's selling point. What makes it operationally sound rather than merely cute is that there is a fully expressive layer underneath it and a live API on top of it, so you can inspect precisely what your short config became and change it without a restart.
- What happens to traffic if the configuration you post to the admin API fails to provision?The load is rejected and the previous configuration keeps serving. Provisioning happens before the swap, so an invalid upstream address, an unparseable value or a module that refuses to start produces an error response from the API rather than a broken server. That property is what makes it safe to reload from automation.
- Why would you set `admin off`, and what do you lose?It removes an unauthenticated control surface entirely, which suits an immutable deployment where config only changes by replacing the container. You lose `caddy reload`, live config introspection and any runtime patching — so every change becomes a restart, and you should be confident that restart is cheap and that certificate storage is persistent.
- When is writing JSON directly better than writing a Caddyfile?When a control plane generates the config, when you want to PATCH one field rather than regenerate a document, or when you need a module option the Caddyfile adapter does not surface. JSON is also the honest choice for anything machine-managed, since round-tripping through a human-oriented syntax adds a conversion step nobody reads.
saying these in an interview costs you the question
- Believes the Caddyfile is the format Caddy actually runs
- Thinks reload restarts the process and drops connections
- Assumes the admin API is authenticated out of the box
- Publishes port 2019 from a container to reach it
- Never inspects the adapted JSON when a Caddyfile misbehaves