caddyserver/caddy · error · APIError

[%s] key does not exist: %s

Error message

[%s] key does not exist: %s

What it means

Returned (APIError, HTTP 404) when PATCH targets a map key that does not exist. PATCH is strictly update-only: the final path segment must already be present in the config map. There is no implicit creation with PATCH.

Source

Thrown at admin.go:1293

						} 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),
						}
					}
					delete(v, part)
				default:
					return fmt.Errorf("unrecognized method %s", method)
				}
			} else {
				// if we are "PUTting" a new resource, the key(s) in its path
				// might not exist yet; that's OK but we need to make them as
				// we go, while we still have a pointer from the level above

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Create first with PUT (key must not exist), then PATCH
  2. GET the parent path to confirm the key exists before patching
  3. If your workflow replaced the config, re-apply the full desired state instead of patching stale paths

Example fix

# before
curl -X PATCH http://localhost:2019/config/apps/http/servers/myserver3/listen -d '[":8443"]'  # 404
# after
curl -X PUT http://localhost:2019/config/apps/http/servers/myserver3/listen -d '[":8443"]'
Defensive patterns

Strategy: validation

Validate before calling

key="myserver3"
exists=$(curl -s "http://localhost:2019/config/apps/http/servers" | jq --arg k "$key" 'has($k)')
[ "$exists" = "true" ] && curl -X PATCH "http://localhost:2019/config/apps/http/servers/$key" -d @body.json \
  || curl -X PUT "http://localhost:2019/config/apps/http/servers/$key" -d @body.json

Try / catch

On 404 from PATCH, verify the key against a fresh GET of the parent; create with PUT if it should exist, otherwise fix the stale path.

Prevention

When it happens

Trigger: PATCH /config/apps/http/servers/myserver3/listen when 'myserver3' was never created, or patching a key that was deleted by a concurrent client or a config reload that reset the tree.

Common situations: Assuming PATCH upserts (RFC-style) when Caddy defines it as update-only; patching after another process replaced the whole config via PUT /config/ or a Caddyfile reload, dropping the key.

Related errors


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