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

  1. Reproduce with `curl -i -X <lock_method> <lock_address>` to see the exact response and message.
  2. Verify lock_address matches the server's documented lock endpoint and lock_method is one the server accepts.
  3. If the server does not support locking, omit lock_address/unlock_address to disable locking entirely.
  4. 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

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


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/e36a70cd0709a4a2. Report an issue: GitHub.