t8y2/dbx · error
ETCD_V2_API_DISABLED
ETCD_V2_API_DISABLED
Error message
ETCD_V2_API_DISABLED: %s does not expose the etcd v2 API (removed in etcd 3.6+)
What it means
The etcd2 agent probes the v2 keys API (/v2/keys) to confirm it is served; etcd 3.6+ removed the v2 API, and such servers respond with HTTP 404. The agent converts that into the coded error ETCD_V2_API_DISABLED identifying the endpoint, so users get a clear diagnosis instead of confusing downstream failures.
Source
Thrown at agents/drivers/etcd2-go/client.go:267
return etcdVersion{etcdserver: parsed.Etcdserver, etcdcluster: parsed.Etcdcluster}, nil
}
// probeV2 checks that the v2 keys API is actually served. A 403 proves the
// channel and credentials reached etcd, mirroring the v3 agent's
// PERMISSION_DENIED handling for restricted users.
func (c *authenticatedClient) probeV2(ctx context.Context) (map[string]any, error) {
response, err := c.request(ctx, http.MethodGet, "/v2/members", "", nil)
if err != nil {
return nil, err
}
defer drainClose(response.Body)
switch response.StatusCode {
case http.StatusOK:
return map[string]any{"ok": true, "endpoint": c.endpoint}, nil
case http.StatusForbidden:
return map[string]any{"ok": true, "endpoint": c.endpoint, "limited": true}, nil
case http.StatusNotFound:
return nil, fmt.Errorf("ETCD_V2_API_DISABLED: %s does not expose the etcd v2 API (removed in etcd 3.6+)", c.endpoint)
case http.StatusUnauthorized:
return nil, fmt.Errorf("ETCD_UNAUTHENTICATED: authentication failed against %s", c.endpoint)
default:
body, _ := io.ReadAll(io.LimitReader(response.Body, 4096))
return nil, fmt.Errorf("etcd v2 probe against %s failed: HTTP %d %s", c.endpoint, response.StatusCode, strings.TrimSpace(string(body)))
}
}
// do performs a v2 API request and returns the body. Non-2xx responses are
// converted into etcdError values carrying the server's errorCode/message.
func (c *authenticatedClient) do(ctx context.Context, method, path, body string, header map[string]string) ([]byte, *http.Response, error) {
response, err := c.request(ctx, method, path, body, header)
if err != nil {
return nil, response, err
}
payload, readErr := io.ReadAll(response.Body)
_ = response.Body.Close()
if response.StatusCode < 200 || response.StatusCode >= 300 {View on GitHub (pinned to c0390bff16)
Solutions
- Use the v3 agent driver (agents/drivers/etcd-go) instead of the etcd2 driver against etcd 3.6+
- Downgrade/keep an etcd 3.5.x server if the v2 API is genuinely required
- On servers that still support the flag, start etcd with --enable-v2=true
- Check for a proxy stripping /v2/ paths and route v2 requests directly to etcd
Example fix
// before
agent := etcd2agent.New("https://etcd:2379") // etcd 3.6: no v2 API
// after
agent := etcdagent.New("https://etcd:2379") // v3 driver for etcd >= 3.6 Defensive patterns
Strategy: fallback
Validate before calling
// detect v2 API availability before connecting with the etcd2 driver
resp, err := http.Get(endpoint + "/v2/keys")
if err == nil && resp.StatusCode == http.StatusNotFound {
return errors.New("v2 API unavailable: use the v3 agent driver")
} Type guard
func isV2Disabled(err error) bool {
return err != nil && strings.Contains(err.Error(), "ETCD_V2_API_DISABLED")
} Try / catch
conn, err := etcd2agent.Connect(endpoint)
if isV2Disabled(err) {
conn, err = etcdagent.Connect(endpoint) // fall back to v3 driver
} Prevention
- Match the driver to the server version (etcd2 driver only for servers exposing /v2)
- Pin etcd 3.5.x if the v2 API is required, or enable --enable-v2 where supported
- Run a connectivity preflight that checks /v2/keys reachability before deploying the etcd2 driver
When it happens
Trigger: Connecting (probeClient -> probeV2) to an etcd server that returns HTTP 404 for /v2/keys — i.e. etcd 3.6 or newer with --enable-v2 not enabled/removed.
Common situations: Upgrading an etcd cluster from 3.5 to 3.6+ where the v2 API was removed; using the etcd2 agent driver against a modern etcd cluster by mistake; a reverse proxy returning 404 for /v2/keys because the route does not exist.
Related errors
- ETCD_WATCH_SCOPE_INVALID
- ETCD_WATCH_NOT_FOUND
- lease, ttl, and preserveLease cannot be specified together
- ETCD_V2_LEASE_UNSUPPORTED
- ETCD_V2_CAS_UNSUPPORTED
AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05).
Data as JSON: /api/errors/c281383d9436c40b.
Report an issue: GitHub.