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:
-
Client Configuration (
frpc): When parsing the TOML/YAML configuration, the client populates theHostHeaderRewrite,RequestHeaders, andResponseHeadersfields in theHTTPProxyConfigstruct. -
Protocol Transmission: During the initial proxy registration,
MarshalToMsgcopies these values into themsg.NewProxyprotobuf message defined inpkg/msg/msg.go. -
Server Processing (
frps): Inserver/proxy/http.go(lines 55-62), the server constructs avhost.RouteConfigfrom the received message, transferring the fields toRewriteHost,Headers, andResponseHeadersrespectively. -
Runtime Application: The vhost router in
pkg/util/vhost/vhost.goapplies these configurations.RewriteHosttriggers specific Host-header logic, whileHeadersandResponseHeadersare 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 theHTTPProxyConfigstruct definitions forHostHeaderRewrite,RequestHeaders, andResponseHeaders, plus theMarshalToMsglogic that serializes them for network transmission. -
server/proxy/http.go: Handles server-side construction ofvhost.RouteConfig, mapping the protobuf fields to the router's rewrite and injection logic (lines 55-62). -
pkg/msg/msg.go: Defines theNewProxyprotobuf 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, andresponseHeaders.set. - Configuration resides in
pkg/config/v1/proxy.goand travels via protobuf inpkg/msg/msg.gofrom client to server. - Server-side application occurs in
server/proxy/http.goandpkg/util/vhost/vhost.go, where headers are rewritten or injected into the request/response flow. - Plugins like
https2httprespect the same configuration fields, enabling header manipulation for protocol translation scenarios. - Health checks support separate header configuration via
healthCheck.httpHeadersfor 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →