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

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 (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.

  3. Server Processing (frps): In 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 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 (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:

[[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 (lines 47-49).

HTTPS-to-HTTP Plugin Configuration

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

[[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 (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:

[[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 (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: Contains the HTTPProxyConfig struct definitions for HostHeaderRewrite, RequestHeaders, and ResponseHeaders, plus the MarshalToMsg logic that serializes them for network transmission.

  • 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: Defines the NewProxy protobuf message structure that transports header configuration from client to server.

  • 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: 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 and travels via protobuf in pkg/msg/msg.go from client to server.
  • Server-side application occurs in server/proxy/http.go and 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) 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →