HTTP

Header

Upgrade Header

Learn how the Upgrade header requests protocol upgrades to WebSocket, HTTP/2, or other protocols on the same TCP connection. Understand upgrade negotiation.

7 min read advanced Try in Playground

TL;DR: Requests switching to a different protocol on the same connection. Most commonly used to upgrade HTTP to WebSocket for real-time bidirectional communication.

What is Upgrade?

The Upgrade header allows clients and servers to negotiate switching from one protocol to another on the same connection. It’s most commonly used for upgrading from HTTP/1.1 to WebSocket, but can also be used for HTTP/2 or other protocols.

Think of it as saying “we’re currently speaking HTTP, but can we switch to WebSocket for real-time bidirectional communication?”

How Upgrade Works

Client requests protocol upgrade:

GET /chat HTTP/1.1
Host: example.com
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
```text

**Server accepts upgrade:**

```http
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

[WebSocket communication begins]

Syntax

Upgrade: <protocol-name>[/<protocol-version>]
Upgrade: <protocol1>, <protocol2>, <protocol3>
```text

Must be used with `Connection: Upgrade` header.

### Protocol Names

- **websocket** - WebSocket protocol
- **h2c** - HTTP/2 over cleartext (non-TLS)
- **HTTP/2.0** - HTTP/2 protocol
- **TLS/1.3** - TLS version upgrade

## WebSocket Upgrade

### Client Request

```http
GET /socket HTTP/1.1
Host: example.com
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Origin: https://example.com

Required headers for WebSocket:

  • Upgrade: websocket
  • Connection: Upgrade
  • Sec-WebSocket-Version: 13
  • Sec-WebSocket-Key: base64-encoded key

Server Response (Success)

HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
```text

Server echoes Upgrade headers and switches to WebSocket.

### Server Response (Rejection)

```http
HTTP/1.1 400 Bad Request
Content-Type: text/plain

WebSocket upgrade failed: Invalid Sec-WebSocket-Key

Server rejects upgrade and continues with HTTP.

HTTP/2 Upgrade

HTTP/2 over Cleartext (h2c)

GET / HTTP/1.1
Host: example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA

```text

Client proposes HTTP/2 upgrade.

### Server Accepts

```http
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c

[HTTP/2 communication begins]

Server Rejects

HTTP/1.1 200 OK
Content-Type: text/html

<!DOCTYPE html>
<html>...
```http

Server continues with HTTP/1.1.

## Real-World Scenarios

### Chat Application

```http
# Initial HTTP request
GET /api/chat/room/123 HTTP/1.1
Host: chat.example.com
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Protocol: chat-protocol-v1

# Server upgrades to WebSocket
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat-protocol-v1

# Now bidirectional WebSocket communication

Real-Time Dashboard

GET /api/live-metrics HTTP/1.1
Host: dashboard.example.com
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
```http

Upgrades to WebSocket for live data streaming.

### Multiplayer Game

```http
GET /game/session/456 HTTP/1.1
Host: game.example.com
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: ZWFjaCBub25jZSBpcyB1bmlxdWU=
Sec-WebSocket-Protocol: game-state-sync

Low-latency game state synchronization.

Stock Ticker

GET /api/stocks/stream HTTP/1.1
Host: trading.example.com
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: c3RvY2sgdGlja2VyIGtleQ==
```javascript

Real-time stock price updates.

## Server Implementation

### Node.js (ws library)

```javascript
const http = require('http')
const WebSocket = require('ws')
const express = require('express')

const app = express()
const server = http.createServer(app)
const wss = new WebSocket.Server({ noServer: true })

// Regular HTTP routes
app.get('/api/data', (req, res) => {
  res.json({ data: 'example' })
})

// Handle upgrade requests
server.on('upgrade', (request, socket, head) => {
  // Verify upgrade request
  if (request.headers['upgrade'] !== 'websocket') {
    socket.destroy()
    return
  }

  // Authentication/authorization check
  const token = new URL(request.url, 'http://localhost').searchParams.get('token')

  if (!isValidToken(token)) {
    socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n')
    socket.destroy()
    return
  }

  // Accept WebSocket upgrade
  wss.handleUpgrade(request, socket, head, (ws) => {
    wss.emit('connection', ws, request)
  })
})

// WebSocket connection handler
wss.on('connection', (ws, request) => {
  console.log('WebSocket connection established')

  ws.on('message', (message) => {
    console.log('Received:', message)
    ws.send(`Echo: ${message}`)
  })

  ws.on('close', () => {
    console.log('WebSocket connection closed')
  })
})

server.listen(3000)

Manual WebSocket Upgrade

const crypto = require('crypto')
const http = require('http')

const server = http.createServer((req, res) => {
  res.writeHead(200)
  res.end('HTTP endpoint')
})

