Skip to content
Control Plane Labs

426 Upgrade Required

Client error response defined by RFC 9110 §15.5.22.

Last updated July 27, 2026

Common causes at a glance

  • An API endpoint that no longer accepts cleartext HTTP
  • A server requiring HTTP/2 for an endpoint called over HTTP/1.1
  • A WebSocket endpoint reached with a plain GET and no Upgrade header
  • A load balancer terminating cleartext and refusing to forward it

Reproduce it

curl -i http://api.example.com/v1/status

What 426 Upgrade Required means

426 is a refusal with instructions attached. Unlike a redirect it does not point at a different URL; it points at a different way of speaking to the same URL. The response must carry an Upgrade header listing acceptable protocols, so the client knows what to do rather than guessing.

Where you actually meet it: an API that has switched off cleartext HTTP and answers 426 with Upgrade: TLS/1.2, HTTP/1.1, or a service requiring HTTP/2. Browsers ignore the mechanism entirely, which is why public websites redirect to HTTPS with a 301 instead — a browser that receives 426 just shows an error page.

What the spec says

RFC 9110 §15.5.22 defines 426 and makes the Upgrade header mandatory on the response. The header field itself is §7.8, and the successful other half of the exchange is 101 Switching Protocols. Two limits matter. The upgrade token mechanism is HTTP/1.1 only — HTTP/2 removed it, so a client already on HTTP/2 cannot act on a 426 the way the spec imagines. And upgrading cleartext HTTP to TLS in-band, specified in RFC 2817, was never meaningfully deployed and is trivially stripped. HSTS is what actually enforces TLS.

What actually causes it

The common trigger is a client pinned to an http:// URL after the service stopped accepting cleartext. Config files, hardcoded base URLs in mobile builds, and monitoring probes are the usual suspects, and they surface all at once on the day a redirect is replaced with a hard refusal. The other frequent source is WebSocket infrastructure: a server expecting an upgrade handshake answers 426 to a bare GET, and a reverse proxy that drops Upgrade and Connection headers makes every handshake look like a bare GET. If clients started getting 426 the moment nginx or an ingress controller went in front of a WebSocket service, that is the cause, not the application.

How to debug it

Read the Upgrade header on the response — it is the entire message:

curl -sSi http://api.example.com/v1/status | grep -i -E '^(HTTP/|upgrade:)'

Upgrade: TLS/1.2, HTTP/1.1 means switch to https://. Upgrade: h2c means the endpoint wants HTTP/2; try curl --http2 and check whether ALPN is negotiating correctly at the edge. Upgrade: websocket means the handshake headers are missing or were stripped in transit, which you confirm by sending the request straight to the origin and comparing with what arrives through the proxy. The header inspector is faster than reading two curl traces side by side.

Working examples

curl

# The Upgrade header tells you exactly what the server wants.
curl -sSi http://api.example.com/v1/status | grep -i -E '^(HTTP/|upgrade:)'

# TLS demanded:
curl -sSi https://api.example.com/v1/status

# HTTP/2 demanded (over TLS, ALPN does the negotiation):
curl -sSi --http2 https://api.example.com/v1/status

Python (requests)

import requests

resp = requests.get("http://api.example.com/v1/status", timeout=10)
if resp.status_code == 426:
    wanted = resp.headers.get("Upgrade", "")
    print("server wants:", wanted)
    if "TLS" in wanted:
        # Fix the base URL rather than retrying per-request.
        resp = requests.get("https://api.example.com/v1/status", timeout=10)
print(resp.status_code)

Node (fetch)

const res = await fetch("http://api.example.com/v1/status", {
  redirect: "manual",
});

if (res.status === 426) {
  // Mandatory header per RFC 9110; if it is missing the server is broken.
  console.error("upgrade required:", res.headers.get("upgrade"));
}

// fetch() speaks HTTP/2 automatically over TLS, so the usual fix is the scheme.
console.log((await fetch("https://api.example.com/v1/status")).status);

Reference and tooling

The authoritative list of assigned status codes is theIANA HTTP Status Code Registry. For a searchable copy with the semantics summarised, use theHTTP status code reference on this site. To see exactly what a server is sending, theheader inspector shows the raw status line and response headers, and thecurl builder assembles the request flags for you.

Other 4xx codes