Reloading Caddy’s configuration without dropping connections is a surprisingly tricky dance.
Let’s see it in action. Imagine you have a Caddyfile like this:
:80 {
respond "Hello from old config!"
}
You start Caddy with caddy run --config Caddyfile. Now, if you hit http://localhost, you get "Hello from old config!".
Let’s say you want to change the response to "Hello from new config!". You update your Caddyfile:
:80 {
respond "Hello from new config!"
}
Now, you need to tell Caddy to pick up this change. This is where the Admin API comes in. Caddy, by default, listens on localhost:2019 for administrative commands. You can send a POST request to its /load endpoint.
First, let’s get the current configuration to see what Caddy thinks it’s running:
curl -v http://localhost:2019/config
You’ll see a JSON output representing your current config. Now, to load the new config, you need to send Caddy the entire new configuration. You can’t just send the diff. A common way to do this is to read your Caddyfile and POST it.
curl localhost:2019/load \
--data-binary @Caddyfile
If successful, you’ll get an empty response with a 204 No Content status code. Now, if you hit http://localhost again:
curl http://localhost
You should get "Hello from new config!". Critically, if you were in the middle of a long-lived connection (like a WebSocket or a slow download) when you ran the reload command, that connection would have remained open and uninterrupted, serving data from the old configuration until it naturally completed, while new connections would immediately start using the new configuration.
The magic behind this is Caddy’s internal state management. When you hit /load, Caddy doesn’t just swap out a config file. It parses the new configuration, validates it, and then performs a graceful handover. It spins up new listeners and workers with the new configuration, then signals the old listeners and workers to shut down after they’ve finished processing any in-flight requests. This is why it can reload without dropping connections.
The primary lever you control is the format of the configuration you send to the /load endpoint. While POSTing the Caddyfile directly works, Caddy also supports its JSON configuration format. You can generate this JSON and send it. This is particularly useful if you’re managing Caddy configurations programmatically.
To get the JSON config Caddy is currently running, you can GET /config:
curl http://localhost:2019/config > current_config.json
Then, you can modify current_config.json and POST it back:
curl localhost:2019/load \
--data-binary @current_config.json
One of the less obvious aspects of the Admin API reload is how Caddy handles certificate renewals. If you have certificates managed by Caddy, a reload operation will trigger Caddy to check for and obtain new certificates if necessary, before it activates the new configuration. This means that if your new configuration relies on a certificate that Caddy couldn’t obtain, the reload will fail gracefully, and your old configuration will remain active. You won’t accidentally deploy a site that’s missing a valid certificate.
The next challenge is managing multiple Caddy instances in a cluster, where a single reload command needs to be propagated to all of them.