HTTP

Header

Via Header

Learn how the Via header tracks the path of HTTP requests through proxies and gateways. Debug routing issues and understand your network infrastructure.

7 min read intermediate Try in Playground

TL;DR: Tracks the path of requests through proxies and gateways, with each intermediary adding its identifier. Helps debug routing and prevent proxy loops.

What is Via?

The Via header tracks the path an HTTP request or response takes through proxy servers, gateways, and other intermediaries. Each intermediary adds its own information to the Via header, creating a breadcrumb trail of the network path.

Think of it as a travel log showing “this message went from client → proxy1 → proxy2 → origin server → proxy2 → proxy1 → client.”

How Via Works

Client sends request:

GET /api/users HTTP/1.1
Host: api.example.com
```text

**First proxy adds Via:**

```http
GET /api/users HTTP/1.1
Host: api.example.com
Via: 1.1 proxy1.example.com

Second proxy appends to Via:

GET /api/users HTTP/1.1
Host: api.example.com
Via: 1.1 proxy1.example.com, 1.1 proxy2.example.com
```text

**Server response includes Via chain:**

```http
HTTP/1.1 200 OK
Via: 1.1 proxy2.example.com, 1.1 proxy1.example.com
Content-Type: application/json

{"users": [...]}

Syntax

Via: <protocol-version> <received-by> [<comment>]
Via: <entry1>, <entry2>, <entry3>
```text

### Components

- **protocol-version** - HTTP version (1.0, 1.1, 2.0)
- **received-by** - Hostname or pseudonym of intermediary
- **comment** - Optional additional information

## Common Examples

### Single Proxy

```http
Via: 1.1 proxy.example.com

Request passed through one proxy.

Multiple Proxies

Via: 1.1 gateway.example.com, 1.1 cache.example.com, 1.1 firewall.example.com
```text

Request passed through three intermediaries.

### With Comments

```http
Via: 1.1 proxy.example.com (nginx/1.20.1)

Includes proxy software version.

HTTP/2 Proxy

Via: 2.0 edge-proxy.example.com
```text

Proxy using HTTP/2.

### Mixed Protocol Versions

```http
Via: 1.1 gateway.example.com, 2.0 edge.example.com, 1.1 cache.example.com

Chain with different HTTP versions.

Real-World Scenarios

CDN Request Path

# Request through CDN
GET /images/logo.png HTTP/1.1
Host: cdn.example.com
Via: 1.1 client-proxy.local

# CDN edge adds itself
Via: 1.1 client-proxy.local, 1.1 edge-us-east.cdn.example.com

# Response back through CDN
HTTP/1.1 200 OK
Via: 1.1 edge-us-east.cdn.example.com, 1.1 client-proxy.local
Content-Type: image/png
```text

### Corporate Network

```http
# Employee request
GET https://api.external.com/data HTTP/1.1
Via: 1.1 workstation-proxy.corp.internal

# Corporate gateway
Via: 1.1 workstation-proxy.corp.internal, 1.1 gateway.corp.internal

# Security proxy
Via: 1.1 workstation-proxy.corp.internal, 1.1 gateway.corp.internal, 1.1 firewall.corp.internal

# External service receives
Via: 1.1 workstation-proxy.corp.internal, 1.1 gateway.corp.internal, 1.1 firewall.corp.internal

API Gateway Chain

# Client → Load Balancer → API Gateway → Service
Via: 1.1 lb-us-west-1.example.com, 1.1 api-gateway.example.com

# Response follows same path back
HTTP/1.1 200 OK
Via: 1.1 api-gateway.example.com, 1.1 lb-us-west-1.example.com
```text

### Reverse Proxy Setup

```http
# Request through Cloudflare and Nginx
GET /api/products HTTP/1.1
Via: 1.1 cloudflare, 1.1 nginx-proxy.example.com

# Response
HTTP/1.1 200 OK
Via: 1.1 nginx-proxy.example.com, 1.1 cloudflare

Proxy Implementation

Node.js (http-proxy)

const http = require('http')
const httpProxy = require('http-proxy')

const proxy = httpProxy.createProxyServer({})

const server = http.createServer((req, res) => {
  // Add Via header to request
  const existingVia = req.headers['via'] || ''
  const viaEntry = '1.1 proxy.example.com'

  req.headers['via'] = existingVia ? `${existingVia}, ${viaEntry}` : viaEntry

  // Proxy the request
  proxy.web(req, res, { target: 'http://backend-server:3000' })
})

// Add Via to response
proxy.on('proxyRes', (proxyRes, req, res) => {
  const existingVia = proxyRes.headers['via'] || ''
  const viaEntry = '1.1 proxy.example.com'

  proxyRes.headers['via'] = existingVia ? `${existingVia}, ${viaEntry}` : viaEntry
})

server.listen(8080)
```javascript

### Express Middleware

```javascript
const express = require('express')
const { createProxyMiddleware } = require('http-proxy-middleware')

const app = express()