server.on('upgrade', (req, socket, head) => {
  // Verify WebSocket upgrade headers
  if (req.headers['upgrade'] !== 'websocket' || !req.headers['sec-websocket-key']) {
    socket.end('HTTP/1.1 400 Bad Request\r\n\r\n')
    return
  }

  // Generate Sec-WebSocket-Accept
  const key = req.headers['sec-websocket-key']
  const hash = crypto
    .createHash('sha1')
    .update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
    .digest('base64')

  // Send 101 Switching Protocols
  const headers = [
    'HTTP/1.1 101 Switching Protocols',
    'Upgrade: websocket',
    'Connection: Upgrade',
    `Sec-WebSocket-Accept: ${hash}`,
    '\r\n'
  ]

  socket.write(headers.join('\r\n'))

  // Now in WebSocket mode
  socket.on('data', (data) => {
    // Handle WebSocket frames
    console.log('WebSocket data received')
  })
})

server.listen(3000)
```javascript

### Python (websockets)

```python
import asyncio
import websockets
from aiohttp import web

# WebSocket handler
async def websocket_handler(request):
    ws = web.WebSocketResponse()

    # Check if upgrade is possible
    if not ws.can_prepare(request):
        return web.Response(text='WebSocket upgrade failed', status=400)

    # Perform upgrade
    await ws.prepare(request)

    # Handle messages
    async for msg in ws:
        if msg.type == web.WSMsgType.TEXT:
            await ws.send_str(f'Echo: {msg.data}')
        elif msg.type == web.WSMsgType.ERROR:
            print(f'WebSocket error: {ws.exception()}')

    return ws

# HTTP handler
async def http_handler(request):
    return web.Response(text='HTTP endpoint')

# Setup routes
app = web.Application()
app.router.add_get('/ws', websocket_handler)
app.router.add_get('/', http_handler)

web.run_app(app, port=3000)

Go (gorilla/websocket)

package main

import (
    "log"
    "net/http"
    "github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool {
        // Validate origin
        return true
    },
}

func websocketHandler(w http.ResponseWriter, r *http.Request) {
    // Upgrade HTTP connection to WebSocket
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println("Upgrade error:", err)
        return
    }
    defer conn.Close()

    // Handle WebSocket messages
    for {
        messageType, message, err := conn.ReadMessage()
        if err != nil {
            log.Println("Read error:", err)
            break
        }

        log.Printf("Received: %s", message)

        err = conn.WriteMessage(messageType, message)
        if err != nil {
            log.Println("Write error:", err)
            break
        }
    }
}

func httpHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("HTTP endpoint"))
}

func main() {
    http.HandleFunc("/ws", websocketHandler)
    http.HandleFunc("/", httpHandler)

    log.Println("Server starting on :3000")
    log.Fatal(http.ListenAndServe(":3000", nil))
}
```javascript

## Client Implementation

### JavaScript (Browser)

```javascript
// WebSocket constructor handles upgrade automatically
const socket = new WebSocket('wss://example.com/socket')

socket.addEventListener('open', (event) => {
  console.log('WebSocket connected')
  socket.send('Hello Server!')
})

socket.addEventListener('message', (event) => {
  console.log('Message from server:', event.data)
})

socket.addEventListener('error', (event) => {
  console.error('WebSocket error:', event)
})

socket.addEventListener('close', (event) => {
  console.log('WebSocket closed:', event.code, event.reason)
})

JavaScript (Node.js)

const WebSocket = require('ws')

const ws = new WebSocket('ws://localhost:3000/socket', {
  headers: {
    Authorization: 'Bearer token123'
  }
})

ws.on('open', () => {
  console.log('Connected')
  ws.send('Hello')
})

ws.on('message', (data) => {
  console.log('Received:', data.toString())
})

ws.on('error', (error) => {
  console.error('Error:', error)
})

ws.on('close', () => {
  console.log('Connection closed')
})
```javascript

### Python (websockets)

```python
import asyncio
import websockets

async def connect():
    uri = "ws://localhost:3000/socket"
    async with websockets.connect(uri) as websocket:
        await websocket.send("Hello Server")
        response = await websocket.recv()
        print(f"Received: {response}")

asyncio.run(connect())

cURL (Request Upgrade)

# Request WebSocket upgrade (won't complete handshake)
curl -i -N \
  -H "Connection: Upgrade" \
  -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" \
  -H "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==" \
  http://localhost:3000/socket

# Use websocat for full WebSocket support
websocat ws://localhost:3000/socket
```text

## Security Considerations

### Validate Upgrade Requests

```javascript
server.on('upgrade', (request, socket, head) => {
  // Check upgrade header
  if (request.headers['upgrade'] !== 'websocket') {
    socket.destroy()
    return
  }

  // Verify WebSocket version
  if (request.headers['sec-websocket-version'] !== '13') {
    socket.write('HTTP/1.1 400 Bad Request\r\n\r\n')
    socket.destroy()
    return
  }

  // Verify Sec-WebSocket-Key exists
  if (!request.headers['sec-websocket-key']) {
    socket.write('HTTP/1.1 400 Bad Request\r\n\r\n')
    socket.destroy()
    return
  }

  // Proceed with upgrade
  handleUpgrade(request, socket, head)
})

