caddyserver/caddy · error · APIError
[%s] key already exists: %s
Error message
[%s] key already exists: %s
What it means
Returned (wrapped in APIError, HTTP 409 Conflict) when PUT targets a map key that already exists. PUT in Caddy's admin API is strictly create-only: the final path segment must not already be present in the map. POST is the create-or-append verb, PATCH the update verb.
Source
Thrown at admin.go:1285
// it, otherwise it just sets or creates the value
if arr, ok := v[part].([]any); ok {
if ellipses {
valArray, ok := val.([]any)
if !ok {
return fmt.Errorf("final element is not an array")
}
v[part] = append(arr, valArray...)
} else {
v[part] = append(arr, val)
}
} else {
v[part] = val
}
case http.MethodPut:
if _, ok := v[part]; ok {
return APIError{
HTTPStatus: http.StatusConflict,
Err: fmt.Errorf("[%s] key already exists: %s", path, part),
}
}
v[part] = val
case http.MethodPatch:
if _, ok := v[part]; !ok {
return APIError{
HTTPStatus: http.StatusNotFound,
Err: fmt.Errorf("[%s] key does not exist: %s", path, part),
}
}
v[part] = val
case http.MethodDelete:
if _, ok := v[part]; !ok {
return APIError{
HTTPStatus: http.StatusNotFound,
Err: fmt.Errorf("[%s] key does not exist: %s", path, part),
}
}View on GitHub (pinned to 50e54ee279)
Solutions
- If the key should be replaced, use PATCH (update existing) or DELETE then PUT
- If it should be created once, treat 409 as 'already done' and skip
- Use POST for append-or-create semantics on list endpoints
Example fix
# before
curl -X PUT http://localhost:2019/config/apps/http/servers/myserver2 -d '{...}' # 409
# after: replace existing
curl -X PATCH http://localhost:2019/config/apps/http/servers/myserver2 -d '{...}' Defensive patterns
Strategy: fallback
Validate before calling
key="myserver2" exists=$(curl -s "http://localhost:2019/config/apps/http/servers" | jq --arg k "$key" 'has($k)') [ "$exists" = "false" ] && curl -X PUT "http://localhost:2019/config/apps/http/servers/$key" -d @body.json
Try / catch
On 409 from PUT, decide intent: skip (already provisioned), PATCH to overwrite, or DELETE+PUT to reset. Never blind-retry the same PUT.
Prevention
- PUT = create-only, PATCH = update-only, POST = append/create in Caddy's API
- Make bootstrap scripts treat 409 as success for idempotent runs
When it happens
Trigger: PUT /config/apps/http/servers/myserver2 when 'myserver2' already exists; PUT any .../key where key is present. Prior creation by another client or an earlier run of the same script.
Common situations: Idempotency-minded scripts using PUT as upsert; re-running a bootstrap script that provisions the same key twice; race between two clients creating the same resource.
Related errors
- unknown object ID '%s'
- decoding request body: %w, at offset %d
- decoding request body: %w
- no traversable path
- path missing
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/7d5761bbf298264c.
Report an issue: GitHub.