Skip to main content
The Proxy helper provides a secure and convenient way to proxy HTTP requests through your Hono application. It automatically handles hop-by-hop headers, encoding, and provides security features to prevent header injection attacks.

Import

import { proxy } from 'hono/proxy'

Basic Usage

Proxy requests to another server:
import { proxy } from 'hono/proxy'

const originServer = 'example.com'

app.get('/proxy/:path', (c) => {
  const path = c.req.param('path')
  return proxy(`http://${originServer}/${path}`)
})

Function Signature

function proxy(
  input: string | URL | Request,
  init?: ProxyRequestInit
): Promise<Response>
input
string | URL | Request
required
The destination URL or Request object to proxy to
init
ProxyRequestInit
Optional configuration object (extends RequestInit with proxy-specific options)
Returns: Promise<Response> - A Response object ready to be returned from your handler

Proxy Options

raw

Pass the original request to copy method, body, and headers:
app.all('/proxy/:path', (c) => {
  return proxy(`http://${originServer}/${c.req.param('path')}`, {
    raw: c.req.raw,
  })
})
raw
Request
Original Request object to copy method, body, and headers from

headers

Customize or override headers:
app.get('/proxy/:path', (c) => {
  return proxy(`http://${originServer}/${c.req.param('path')}`, {
    headers: {
      ...c.req.header(),
      'X-Forwarded-For': '127.0.0.1',
      'X-Forwarded-Host': c.req.header('host'),
      Authorization: undefined, // Remove Authorization header
    },
  })
})
headers
HeadersInit | Record<string, string | undefined>
Headers to send with the proxied request. Set to undefined to remove a header.

customFetch

Use a custom fetch implementation:
const customFetch = async (request: Request) => {
  console.log('Proxying request:', request.url)
  return fetch(request)
}

app.get('/proxy/:path', (c) => {
  return proxy(`http://${originServer}/${c.req.param('path')}`, {
    customFetch,
  })
})
customFetch
(request: Request) => Promise<Response>
Custom fetch function for making the proxied request

strictConnectionProcessing

Enable RFC 9110 compliant Connection header processing:
app.get('/internal-proxy/:path', (c) => {
  return proxy(`http://${internalServer}/${c.req.param('path')}`, {
    raw: c.req.raw,
    strictConnectionProcessing: true,
  })
})
strictConnectionProcessing
boolean
default:"false"
  • false (default): Ignores Connection header to prevent Hop-by-Hop Header Injection attacks. Recommended for untrusted clients.
  • true: Processes Connection header per RFC 9110 and removes listed headers. Only use in trusted environments.
Security: Only enable strictConnectionProcessing in trusted environments. When false (default), the Connection header is ignored to prevent potential security vulnerabilities.

Complete Example

Simple Proxy

import { Hono } from 'hono'
import { proxy } from 'hono/proxy'

const app = new Hono()

app.get('/proxy', (c) => {
  return proxy('https://example.com/')
})

export default app

Forward All Request Data

Proxy the entire request including method, body, and headers:
import { proxy } from 'hono/proxy'

const originServer = 'api.example.com'

app.all('/api/*', (c) => {
  const path = c.req.path.replace(/^\/api/, '')
  return proxy(`https://${originServer}${path}`, {
    raw: c.req.raw,
    headers: {
      'X-Forwarded-Host': c.req.header('host'),
    },
  })
})

Modify Response

Modify the proxied response before returning:
import { proxy } from 'hono/proxy'

app.get('/proxy/:path', async (c) => {
  const res = await proxy(`http://${originServer}/${c.req.param('path')}`, {
    headers: {
      'X-Forwarded-For': '127.0.0.1',
    },
  })
  
  // Remove sensitive headers from response
  res.headers.delete('Set-Cookie')
  res.headers.delete('X-Internal-Header')
  
  return res
})

Add Authentication

Add authentication to proxied requests:
import { proxy } from 'hono/proxy'

app.get('/secure-proxy/*', async (c) => {
  // Verify client authentication
  const token = c.req.header('Authorization')
  if (!token) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  
  const path = c.req.path.replace(/^\/secure-proxy/, '')
  return proxy(`https://${originServer}${path}`, {
    headers: {
      'Authorization': 'Bearer secret-backend-token',
      'X-Client-Token': token,
    },
  })
})

Handle Query Parameters

import { proxy } from 'hono/proxy'

app.get('/search', (c) => {
  const query = c.req.query('q')
  const url = new URL('https://api.example.com/search')
  url.searchParams.set('q', query)
  
  return proxy(url.toString())
})

Automatic Processing

The proxy helper automatically:

Removes Hop-by-Hop Headers

The following headers are automatically removed from both request and response per RFC 2616:
  • Connection
  • Keep-Alive
  • Proxy-Authenticate
  • Proxy-Authorization
  • TE
  • Trailer
  • Transfer-Encoding
  • Upgrade

Handles Accept-Encoding

The Accept-Encoding header is automatically removed from the request. The runtime will handle compression natively.

Handles Content-Encoding

If the response has Content-Encoding, it’s removed along with Content-Length to prevent decompression issues.

Security Considerations

Hop-by-Hop Header Injection: By default (strictConnectionProcessing: false), the Connection header is ignored to prevent attackers from manipulating which headers are removed. Only enable strict processing in trusted environments.
Credentials Exposure: Be careful when forwarding request headers with ...c.req.header(). This may expose credentials like Authorization or Cookie to the target server. Explicitly control which headers are forwarded.
Open Proxy: Avoid creating an open proxy that allows users to proxy to arbitrary URLs. Always validate or restrict destination URLs.

Validation Example

Restrict proxy destinations for security:
import { proxy } from 'hono/proxy'

const ALLOWED_HOSTS = ['api.example.com', 'cdn.example.com']

app.get('/proxy/:host/:path', async (c) => {
  const host = c.req.param('host')
  const path = c.req.param('path')
  
  if (!ALLOWED_HOSTS.includes(host)) {
    return c.json({ error: 'Host not allowed' }, 403)
  }
  
  return proxy(`https://${host}/${path}`)
})

Error Handling

Handle fetch errors gracefully:
import { proxy } from 'hono/proxy'

app.get('/proxy/:path', async (c) => {
  try {
    return await proxy(`http://${originServer}/${c.req.param('path')}`)
  } catch (error) {
    console.error('Proxy error:', error)
    return c.json({ error: 'Proxy failed' }, 502)
  }
})

Type Definitions

interface ProxyRequestInit extends Omit<RequestInit, 'headers'> {
  raw?: Request
  headers?:
    | HeadersInit
    | [string, string][]
    | Record<string, string | undefined>
  customFetch?: (request: Request) => Promise<Response>
  strictConnectionProcessing?: boolean
}

function proxy(
  input: string | URL | Request,
  init?: ProxyRequestInit
): Promise<Response>

Best Practices

Validate Destinations

Always validate or allowlist destination URLs to prevent open proxy vulnerabilities

Control Headers

Explicitly specify which headers to forward rather than forwarding all headers

Remove Credentials

Set sensitive headers like Authorization to undefined when they shouldn’t be forwarded

Handle Errors

Wrap proxy calls in try-catch blocks and return appropriate error responses
The proxy helper returns a fully-formed Response object that can be returned directly from your handler or modified before returning.

Build docs developers (and LLMs) love