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/statusWhat 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
- 400 Bad Request
- 401 Unauthorized
- 402 Payment Required
- 403 Forbidden
- 404 Not Found
- 405 Method Not Allowed
- 406 Not Acceptable
- 407 Proxy Authentication Required
- 408 Request Timeout
- 409 Conflict
- 410 Gone
- 411 Length Required
- 412 Precondition Failed
- 413 Content Too Large
- 414 URI Too Long
- 415 Unsupported Media Type
- 416 Range Not Satisfiable
- 417 Expectation Failed
- 418 (Unused)
- 419 Page Expired
- 421 Misdirected Request
- 422 Unprocessable Content
- 423 Locked
- 424 Failed Dependency
- 425 Too Early
- 428 Precondition Required
- 429 Too Many Requests
- 431 Request Header Fields Too Large
- 444 No Response
- 451 Unavailable For Legal Reasons
- 494 Request Header Too Large
- 495 SSL Certificate Error
- 496 SSL Certificate Required
- 497 HTTP Request Sent to HTTPS Port
- 499 Client Closed Request