426 Upgrade Required — HTTP/1.1 to HTTP/2 Migration
Симптомы
- A server or API responds with 426 Upgrade Required along with an `Upgrade` response header specifying the minimum required protocol version
- Older client libraries or SDKs that do not support HTTP/2 or TLS 1.2+ receive this error while newer clients on the same code path succeed
- WebSocket endpoints return 426 when the client sends a regular HTTP GET request without the `Upgrade: websocket` header in the request
- An API that recently deprecated HTTP/1.1 begins returning 426 to clients that were not updated as part of the migration announcement
- An intermediary proxy downgrades the protocol — it strips the `Upgrade` request header, so the server sees a plain HTTP/1.1 request and returns 426
- Older client libraries or SDKs that do not support HTTP/2 or TLS 1.2+ receive this error while newer clients on the same code path succeed
- WebSocket endpoints return 426 when the client sends a regular HTTP GET request without the `Upgrade: websocket` header in the request
- An API that recently deprecated HTTP/1.1 begins returning 426 to clients that were not updated as part of the migration announcement
- An intermediary proxy downgrades the protocol — it strips the `Upgrade` request header, so the server sees a plain HTTP/1.1 request and returns 426
Первопричины
- Server enforcing HTTP/2 and rejecting HTTP/1.1 connections — the server uses HTTP/2 ALPN negotiation and will not serve HTTP/1.1 clients
- TLS version too old — server requires TLS 1.2+ but the client only supports TLS 1.0/1.1 which are now deprecated per RFC 8996; many modern servers reject older TLS versions
- WebSocket endpoint receiving a regular HTTP GET without the `Upgrade: websocket` request header — the server returns 426 to signal that a protocol upgrade is required
- API deprecating older protocol versions and forcing client upgrades — the server returns 426 after a grace period to enforce the migration
- Intermediary proxy (corporate proxy, API gateway, or CDN) downgrading the protocol by stripping the `Upgrade` request header before it reaches the server
Диагностика
**Step 1 — Read the Upgrade response header to know what's required**
```bash
curl -v https://api.example.com/endpoint
# < HTTP/1.1 426 Upgrade Required
# < Upgrade: HTTP/2.0
# Means the server requires HTTP/2
# Or for WebSocket endpoints:
curl -v https://api.example.com/ws/
# < HTTP/1.1 426 Upgrade Required
# < Upgrade: websocket
# Means you must send a WebSocket upgrade request
```
**Step 2 — Test with HTTP/2 explicitly**
```bash
# curl uses HTTP/2 by default over TLS if the server supports it
curl -v --http2 https://api.example.com/endpoint
# Successful: HTTP/2 200
# Force HTTP/1.1 to confirm the 426 is triggered:
curl -v --http1.1 https://api.example.com/endpoint
# < HTTP/1.1 426 Upgrade Required
```
**Step 3 — Check TLS version support**
```bash
# Test which TLS versions the server accepts:
curl -v --tlsv1.0 https://api.example.com/ # Should fail (TLS 1.0 deprecated)
curl -v --tlsv1.1 https://api.example.com/ # Should fail (TLS 1.1 deprecated)
curl -v --tlsv1.2 https://api.example.com/ # Should succeed
# Or use openssl to check supported protocols:
openssl s_client -connect api.example.com:443 -tls1_2
# Verify: Protocol : TLSv1.2 (or 1.3)
```
**Step 4 — Verify the WebSocket Upgrade request format**
```bash
# A proper WebSocket upgrade request must include these headers:
curl -v \
-H 'Connection: Upgrade' \
-H 'Upgrade: websocket' \
-H 'Sec-WebSocket-Version: 13' \
-H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \
https://api.example.com/ws/
# Should get: HTTP/1.1 101 Switching Protocols
# If still 426: server requires a specific subprotocol or version
```
**Step 5 — Check for proxy stripping the Upgrade header**
```bash
# Test with a request bin to see what headers arrive at the server:
curl -v https://httpbin.org/get \
-H 'Upgrade: websocket' \
-H 'Connection: Upgrade'
# If the request bin doesn't show Upgrade — a proxy stripped it
```
```bash
curl -v https://api.example.com/endpoint
# < HTTP/1.1 426 Upgrade Required
# < Upgrade: HTTP/2.0
# Means the server requires HTTP/2
# Or for WebSocket endpoints:
curl -v https://api.example.com/ws/
# < HTTP/1.1 426 Upgrade Required
# < Upgrade: websocket
# Means you must send a WebSocket upgrade request
```
**Step 2 — Test with HTTP/2 explicitly**
```bash
# curl uses HTTP/2 by default over TLS if the server supports it
curl -v --http2 https://api.example.com/endpoint
# Successful: HTTP/2 200
# Force HTTP/1.1 to confirm the 426 is triggered:
curl -v --http1.1 https://api.example.com/endpoint
# < HTTP/1.1 426 Upgrade Required
```
**Step 3 — Check TLS version support**
```bash
# Test which TLS versions the server accepts:
curl -v --tlsv1.0 https://api.example.com/ # Should fail (TLS 1.0 deprecated)
curl -v --tlsv1.1 https://api.example.com/ # Should fail (TLS 1.1 deprecated)
curl -v --tlsv1.2 https://api.example.com/ # Should succeed
# Or use openssl to check supported protocols:
openssl s_client -connect api.example.com:443 -tls1_2
# Verify: Protocol : TLSv1.2 (or 1.3)
```
**Step 4 — Verify the WebSocket Upgrade request format**
```bash
# A proper WebSocket upgrade request must include these headers:
curl -v \
-H 'Connection: Upgrade' \
-H 'Upgrade: websocket' \
-H 'Sec-WebSocket-Version: 13' \
-H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \
https://api.example.com/ws/
# Should get: HTTP/1.1 101 Switching Protocols
# If still 426: server requires a specific subprotocol or version
```
**Step 5 — Check for proxy stripping the Upgrade header**
```bash
# Test with a request bin to see what headers arrive at the server:
curl -v https://httpbin.org/get \
-H 'Upgrade: websocket' \
-H 'Connection: Upgrade'
# If the request bin doesn't show Upgrade — a proxy stripped it
```
Решение
**Fix 1: Update HTTP client library to support HTTP/2**
```python
# Python — use httpx which supports HTTP/2 natively
# pip install httpx[http2]
import httpx
# httpx uses HTTP/2 automatically over TLS when server supports it:
async with httpx.AsyncClient(http2=True) as client:
resp = await client.get('https://api.example.com/endpoint')
print(resp.http_version) # 'HTTP/2'
```
**Fix 2: Update TLS version in older client configurations**
```python
import ssl
import httpx
# Force minimum TLS 1.2
ssl_context = ssl.create_default_context()
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
async with httpx.AsyncClient(verify=ssl_context) as client:
resp = await client.get('https://api.example.com/endpoint')
```
**Fix 3: Send a proper WebSocket upgrade request**
```python
import websockets
import asyncio
async def connect_websocket():
# websockets library handles the upgrade request automatically:
async with websockets.connect(
'wss://api.example.com/ws/',
subprotocols=['chat'], # If server requires a specific subprotocol
extra_headers={'Authorization': 'Bearer token'},
) as ws:
await ws.send('{"type": "subscribe"}')
msg = await ws.recv()
print(msg)
asyncio.run(connect_websocket())
```
**Fix 4: Configure proxy to pass the Upgrade header**
```nginx
# Nginx: Ensure Upgrade header is forwarded for WebSocket routes
location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
}
```
**Fix 5: Implement graceful degradation for older clients**
```python
# Server-side: Detect old protocol and return helpful 426 with guidance
from django.http import HttpResponse
def my_api_view(request):
if request.META.get('SERVER_PROTOCOL') == 'HTTP/1.0':
response = HttpResponse(status=426)
response['Upgrade'] = 'HTTP/1.1, HTTP/2.0'
response['Content-Type'] = 'application/problem+json'
response.content = (
'{"title": "Upgrade Required",'
'"detail": "This API requires HTTP/1.1 or higher. '
'Please update your HTTP client library.",'
'"upgrade_guide": "https://docs.example.com/migration"}'
)
return response
# Normal handling
...
```
```python
# Python — use httpx which supports HTTP/2 natively
# pip install httpx[http2]
import httpx
# httpx uses HTTP/2 automatically over TLS when server supports it:
async with httpx.AsyncClient(http2=True) as client:
resp = await client.get('https://api.example.com/endpoint')
print(resp.http_version) # 'HTTP/2'
```
**Fix 2: Update TLS version in older client configurations**
```python
import ssl
import httpx
# Force minimum TLS 1.2
ssl_context = ssl.create_default_context()
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
async with httpx.AsyncClient(verify=ssl_context) as client:
resp = await client.get('https://api.example.com/endpoint')
```
**Fix 3: Send a proper WebSocket upgrade request**
```python
import websockets
import asyncio
async def connect_websocket():
# websockets library handles the upgrade request automatically:
async with websockets.connect(
'wss://api.example.com/ws/',
subprotocols=['chat'], # If server requires a specific subprotocol
extra_headers={'Authorization': 'Bearer token'},
) as ws:
await ws.send('{"type": "subscribe"}')
msg = await ws.recv()
print(msg)
asyncio.run(connect_websocket())
```
**Fix 4: Configure proxy to pass the Upgrade header**
```nginx
# Nginx: Ensure Upgrade header is forwarded for WebSocket routes
location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
}
```
**Fix 5: Implement graceful degradation for older clients**
```python
# Server-side: Detect old protocol and return helpful 426 with guidance
from django.http import HttpResponse
def my_api_view(request):
if request.META.get('SERVER_PROTOCOL') == 'HTTP/1.0':
response = HttpResponse(status=426)
response['Upgrade'] = 'HTTP/1.1, HTTP/2.0'
response['Content-Type'] = 'application/problem+json'
response.content = (
'{"title": "Upgrade Required",'
'"detail": "This API requires HTTP/1.1 or higher. '
'Please update your HTTP client library.",'
'"upgrade_guide": "https://docs.example.com/migration"}'
)
return response
# Normal handling
...
```
Профилактика
- **Communicate protocol deprecation timelines clearly** — announce HTTP/1.0 or TLS 1.0/1.1 end-of-support dates in API changelogs, response headers, and developer documentation at least 6 months before enforcement
- **Return 426 with a machine-readable Problem Details body (RFC 7807)** — include an `upgrade_guide` URL and migration deadline so clients can automatically surface the deprecation notice to developers
- **Use API versioning to manage protocol transitions** — new API versions can require HTTP/2 while old versions maintain HTTP/1.1 support during a deprecation window, avoiding hard breaks for existing integrations
- **Test your application with HTTP/1.1 force-disabled in your CI pipeline** to verify the 426 response is correct, the Upgrade header is present, and error messages are actionable before the deprecation date arrives
- **Return 426 with a machine-readable Problem Details body (RFC 7807)** — include an `upgrade_guide` URL and migration deadline so clients can automatically surface the deprecation notice to developers
- **Use API versioning to manage protocol transitions** — new API versions can require HTTP/2 while old versions maintain HTTP/1.1 support during a deprecation window, avoiding hard breaks for existing integrations
- **Test your application with HTTP/1.1 force-disabled in your CI pipeline** to verify the 426 response is correct, the Upgrade header is present, and error messages are actionable before the deprecation date arrives