hashicorp/terraform · error
Unexpected HTTP response code %d
Error message
Unexpected HTTP response code %d
What it means
Lock() received a status code outside the handled set (200/401/403/409/423). The numeric code is printed. Common culprits are 400 (malformed lock request body), 404 (lock endpoint not found), 405 (method not allowed — server rejects LOCK), or 5xx. Indicates a mismatch between the client's expectations and the server's lock API.
Source
Thrown at internal/backend/remote-state/http/client.go:122
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", &statemgr.LockError{
Err: fmt.Errorf("HTTP remote state already locked, failed to read body"),
}
}
existing := statemgr.LockInfo{}
err = json.Unmarshal(body, &existing)
if err != nil {
return "", &statemgr.LockError{
Err: fmt.Errorf("HTTP remote state already locked, failed to unmarshal body"),
}
}
return "", &statemgr.LockError{
Info: &existing,
Err: fmt.Errorf("HTTP remote state already locked: ID=%s", existing.ID),
}
default:
return "", fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode)
}
}
func (c *httpClient) Unlock(id string) error {
if c.UnlockURL == nil {
return nil
}
resp, err := c.httpRequest(c.UnlockMethod, c.UnlockURL, &c.jsonLockInfo, "unlock")
if err != nil {
return err
}
defer resp.Body.Close()
switch resp.StatusCode {
case http.StatusOK:
return nil
default:View on GitHub (pinned to c9def3e214)
Solutions
- Reproduce with `curl -i -X <lock_method> <lock_address>` to see the exact response and message.
- Verify lock_address matches the server's documented lock endpoint and lock_method is one the server accepts.
- If the server does not support locking, omit lock_address/unlock_address to disable locking entirely.
- For 5xx, inspect server logs and retry; consider raising retry_max.
Example fix
// before: server doesn't implement locking
backend "http" {
address = "https://state/state"
lock_address = "https://state/lock"
unlock_address = "https://state/unlock"
}
// after: drop locking if unsupported
backend "http" {
address = "https://state/state"
} Defensive patterns
Strategy: try-catch
Validate before calling
// Smoke-test the lock endpoint contract in CI
func probeLock(method, url, user, pass string) error {
req, _ := http.NewRequest(method, url, nil)
req.SetBasicAuth(user, pass)
resp, err := http.DefaultClient.Do(req)
if err != nil { return err }
defer resp.Body.Close()
switch {
case resp.StatusCode == http.StatusMethodNotAllowed:
return fmt.Errorf("server rejects %s method", method)
case resp.StatusCode == http.StatusNotFound:
return fmt.Errorf("lock endpoint not found")
}
return nil
} Type guard
func isUnexpectedLockCode(status int) bool {
switch status {
case http.StatusOK, http.StatusUnauthorized, http.StatusForbidden,
http.StatusConflict, http.StatusLocked:
return false
}
return true
} Prevention
- Verify lock_address/lock_method match the server contract.
- Drop lock_address/unlock_address if the server lacks locking support.
- Probe the lock endpoint in CI with curl before terraform.
When it happens
Trigger: lock_address points at a URL that returns 404; server does not implement the LOCK method (405); server requires a different lock request schema (400); transient 5xx during lock. Fires during lock acquisition.
Common situations: Wrong lock_address; server only supports a different locking protocol; reverse proxy not forwarding custom HTTP methods; server bug returning 500 on lock.
Related errors
- HTTP remote state endpoint requires auth
- HTTP remote state already locked, failed to read body
- HTTP remote state already locked, failed to unmarshal body
- Failed to make %s HTTP request: %s
- HTTP remote state endpoint invalid auth
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/e36a70cd0709a4a2.
Report an issue: GitHub.