// Via header middleware
app.use((req, res, next) => {
  const hostname = 'gateway.example.com'
  const protocol = '1.1'
  const viaEntry = `${protocol} ${hostname}`

  // Add to request
  if (req.headers['via']) {
    req.headers['via'] += `, ${viaEntry}`
  } else {
    req.headers['via'] = viaEntry
  }

  // Intercept response to add Via
  const originalSend = res.send
  res.send = function (data) {
    if (res.getHeader('via')) {
      res.setHeader('via', `${res.getHeader('via')}, ${viaEntry}`)
    } else {
      res.setHeader('via', viaEntry)
    }
    originalSend.call(this, data)
  }

  next()
})

// Proxy middleware
app.use(
  '/api',
  createProxyMiddleware({
    target: 'http://backend:3000',
    changeOrigin: true
  })
)

app.listen(8080)

Nginx

http {
    # Add Via header to requests
    proxy_set_header Via "$http_via, 1.1 nginx-proxy";

    # If no existing Via header
    map $http_via $via_header {
        default "$http_via, 1.1 nginx-proxy";
        ""      "1.1 nginx-proxy";
    }

    server {
        listen 80;
        server_name proxy.example.com;

        location / {
            proxy_pass http://backend;
            proxy_set_header Via $via_header;

            # Add Via to response
            add_header Via "1.1 nginx-proxy" always;
        }
    }

    # Upstream backend
    upstream backend {
        server backend-server:3000;
    }
}
```text

### Apache

```apache
# Load required modules
LoadModule headers_module modules/mod_headers.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so

<VirtualHost *:80>
    ServerName proxy.example.com

    # Add Via header to requests
    RequestHeader edit Via "^(.*)$" "$1, 1.1 apache-proxy"
    RequestHeader set Via "1.1 apache-proxy" "expr=%{req:Via} == ''"

    # Add Via header to responses
    Header append Via "1.1 apache-proxy"

    # Proxy configuration
    ProxyPass / http://backend-server:3000/
    ProxyPassReverse / http://backend-server:3000/
</VirtualHost>

Python (HTTP Proxy)

from flask import Flask, request, Response
import requests

app = Flask(__name__)

BACKEND_URL = 'http://backend-server:3000'
PROXY_NAME = '1.1 python-proxy.example.com'

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE'])
def proxy(path):
    # Build backend URL
    url = f'{BACKEND_URL}/{path}'

    # Copy headers and add Via
    headers = dict(request.headers)
    if 'Via' in headers:
        headers['Via'] += f', {PROXY_NAME}'
    else:
        headers['Via'] = PROXY_NAME

    # Forward request
    resp = requests.request(
        method=request.method,
        url=url,
        headers=headers,
        data=request.get_data(),
        allow_redirects=False
    )

    # Build response with Via header
    response_headers = dict(resp.headers)
    if 'Via' in response_headers:
        response_headers['Via'] += f', {PROXY_NAME}'
    else:
        response_headers['Via'] = PROXY_NAME

    return Response(
        resp.content,
        status=resp.status_code,
        headers=response_headers
    )

if __name__ == '__main__':
    app.run(port=8080)
```javascript

## Analyzing Via Headers

### Logging Via Information

```javascript
const express = require('express')
const app = express()

app.use((req, res, next) => {
  const via = req.headers['via']

  if (via) {
    console.log('Request path:')
    via.split(',').forEach((entry, index) => {
      console.log(`  ${index + 1}. ${entry.trim()}`)
    })
  }

  next()
})

app.get('/api/users', (req, res) => {
  res.json({ users: [] })
})

app.listen(3000)

Parsing Via Entries

function parseViaHeader(viaHeader) {
  if (!viaHeader) return []

  return viaHeader.split(',').map((entry) => {
    const trimmed = entry.trim()
    const match = trimmed.match(/^(\S+)\s+(\S+)(?:\s+\(([^)]+)\))?$/)

    if (!match) return { raw: trimmed }

    return {
      protocol: match[1],
      host: match[2],
      comment: match[3] || null,
      raw: trimmed
    }
  })
}

// Usage
const via = '1.1 proxy1.example.com, 2.0 proxy2.example.com (nginx)'
const entries = parseViaHeader(via)

console.log(entries)
// [
//   { protocol: '1.1', host: 'proxy1.example.com', comment: null, raw: '...' },
//   { protocol: '2.0', host: 'proxy2.example.com', comment: 'nginx', raw: '...' }
// ]
```javascript

### Detecting Proxy Chains

```javascript
app.use((req, res, next) => {
  const via = req.headers['via']

  if (via) {
    const entries = parseViaHeader(via)

    if (entries.length > 5) {
      console.warn('Unusually long proxy chain detected:', entries.length)
    }

    // Check for loops
    const hosts = entries.map((e) => e.host)
    const uniqueHosts = new Set(hosts)

    if (hosts.length !== uniqueHosts.size) {
      console.error('Proxy loop detected!')
      return res.status(508).send('Loop Detected')
    }
  }

  next()
})