Authenticate Before Upgrading

server.on('upgrade', async (request, socket, head) => {
  // Extract authentication token
  const token = new URL(request.url, 'http://localhost').searchParams.get('token')

  try {
    // Verify token
    const user = await verifyToken(token)

    // Attach user to request
    request.user = user

    // Proceed with upgrade
    wss.handleUpgrade(request, socket, head, (ws) => {
      wss.emit('connection', ws, request)
    })
  } catch (error) {
    socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n')
    socket.destroy()
  }
})
```javascript

### Validate Origin

```javascript
const upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool {
        origin := r.Header.Get("Origin")
        allowedOrigins := []string{
            "https://example.com",
            "https://www.example.com",
        }

        for _, allowed := range allowedOrigins {
            if origin == allowed {
                return true
            }
        }

        return false
    },
}

Rate Limiting

const rateLimiter = new Map()

server.on('upgrade', (request, socket, head) => {
  const ip = request.socket.remoteAddress
  const now = Date.now()

  // Check rate limit
  if (rateLimiter.has(ip)) {
    const lastConnection = rateLimiter.get(ip)
    if (now - lastConnection < 1000) {
      // Less than 1 second since last connection
      socket.write('HTTP/1.1 429 Too Many Requests\r\n\r\n')
      socket.destroy()
      return
    }
  }

  rateLimiter.set(ip, now)

  // Proceed with upgrade
  handleUpgrade(request, socket, head)
})
```text

## Best Practices

### 1. Always Check Connection Header

```javascript
// ✅ Verify both headers
if (
  request.headers['upgrade'] === 'websocket' &&
  request.headers['connection'].toLowerCase().includes('upgrade')
) {
  // Proceed
}

// ❌ Only checking Upgrade
if (request.headers['upgrade'] === 'websocket') {
  // Incomplete check
}

2. Handle Upgrade Failures Gracefully

server.on('upgrade', (request, socket, head) => {
  try {
    validateUpgrade(request)
    handleUpgrade(request, socket, head)
  } catch (error) {
    socket.write('HTTP/1.1 400 Bad Request\r\n\r\n')
    socket.destroy()
    console.error('Upgrade failed:', error)
  }
})
```javascript

### 3. Use TLS for Production

```javascript
// ✅ Secure WebSocket (wss://)
const socket = new WebSocket('wss://example.com/socket')

// ❌ Insecure WebSocket (ws://) - only for development
const socket = new WebSocket('ws://example.com/socket')

4. Set Reasonable Timeouts

wss.on('connection', (ws) => {
  // Ping/pong to detect broken connections
  let isAlive = true

  ws.on('pong', () => {
    isAlive = true
  })

  const interval = setInterval(() => {
    if (!isAlive) {
      ws.terminate()
      return
    }

    isAlive = false
    ws.ping()
  }, 30000)

  ws.on('close', () => {
    clearInterval(interval)
  })
})
```text

## Common Issues

### Missing Connection Header

```http
# ❌ Missing Connection: Upgrade
GET /socket HTTP/1.1
Upgrade: websocket

# ✅ Both headers present
GET /socket HTTP/1.1
Connection: Upgrade
Upgrade: websocket

Wrong Protocol Name

# ❌ Incorrect protocol name
Upgrade: WebSocket

# ✅ Lowercase
Upgrade: websocket
```javascript

### Mixed HTTP/HTTPS and WS/WSS

```javascript
// ❌ HTTPS page trying ws:// connection (blocked by browsers)
const socket = new WebSocket('ws://example.com/socket')

// ✅ Use wss:// with HTTPS pages
const socket = new WebSocket('wss://example.com/socket')

HTTP/2 Considerations

HTTP/2 doesn’t use Upgrade header for protocol negotiation. Instead:

  • ALPN (Application-Layer Protocol Negotiation) is used during TLS handshake
  • Clients can’t upgrade from HTTP/1.1 to HTTP/2 mid-connection over TLS
  • h2c (HTTP/2 over cleartext) can use Upgrade for testing

Frequently Asked Questions

What is the Upgrade header?

Upgrade requests switching to a different protocol on the same connection. Most commonly used to upgrade HTTP to WebSocket. Server responds with 101 if it agrees.

How does WebSocket upgrade work?

Client sends Upgrade: websocket with Connection: Upgrade. Server responds 101 Switching Protocols with same headers. The connection then uses WebSocket protocol.

Can Upgrade be used for HTTP/2?

HTTP/2 upgrade via Upgrade header is possible but rarely used. Most HTTP/2 connections use ALPN during TLS handshake instead.

What happens if server ignores Upgrade?

Server can ignore Upgrade and respond normally with HTTP. The client should handle both upgraded and non-upgraded responses gracefully.

Keep Learning