SiteError.comYour friendly guide to HTTP status codes
Status CodesBlog
  1. Home
  2. Blog
  3. Understanding HTTP 426 Upgrade Required: The Server's Demand, Not Its Agreement

Understanding HTTP 426 Upgrade Required: The Server's Demand, Not Its Agreement

September 21, 20268 min read
4xxClient Error

426 Upgrade Required is the least-used member of the "protocol switching" family of status codes, and it's easy to confuse with its much more common sibling, 101 Switching Protocols. They sound similar and share a header, but they represent opposite moves in the same negotiation. This post covers what 426 actually requires, its mandatory Upgrade response header, the largely historical use case that gave the code its most common example (forcing TLS), and how it differs from just redirecting to HTTPS.

What Is a 426?

A 426 tells the client that the server won't honor the current request on the protocol it arrived on, and names the protocol it wants instead.

The 426 (Upgrade Required) status code indicates that the server refuses to perform the request using the current protocol but might be willing to do so after the client upgrades to a different protocol. The server MUST send an Upgrade header field in a 426 response to indicate the required protocol(s) (Section 7.8). — RFC 9110, Section 15.5.22

In plain English: "I'm not going to do this on the protocol you're currently using — switch, and try again." Unlike 101, which is the server agreeing to a change the client proposed, 426 is the server initiating the demand itself, as an error response to a request it's refusing to fulfill as-is.

The RFC's own example is refreshingly concrete:

HTTP/1.1 426 Upgrade Required
Upgrade: HTTP/3.0
Connection: Upgrade
Content-Length: 53
Content-Type: text/plain
 
This service requires use of the HTTP/3.0 protocol.

The Upgrade Header Isn't Optional

426's defining rule is right there in the RFC text: the server MUST send an Upgrade header naming the protocol it will accept. A bare 426 with no Upgrade header leaves the client knowing it was rejected but not what to do about it — which defeats the entire point of the code. The same requirement is echoed in the header's own definition:

A server that sends a 426 (Upgrade Required) response MUST send an Upgrade header field to indicate the acceptable protocols, in order of descending preference. — RFC 9110, Section 7.8

Don't use 426 for API versioning — telling clients to switch from your /v1 endpoints to /v2. That's an application-level concern, not a protocol upgrade, and 426 with its mandatory Upgrade header doesn't map onto it. Use 426 specifically when you're refusing a request until the client switches HTTP protocol versions or adds a transport-layer feature the current connection lacks.

101 vs 426: Agreement vs Demand

These two codes are structurally linked — RFC 9110's Upgrade header section defines both together — but they point in opposite directions:

CodeMeaningWho initiatesStandard?
101 Switching ProtocolsServer agrees to a protocol change the client requestedClient proposes via Upgrade; server acceptsYes (RFC 9110)
426 Upgrade RequiredServer refuses to proceed on the current protocol and demands a changeServer insists via Upgrade on an error responseYes (RFC 9110)

Put another way: if the client offers and the server accepts, that's 101. If the server insists and the client hasn't offered anything yet, that's 426. See our 101 Switching Protocols post for the handshake side of this story — including why HTTP/2 and HTTP/3 don't use the Upgrade mechanism at all.

The TLS-Upgrade Backstory (and Why It's Mostly History)

426 shows up in a lot of explanations as "the code for forcing HTTPS," and that's rooted in a real, older mechanism: RFC 2817 defined a way to upgrade a plain HTTP connection to TLS in place, on the same port, using this exact status code:

A server MAY indicate that a client request can not be completed without TLS using the "426 Upgrade Required" status code, which MUST include an an [sic] Upgrade header field specifying the token of the required TLS version.

HTTP/1.1 426 Upgrade Required
Upgrade: TLS/1.0, HTTP/1.1
Connection: Upgrade

— RFC 2817, Section 4.2

The idea was that a single port (traditionally 80) could serve both plaintext and TLS-secured HTTP, negotiating security via Upgrade instead of dedicating a separate port (443) to it. In practice, the web went the other way: HTTPS runs on its own port with its own TLS handshake happening before any HTTP bytes are exchanged, so there's no HTTP request yet at the point a server might want to demand TLS. Virtually nothing implements RFC 2817's Upgrade-to-TLS mechanism today.

That has a real consequence worth knowing if you're troubleshooting: 426 cannot be used to reject an already-negotiated TLS connection that's using too old a version. By the time an HTTP request exists to respond to, the TLS handshake — and the version it settled on — already happened underneath it. If you need to enforce a minimum TLS version, that's a TLS-layer configuration (like NGINX's ssl_protocols directive), not something you can express as an HTTP status code, because a client offering only obsolete TLS versions never gets far enough to receive an HTTP response at all. For plain "you're on HTTP, please use HTTPS," most servers today just issue a 301 or 302 redirect to the https:// URL instead of RFC 2817's Upgrade mechanism — it's simpler and works with every client, including ones that never implemented the older negotiation.

