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

  1. If the key should be replaced, use PATCH (update existing) or DELETE then PUT
  2. If it should be created once, treat 409 as 'already done' and skip
  3. 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

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


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/7d5761bbf298264c. Report an issue: GitHub.