-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Description
How to reproduce
Here is the Caddyfile that can be adapted to JSON correctly but is invalid when trying to load into Caddy because cert.pem
and key.pem
don't exist:
example.com {
# Note a two-space indentation, not a tab
tls cert.pem key.pem
}
Let's load it through the Caddy Admin API /load
via the unix socket:
curl -v --unix-socket /run/uncloud/caddy/admin.sock -H "Content-Type: text/caddyfile" --data-binary @Caddyfile http://localhost/load
* Trying /run/uncloud/caddy/admin.sock:0...
* Connected to localhost (/run/caddy/admin.sock) port 80 (#0)
> POST /load HTTP/1.1
> Host: localhost
> User-Agent: curl/7.88.1
> Accept: */*
> Content-Type: text/caddyfile
> Content-Length: 39
>
< HTTP/1.1 200 OK
< Date: Tue, 09 Sep 2025 07:39:22 GMT
< Content-Length: 336
< Content-Type: text/plain; charset=utf-8
<
[{"file":"Caddyfile","line":2,"message":"Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies"}]{"error":"loading config: loading new config: loading http app module: provision http: getting tls app: loading tls app module: provision tls: loading certificates: open cert.pem: no such file or directory"}
* Connection #0 to host localhost left intact
It returns 200 OK
while 400 Bad Request
would be more appropriate in this case.
Let's replace two-space indentation with a tab so that the Caddyfile becomes a properly formatted config and try to load it again:
curl -v --unix-socket /run/uncloud/caddy/admin.sock -H "Content-Type: text/caddyfile" --data-binary @Caddyfile http://localhost/load
* Trying /run/uncloud/caddy/admin.sock:0...
* Connected to localhost (/run/caddy/admin.sock) port 80 (#0)
> POST /load HTTP/1.1
> Host: localhost
> User-Agent: curl/7.88.1
> Accept: */*
> Content-Type: text/caddyfile
> Content-Length: 38
>
< HTTP/1.1 400 Bad Request
< Content-Type: application/json
< Date: Tue, 09 Sep 2025 07:43:33 GMT
< Content-Length: 208
< Connection: close
<
{"error":"loading config: loading new config: loading http app module: provision http: getting tls app: loading tls app module: provision tls: loading certificates: open cert.pem: no such file or directory"}
* Closing connection 0
There is no warning about the formatting in the body and the status code is correct 400 Bad Request
.
The problem
The reason why this happens is that when the /load
handler adapts the Caddyfile and the adapter returns non-empty warnings, they're written to the response body right away: https://github.com/caddyserver/caddy/blob/master/caddyconfig/load.go#L104-L110. This happens before a proper status code is written to the response.
According to the docs for the ResponseWriter.Write
method:
// If [ResponseWriter.WriteHeader] has not yet been called, Write calls
// WriteHeader(http.StatusOK) before writing the data.
This is what happens here I believe. The caddy.APIError
returned later in the handler https://github.com/caddyserver/caddy/blob/master/caddyconfig/load.go#L118-L121 can't change the response status code because it's too late.
Expected behaviour
The response should return 400 Bad Request
status code and the body should contain a valid JSON instead of two concatenated JSONs (warnings + error):
[{"file":"Caddyfile","line":2,"message":"Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies"}]{"error":"loading config: loading new config: loading http app module: provision http: getting tls app: loading tls app module: provision tls: loading certificates: open cert.pem: no such file or directory"}
I'm not sure if there is a canonical type for replies that include both the result/error and warnings but I believe a user should be able to correctly parse the body as json and extract warnings and errors from it if there are any.
Assistance Disclosure
AI not used
If AI was used, describe the extent to which it was used.
No response