HTTP

Header

Connection Header

Learn how the Connection header controls whether HTTP connections stay open (keep-alive) or close after each request. Optimize with persistent connections.

6 min read intermediate Try in Playground

Connection Header

TL;DR: Controls whether the network connection stays open after the current transaction. Use keep-alive for reuse or close to end the connection immediately.

What is Connection?

The Connection header controls whether the network connection stays open after the current HTTP transaction completes. It’s like deciding whether to keep a phone line open for more conversation or hang up after each call.

In modern HTTP (HTTP/1.1+), connections are persistent by default, meaning they stay open for multiple requests. The Connection header can override this behavior.

How Connection Works

HTTP/1.1 - Persistent by default:

GET /page1 HTTP/1.1
Host: example.com
Connection: keep-alive
```text

**Server confirms:**

```http
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 1234

[response body]

The connection stays open for the next request.

Close connection after response:

GET /page1 HTTP/1.1
Host: example.com
Connection: close
```text

**Server responds and closes:**

```http
HTTP/1.1 200 OK
Connection: close
Content-Length: 1234

[response body]

The connection closes after this response.

Syntax

Connection: keep-alive
Connection: close
Connection: upgrade
```text

### Common Values

```http
# Keep connection open (default in HTTP/1.1)
Connection: keep-alive

# Close connection after response
Connection: close

# Upgrade to different protocol (WebSocket, HTTP/2)
Connection: upgrade

# Multiple directives (comma-separated)
Connection: keep-alive, Upgrade

HTTP Version Differences

HTTP/1.0

# Connections close by default
GET / HTTP/1.0
Host: example.com

# Must explicitly request keep-alive
GET / HTTP/1.0
Host: example.com
Connection: keep-alive
```text

### HTTP/1.1

```http
# Connections persist by default
GET / HTTP/1.1
Host: example.com

# Equivalent to:
GET / HTTP/1.1
Host: example.com
Connection: keep-alive

HTTP/2 and HTTP/3

# Connection header is ignored
# HTTP/2+ uses multiplexed streams over a single connection
# Connection management is built into the protocol
```text

## Common Examples

### Normal Persistent Connection

```http
GET /api/users HTTP/1.1
Host: api.example.com
Connection: keep-alive

HTTP/1.1 200 OK
Connection: keep-alive
Keep-Alive: timeout=5, max=100
Content-Length: 1234

{"users": [...]}

Connection stays open for up to 5 seconds or 100 requests.

Explicit Connection Close

GET /api/logout HTTP/1.1
Host: api.example.com
Connection: close

HTTP/1.1 200 OK
Connection: close
Content-Length: 25

{"message": "Logged out"}
```http

Connection closes after logout.

### WebSocket Upgrade

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

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

Connection upgraded to WebSocket protocol.

Real-World Scenarios

High-Performance API

Client makes multiple requests:

// All these requests reuse the same connection
async function fetchUserData() {
  const profile = await fetch('https://api.example.com/profile')
  const posts = await fetch('https://api.example.com/posts')
  const comments = await fetch('https://api.example.com/comments')

  // Browser automatically manages connection pooling
  // Connection: keep-alive is implicit in HTTP/1.1
}
```javascript

**Benefits:**

- No TCP handshake overhead for each request
- Reduced latency
- Better throughput

### Graceful Server Shutdown

```javascript
// Node.js/Express
let isShuttingDown = false

process.on('SIGTERM', () => {
  isShuttingDown = true
  console.log('Server shutting down gracefully...')
})

app.use((req, res, next) => {
  if (isShuttingDown) {
    // Tell clients to close connection
    res.setHeader('Connection', 'close')
  }
  next()
})

app.get('/api/data', (req, res) => {
  res.json({ data: 'value' })
})

Load Balancer Health Checks

app.get('/health', (req, res) => {
  // Health checks should close connection immediately
  res.setHeader('Connection', 'close')
  res.json({ status: 'healthy' })
})
```javascript

### Long-Polling with Connection Management

```javascript
app.get('/events', async (req, res) => {
  // Keep connection alive for long-polling
  res.setHeader('Connection', 'keep-alive')
  res.setHeader('Keep-Alive', 'timeout=60')

  // Wait for events (simplified)
  const event = await waitForEvent(60000)

  if (event) {
    res.json(event)
  } else {
    res.status(204).end()
  }
})

Keep-Alive Parameters

The Keep-Alive header (separate from Connection) provides parameters:

HTTP/1.1 200 OK
Connection: keep-alive
Keep-Alive: timeout=5, max=100
Content-Length: 1234
```javascript

- `timeout=5`: Keep connection idle for 5 seconds
- `max=100`: Allow max 100 requests on this connection

```javascript
// Node.js server configuration
const server = http.createServer(app)

server.keepAliveTimeout = 5000 // 5 seconds
server.maxHeadersCount = 100 // max 100 requests

Best Practices

1. Let HTTP/1.1 Use Default Persistence

// ❌ Unnecessary - keep-alive is default in HTTP/1.1
res.setHeader('Connection', 'keep-alive')

// ✅ Only set when you need to override default behavior
if (shuttingDown) {
  res.setHeader('Connection', 'close')
}
```text

### 2. Close Connections for Final Requests