Returning 426 Correctly

The realistic modern use for 426 is refusing a request until the client speaks a newer HTTP version, or until it supports some transport-layer capability the server requires — not TLS renegotiation.

Node.js / Express

app.use((req, res, next) => {
	if (req.httpVersion === "1.0") {
		res
			.status(426)
			.set("Upgrade", "HTTP/1.1")
			.set("Connection", "Upgrade")
			.type("text/plain")
			.send("This service requires HTTP/1.1 or newer.");
		return;
	}
	next();
});

Next.js App Router (Route Handler)

The Web-standard Request object Route Handlers receive doesn't expose the raw negotiated HTTP version the way Node's http.IncomingMessage does, so this is necessarily an application-defined check — for example, a required capability signaled by a custom client header:

import { NextResponse } from "next/server";
 
export async function POST(request: Request) {
	if (!request.headers.get("x-client-protocol-v2")) {
		return NextResponse.json(
			{ error: "This endpoint requires the v2 client protocol." },
			{
				status: 426,
				headers: { Upgrade: "example-v2", Connection: "Upgrade" },
			},
		);
	}
 
	// handle the request
}

NGINX

If you're synthesizing a 426 at the proxy layer, remember NGINX's add_header directive only applies to a specific whitelist of successful/redirect status codes by default — 200, 201, 204, 206, 301, 302, 303, 304, 307, and 308. A 426 isn't on that list, so an Upgrade header added without the always parameter will silently be dropped from the error response, leaving you with exactly the malformed "426 with no Upgrade header" situation the RFC forbids:

location /legacy-only/ {
    return 426;
    add_header Upgrade "HTTP/1.1" always;
    add_header Connection "Upgrade" always;
}

Common Pitfalls

  1. Sending a 426 without an Upgrade header. This is the one the RFC calls out explicitly as mandatory — a 426 that doesn't say what to upgrade to leaves the client stuck.
  2. Using 426 for API versioning. Telling a client to move from /v1 to /v2 is an application concern communicated through documentation, deprecation headers, or a 400/410 response — not a protocol-level 426.
  3. Reaching for 426 to enforce a minimum negotiated TLS version. That failure happens at the TLS handshake, before any HTTP response — including a 426 — can be sent. Configure it at the TLS layer instead (e.g., NGINX's ssl_protocols).
  4. Forgetting NGINX's add_header skips non-2xx/3xx status codes by default. If you're adding the required Upgrade header at the proxy layer, use the always parameter or the header silently disappears from the 426 response.

Wrapping Up

426 is the server's demand for a protocol change, not its agreement to one — the opposite half of the negotiation from 101. The rules of thumb:

  • 426 means the server refuses the current protocol and names what it wants instead
  • The Upgrade header on the response is mandatory, not optional — RFC 9110 says MUST
  • It's paired with 101: 101 is agreement, 426 is demand
  • Its classic "force TLS" example (RFC 2817) is largely historical; modern servers use HTTPS redirects or TLS-layer configuration instead
  • On NGINX, remember add_header needs always to survive onto a 426 response

For more, see our pages on 426 Upgrade Required and 101 Switching Protocols, and our 101 Switching Protocols post for the handshake this code's demand is aimed at producing.

Full Reference

426 Upgrade Required

The client should switch to a different protocol such as TLS/1.3.

Related Status Codes

🤨400Bad Request🔐401Unauthorized💳402Payment Required🚫403Forbidden🔍404Not Found🙅405Method Not Allowed🍽️406Not Acceptable🎫407Proxy Authentication Required⏰408Request Timeout⚔️409Conflict👻410Gone📏411Length Required❌412Precondition Failed📦413Payload Too Large📜414URI Too Long📼415Unsupported Media Type📖416Range Not Satisfiable😞417Expectation Failed🫖418I'm a Teapot🚪421Misdirected Request🤔422Unprocessable Entity🔒423Locked🎯424Failed Dependency⏰425Too Early⬆️426Upgrade Required🔑428Precondition Required🚦429Too Many Requests📋431Request Header Fields Too Large⚖️451Unavailable For Legal Reasons
Back to Blog

Popular Status Codes

  • 200 OK
  • 301 Moved Permanently
  • 302 Found
  • 400 Bad Request
  • 401 Unauthorized
  • 403 Forbidden
  • 404 Not Found
  • 500 Internal Server Error
  • 502 Bad Gateway
  • 503 Service Unavailable

Compare Codes

  • 401 vs 403
  • 301 vs 302
  • 404 vs 410
  • 500 vs 502
  • Compare any codes →

Categories

  • Informational
  • Success
  • Redirection
  • Client Error
  • Server Error
  • NGINX
  • Cloudflare
  • AWS ELB
  • Microsoft IIS

Tools

  • Cheat Sheet
  • Status Code Quiz
  • URL Checker
  • API Playground
  • Blog

© 2026 SiteError.com. All rights reserved.