Privacy Considerations

Pseudonyms for Privacy

# Use pseudonym instead of actual hostname
proxy_set_header Via "1.1 proxy-server-1";

# Instead of revealing internal structure
# proxy_set_header Via "1.1 internal-gateway-us-west-2.corp.example.com";
```javascript

### Removing Via Header

```javascript
// Remove Via header before forwarding to external services
delete req.headers['via']

// Or only remove internal proxy entries
if (req.headers['via']) {
  const entries = parseViaHeader(req.headers['via'])
  const externalEntries = entries.filter((e) => !e.host.includes('.internal'))
  req.headers['via'] = externalEntries.map((e) => e.raw).join(', ')
}

Best Practices

1. Always Include Protocol Version

# ✅ Includes protocol version
Via: 1.1 proxy.example.com

# ❌ Missing protocol version
Via: proxy.example.com
```text

### 2. Use Meaningful Host Identifiers

```http
# ✅ Descriptive hostname
Via: 1.1 us-west-cache.cdn.example.com

# ⚠️ Less informative
Via: 1.1 proxy-123

3. Preserve Existing Via Headers

// ✅ Append to existing Via
const existingVia = req.headers['via'] || ''
req.headers['via'] = existingVia ? `${existingVia}, 1.1 proxy` : '1.1 proxy'

// ❌ Overwrite existing Via
req.headers['via'] = '1.1 proxy'
```text

### 4. Keep Comments Concise

```http
# ✅ Brief, useful comment
Via: 1.1 proxy.example.com (nginx/1.20)

# ❌ Too verbose
Via: 1.1 proxy.example.com (nginx version 1.20.1 running on Ubuntu 20.04 LTS with custom configuration)

5. Detect Proxy Loops

function hasProxyLoop(viaHeader) {
  if (!viaHeader) return false

  const entries = parseViaHeader(viaHeader)
  const hosts = entries.map((e) => e.host)

  return hosts.length !== new Set(hosts).size
}

if (hasProxyLoop(req.headers['via'])) {
  return res.status(508).send('Loop Detected')
}
```text

## Common Patterns

### Load Balancer + Cache + Origin

```http
Via: 1.1 lb.example.com, 1.1 cache.example.com, 1.1 origin.example.com

CDN Edge Network

Via: 1.1 edge-us-east.cdn.com, 1.1 origin.example.com
```text

### Corporate Proxy Chain

```http
Via: 1.1 employee-proxy.corp, 1.1 gateway.corp, 1.1 firewall.corp

API Gateway Architecture

Via: 1.1 kong-gateway.example.com, 1.1 service-mesh.internal
```javascript

## Debugging with Via

### Identifying Bottlenecks

```javascript
app.use((req, res, next) => {
  const start = Date.now()

  res.on('finish', () => {
    const duration = Date.now() - start
    const via = req.headers['via'] || 'direct'

    console.log(`${req.method} ${req.url}`)
    console.log(`  Duration: ${duration}ms`)
    console.log(`  Path: ${via}`)

    if (duration > 1000) {
      console.warn('  ⚠️  Slow request detected!')
    }
  })

  next()
})

Testing Proxy Configuration

# Test proxy path with curl
curl -v http://proxy.example.com/api/users

# Check Via header in response
curl -s -D - http://proxy.example.com/api/users | grep -i via

# Example output:
# Via: 1.1 proxy.example.com, 1.1 cache.example.com
```text

## Security Considerations

### Information Disclosure

```http
# ❌ Reveals internal infrastructure
Via: 1.1 internal-db-proxy-us-west-2-prod.corp.internal.example.com

# ✅ Uses pseudonym
Via: 1.1 proxy-1

Loop Detection

// Prevent infinite proxy loops
const MAX_VIA_ENTRIES = 10

if (req.headers['via']) {
  const entries = parseViaHeader(req.headers['via'])

  if (entries.length >= MAX_VIA_ENTRIES) {
    return res.status(508).send('Loop Detected')
  }
}
```text

## HTTP/2 Considerations

Via header works the same in HTTP/2:

```http
:method: GET
:path: /api/users
:scheme: https
:authority: api.example.com
via: 2.0 h2-proxy.example.com

Protocol version in Via should reflect the protocol used between hops, not end-to-end.

Frequently Asked Questions

What is the Via header?

Via tracks proxies and gateways the request passed through. Each proxy adds its identifier. It helps debug routing and detect proxy loops.

What information does Via contain?

Each entry has protocol version and proxy identifier: Via: 1.1 proxy1.example.com, 1.1 proxy2.example.com. Shows the path through intermediaries.

Why is Via important?

It prevents infinite loops (proxies check if they are already listed), aids debugging, and provides transparency about request routing.

Should I strip Via headers?

Generally no, Via provides useful debugging information. However, for privacy, edge proxies may remove or anonymize Via before sending to clients.

Keep Learning