```javascript
// ✅ Close connection after logout
app.post('/logout', (req, res) => {
  req.session.destroy()
  res.setHeader('Connection', 'close')
  res.json({ message: 'Logged out' })
})

// ✅ Close connection after delete account
app.delete('/account', (req, res) => {
  deleteUserAccount(req.user.id)
  res.setHeader('Connection', 'close')
  res.json({ message: 'Account deleted' })
})

3. Handle Connection Timeouts

// ✅ Configure appropriate timeouts
const server = http.createServer(app)

// Idle timeout for keep-alive connections
server.keepAliveTimeout = 65000 // 65 seconds (slightly longer than load balancer)

// Overall request timeout
server.requestTimeout = 120000 // 120 seconds
```text

### 4. Don't Use Connection Header in HTTP/2

```javascript
// ❌ Connection header is ignored in HTTP/2
// HTTP/2 manages connections differently

// ✅ Detect HTTP version
app.use((req, res, next) => {
  if (req.httpVersion === '1.1' && shouldCloseConnection) {
    res.setHeader('Connection', 'close')
  }
  next()
})

Connection Pooling

Client-Side (Browser)

// Browsers automatically pool connections
// Default: 6-8 connections per origin

// Multiple parallel requests share connections
Promise.all([fetch('/api/users'), fetch('/api/posts'), fetch('/api/comments'), fetch('/api/likes')])
// These 4 requests may use 2-4 connections from the pool
```javascript

### Node.js Client

```javascript
const http = require('http')

// Configure connection pooling
const agent = new http.Agent({
  keepAlive: true,
  keepAliveMsecs: 1000,
  maxSockets: 50, // max 50 connections per host
  maxFreeSockets: 10, // max 10 idle connections
  timeout: 60000 // 60 second timeout
})

// Use the agent
fetch('https://api.example.com/data', {
  agent: agent
})

Debugging Connection Issues

Check Connection Header

# Curl shows connection management
curl -v https://api.example.com/data

# Look for:
# < Connection: keep-alive
# < Keep-Alive: timeout=5, max=100
```text

### Monitor Connection Reuse

```javascript
// Chrome DevTools
// Network tab Right-click header add "Connection ID"
// Same Connection ID = reused connection

Server-Side Logging

app.use((req, res, next) => {
  const connectionId = req.socket.remotePort
  console.log(`Request on connection ${connectionId}`)

  res.on('finish', () => {
    console.log(`Response sent, connection ${connectionId}`)
  })

  req.socket.on('close', () => {
    console.log(`Connection ${connectionId} closed`)
  })

  next()
})
```javascript

## Common Patterns

### Graceful Shutdown Pattern

```javascript
let server
let isShuttingDown = false

function startServer() {
  server = app.listen(3000)

  server.keepAliveTimeout = 65000
}

function gracefulShutdown() {
  isShuttingDown = true

  // Stop accepting new connections
  server.close(() => {
    console.log('Server shut down')
    process.exit(0)
  })

  // Force close after 30 seconds
  setTimeout(() => {
    console.error('Forcing shutdown')
    process.exit(1)
  }, 30000)
}

process.on('SIGTERM', gracefulShutdown)
process.on('SIGINT', gracefulShutdown)

app.use((req, res, next) => {
  if (isShuttingDown) {
    res.setHeader('Connection', 'close')
    res.status(503).json({ error: 'Server shutting down' })
  } else {
    next()
  }
})

Load Balancer-Aware Configuration

// AWS ALB idle timeout is 60 seconds
// Set server keep-alive slightly longer
const server = http.createServer(app)
server.keepAliveTimeout = 65000 // 65 seconds

// Ensure headers timeout is longer too
server.headersTimeout = 66000 // 66 seconds
```text

## Testing

### Test Keep-Alive

```bash
# Use telnet to send multiple requests
telnet example.com 80

GET / HTTP/1.1
Host: example.com
Connection: keep-alive

# Send another request on same connection
GET /page2 HTTP/1.1
Host: example.com
Connection: keep-alive

Test Connection Close

curl -v -H "Connection: close" https://api.example.com/data

# Look for:
# < Connection: close
# * Closing connection 0
```javascript

### Monitor Connection Pool

```javascript
// Node.js - monitor active connections
const server = http.createServer(app)

server.on('connection', (socket) => {
  console.log('New connection')
  socket.on('close', () => {
    console.log('Connection closed')
  })
})

HTTP/2 and the Connection Header

In HTTP/2, the Connection header is explicitly forbidden. HTTP/2 uses a single multiplexed connection for all requests to a server, and connection management is handled at the protocol level rather than through headers. If a client sends Connection: keep-alive in an HTTP/2 request, the server must treat it as a protocol error.

This distinction matters when building proxies or middleware that need to handle both HTTP/1.1 and HTTP/2. A proxy that forwards Connection headers from HTTP/1.1 clients to HTTP/2 upstream servers will cause protocol errors. The proxy must strip hop-by-hop headers (including Connection and any headers listed in the Connection header value) before forwarding requests upstream, regardless of the upstream protocol version.

Frequently Asked Questions

What is the Connection header?

Connection controls whether the network connection stays open after the current transaction. Values are keep-alive (reuse connection) or close (end connection).

Is Connection: keep-alive still needed?

In HTTP/1.1, keep-alive is the default so the header is optional. In HTTP/2 and HTTP/3, Connection is ignored as they handle connections differently.

When should I use Connection: close?

Use close when you know no more requests will follow, to free server resources. Servers may also send it when shutting down or under heavy load.

What is a hop-by-hop header?

Connection is hop-by-hop, meaning it applies only to the immediate connection, not end-to-end. Proxies must not forward Connection or headers it lists.

Keep Learning