# How to Configure HTTP Header Rewriting and Custom Headers in frp Proxies

> Learn to configure HTTP header rewriting and custom headers in frp proxies using hostHeaderRewrite, requestHeaders.set, and responseHeaders.set for effective request modification.

- Repository: [fatedier/frp](https://github.com/fatedier/frp)
- Tags: how-to-guide
- Published: 2026-02-26

---

**frp enables HTTP header rewriting and custom header injection through three client-side configuration fields—`hostHeaderRewrite`, `requestHeaders.set`, and `responseHeaders.set`—which modify requests and responses as they traverse HTTP and HTTPS proxies.**

frp (Fast Reverse Proxy) is a widely-used open-source reverse proxy application written in Go that exposes local services behind NATs and firewalls to the internet. When proxying HTTP traffic through frp, you frequently need to manipulate headers to ensure backend services receive correct Host values, inject authentication tokens, or add debugging information. The fatedier/frp source code implements this through specific configuration fields defined in the client's proxy definitions.

## Configuration Fields for Header Manipulation

frp provides three distinct fields for HTTP header manipulation, all defined in the `HTTPProxyConfig` struct within [`pkg/config/v1/proxy.go`](https://github.com/fatedier/frp/blob/main/pkg/config/v1/proxy.go) (lines 300-306). These fields are serialized into the `msg.NewProxy` protobuf message during client initialization via the `MarshalToMsg` method (lines 312-321).

### Host Header Rewriting

The **`hostHeaderRewrite`** field rewrites the incoming request's Host header before forwarding it to the backend service. This is essential when your backend application expects a specific domain name but receives requests through a local IP or different domain.

### Custom Request Headers

The **`requestHeaders.set.<key>`** field adds or overrides arbitrary headers in the request sent from the frp server to your backend service. Replace `<key>` with your header name (hyphens allowed). This injects metadata such as client identifiers or authentication tokens.

### Custom Response Headers

The **`responseHeaders.set.<key>`** field adds or overrides headers in the HTTP response returned from the backend to the client. This is useful for injecting CORS headers, cache-control directives, or debugging markers visible to the end user.

## How Header Transformation Works in frp

The header modification logic follows a clear path from client configuration to runtime execution:

1. **Client Configuration (`frpc`)**: When parsing the TOML/YAML configuration, the client populates the `HostHeaderRewrite`, `RequestHeaders`, and `ResponseHeaders` fields in the `HTTPProxyConfig` struct.

2. **Protocol Transmission**: During the initial proxy registration, `MarshalToMsg` copies these values into the `msg.NewProxy` protobuf message defined in [`pkg/msg/msg.go`](https://github.com/fatedier/frp/blob/main/pkg/msg/msg.go).

3. **Server Processing (`frps`)**: In [`server/proxy/http.go`](https://github.com/fatedier/frp/blob/main/server/proxy/http.go) (lines 55-62), the server constructs a `vhost.RouteConfig` from the received message, transferring the fields to `RewriteHost`, `Headers`, and `ResponseHeaders` respectively.

4. **Runtime Application**: The vhost router in [`pkg/util/vhost/vhost.go`](https://github.com/fatedier/frp/blob/main/pkg/util/vhost/vhost.go) applies these configurations. `RewriteHost` triggers specific Host-header logic, while `Headers` and `ResponseHeaders` are injected into the outbound request and inbound response streams.

Plugin implementations such as `https2http` and `https2https` reuse these same protobuf fields. The plugin code in [`pkg/plugin/client/https2http.go`](https://github.com/fatedier/frp/blob/main/pkg/plugin/client/https2http.go) (lines 65-71) reads `HostHeaderRewrite`, `RequestHeaders`, and `ResponseHeaders` identically to standard HTTP proxies.

## Configuration Examples

### Basic HTTP Proxy with Header Manipulation

This configuration exposes a local web server while rewriting the Host header and injecting custom headers in both directions:

```toml
[[proxies]]
name = "web01"
type = "http"
localIP = "127.0.0.1"
localPort = 80
customDomains = ["app.example.com"]

# Rewrite the Host header that the backend receives

hostHeaderRewrite = "internal-app.local"

# Add a custom request header (sent to the backend)

requestHeaders.set.x-from-where = "frp"
requestHeaders.set.x-request-id = "unique-id"

# Add a custom response header (sent back to the client)

responseHeaders.set.x-proxy-by = "frp-server"
responseHeaders.set.x-frame-options = "DENY"

```

This example appears in the official full configuration file at [`conf/frpc_full_example.toml`](https://github.com/fatedier/frp/blob/main/conf/frpc_full_example.toml) (lines 47-49).

### HTTPS-to-HTTP Plugin Configuration

When using the `https2http` plugin to terminate TLS locally, header manipulation works identically:

```toml
[[proxies]]
name = "secure-web"
type = "https"
customDomains = ["secure.example.com"]

[proxies.plugin]
type = "https2http"
localAddr = "127.0.0.1:80"
crtPath = "./server.crt"
keyPath = "./server.key"

# Plugin-level host rewrite

hostHeaderRewrite = "localhost"

# Plugin-level request header injection

requestHeaders.set.x-scheme = "https"
requestHeaders.set.x-forwarded-proto = "https"

```

The plugin implementation in [`pkg/plugin/client/https2http.go`](https://github.com/fatedier/frp/blob/main/pkg/plugin/client/https2http.go) (lines 65-71) processes these fields through the same protobuf mechanism as standard HTTP proxies.

### Health Check Custom Headers

For proxies with HTTP health checks, you can customize the headers sent during health probe requests separately from the main traffic:

```toml
[[proxies]]
name = "web01"
type = "http"
localIP = "127.0.0.1"
localPort = 80
healthCheck.type = "http"
healthCheck.path = "/health"
healthCheck.intervalSeconds = 30

# Add a header to the health-check request only

healthCheck.httpHeaders = [
  { name = "x-from-where", value = "frp" },
  { name = "authorization", value = "Bearer health-token" }
]

```

These headers are defined in the `HealthCheckConfig` struct within [`pkg/config/v1/proxy.go`](https://github.com/fatedier/frp/blob/main/pkg/config/v1/proxy.go) (lines 100-103) and apply exclusively to health check probes.

## Key Source Files and Implementation Details

Understanding the following source files helps debug header behavior:

- **[`pkg/config/v1/proxy.go`](https://github.com/fatedier/frp/blob/main/pkg/config/v1/proxy.go)**: Contains the `HTTPProxyConfig` struct definitions for `HostHeaderRewrite`, `RequestHeaders`, and `ResponseHeaders`, plus the `MarshalToMsg` logic that serializes them for network transmission.

- **[`server/proxy/http.go`](https://github.com/fatedier/frp/blob/main/server/proxy/http.go)**: Handles server-side construction of `vhost.RouteConfig`, mapping the protobuf fields to the router's rewrite and injection logic (lines 55-62).

- **[`pkg/msg/msg.go`](https://github.com/fatedier/frp/blob/main/pkg/msg/msg.go)**: Defines the `NewProxy` protobuf message structure that transports header configuration from client to server.

- **[`pkg/util/vhost/vhost.go`](https://github.com/fatedier/frp/blob/main/pkg/util/vhost/vhost.go)**: Implements the core HTTP router that executes the actual header rewriting and injection at runtime.

- **[`pkg/plugin/client/https2http.go`](https://github.com/fatedier/frp/blob/main/pkg/plugin/client/https2http.go)**: Demonstrates how plugins consume the same header configuration fields, ensuring consistent behavior across proxy types.

## Summary

- **frp** modifies HTTP headers through three client-side fields: `hostHeaderRewrite`, `requestHeaders.set`, and `responseHeaders.set`.
- **Configuration** resides in [`pkg/config/v1/proxy.go`](https://github.com/fatedier/frp/blob/main/pkg/config/v1/proxy.go) and travels via protobuf in [`pkg/msg/msg.go`](https://github.com/fatedier/frp/blob/main/pkg/msg/msg.go) from client to server.
- **Server-side application** occurs in [`server/proxy/http.go`](https://github.com/fatedier/frp/blob/main/server/proxy/http.go) and [`pkg/util/vhost/vhost.go`](https://github.com/fatedier/frp/blob/main/pkg/util/vhost/vhost.go), where headers are rewritten or injected into the request/response flow.
- **Plugins** like `https2http` respect the same configuration fields, enabling header manipulation for protocol translation scenarios.
- **Health checks** support separate header configuration via `healthCheck.httpHeaders` for probe requests.

## Frequently Asked Questions

### Can I use HTTP header rewriting with TCP proxies?

No, header rewriting applies exclusively to **HTTP** and **HTTPS** proxy types (including plugin-based proxies like `https2http`). TCP proxies operate at the transport layer and cannot inspect or modify HTTP headers. If you need header manipulation for TLS traffic, use the `https2http` or `https2https` plugins.

### What is the difference between `hostHeaderRewrite` and `requestHeaders.set.Host`?

The `hostHeaderRewrite` field triggers specific logic in the vhost router ([`pkg/util/vhost/vhost.go`](https://github.com/fatedier/frp/blob/main/pkg/util/vhost/vhost.go)) designed for Host header manipulation and interacts with the routing system. Using `requestHeaders.set.Host` would technically override the header value but lacks the integrated routing optimizations. For reliable Host header modification, always use `hostHeaderRewrite`.

### Do custom headers work with load balancing configurations?

Yes, the header configuration applies to the proxy definition regardless of whether load balancing is enabled. However, health check headers configured via `healthCheck.httpHeaders` are sent only during health probe requests to individual backend instances, while `requestHeaders` and `responseHeaders` apply to all proxied traffic passing through that frontend.

### Can I remove or unset headers using these configuration fields?

The current implementation in fatedier/frp only supports **setting** or **overwriting** headers via the `.set` syntax. There is no native configuration syntax for removing headers entirely (setting them to empty values is possible, but this differs from deletion). For complex header manipulation including deletions, you must implement a custom middleware plugin or handle it at the application layer.