Which headers does httputil.ReverseProxy remove before sending a request upstream?
answer
- per-connection, not end-to-end
- a fixed list plus what Connection names
- stripped after your hook runs
- the response side is scrubbed too
- upgrades and trailers get theirs back
basics
~10 shttputil.ReverseProxy removes the per-connection headers - Connection, Proxy-Connection, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, Te, Trailer, Transfer-Encoding and Upgrade - plus any header the Connection header names, on request and response alike.
solid answer
~40 s`httputil.ReverseProxy` drops the hop-by-hop set: `Connection`, `Proxy-Connection`, `Keep-Alive`, `Proxy-Authenticate`, `Proxy-Authorization`, `Te`, `Trailer`, `Transfer-Encoding` and `Upgrade`, together with every header whose name appears as a token in the request's own `Connection` header. The same removal runs over the upstream's response before it is copied back to the client. Two exceptions are added back deliberately: if the inbound request advertised `Te: trailers`, the proxy re-sets it so the upstream knows trailers are acceptable; and if the request was a protocol upgrade, the proxy restores `Connection: Upgrade` and the `Upgrade` token so the upgrade can be negotiated end to end. Removal happens *after* your Director or Rewrite hook, so a `Connection` header you set in the hook never reaches the upstream.
code
go · 8 linesproxy.Director = func(req *http.Request) {
req.URL.Scheme = upstream.Scheme
req.URL.Host = upstream.Host
// dropped: Connection is hop-by-hop, and stripping runs after this hook
req.Header.Set("Connection", "close")
// forwarded: an ordinary end-to-end header
req.Header.Set("X-Tenant", "acme")
}go deeper
Know that a Go reverse proxy does not copy headers blindly: a small set describing the connection itself is removed on the way through, and the rest is forwarded.
Name the set, add that the Connection header can designate further headers for removal, and explain that stripping happens after your hook and on both request and response.
Show how you debug a missing or unexpected header by comparing the outbound map you logged in the hook with what the upstream handler received, and know why upgrades and trailers are re-added.
Decide what the gateway scrubs beyond the protocol's list — internal identity or tenancy headers a caller must never be able to set — and make that policy uniform rather than per-service.
## The rule the proxy is implementing Some HTTP header fields describe the message; others describe the single TCP connection the message travelled on. The second kind belongs to one hop only. A proxy that copies them onward is telling the next hop something about a connection it is not part of, and the results range from harmless to genuinely broken — a forwarded `Transfer-Encoding` next to a re-framed body is the classic smuggling shape. `httputil.ReverseProxy` handles this for you and does not ask. ## What is removed The fixed set the package strips is: `Connection`, `Proxy-Connection`, `Keep-Alive`, `Proxy-Authenticate`, `Proxy-Authorization`, `Te`, `Trailer`, `Transfer-Encoding`, `Upgrade`. On top of that, the `Connection` header may itself *name* further headers as hop-by-hop, as a comma-separated token list. A request carrying `Connection: X-Internal-Token` is asking that `X-Internal-Token` not be forwarded, and the proxy honours it by deleting that header too — on both the request and the response. This is the part candidates usually miss: it is not a fixed blacklist, it is a fixed list plus whatever the message declares. The same removal runs over the upstream's response headers before they are copied to the `http.ResponseWriter`, so a `Connection` or `Keep-Alive` that governed the proxy-to-upstream connection does not leak out to the browser. ## Where in the pipeline it happens The removal runs **after** your `Director` or `Rewrite` hook, over the outbound header map. That ordering has a visible consequence: anything hop-by-hop you set inside the hook is thrown away moments later. Setting `Connection: close` in a Director to try to stop the proxy from reusing the upstream connection does nothing at all — connection behaviour towards the upstream is a property of the round tripper, not a header you inject. End-to-end headers you set in the same hook, such as a tenant or trace identifier, survive untouched. ## The two things put back Stripping the whole set unconditionally would break two legitimate cases, so the proxy restores them. **Trailers.** If the inbound request's `Te` field contained the `trailers` token, the proxy re-sets `Te: trailers` on the outbound request. Without it, an upstream that would have sent trailing headers has been told, by the proxy's own scrubbing, that the client cannot accept them. **Protocol upgrades.** The proxy reads the inbound `Upgrade` token before stripping. If there was one, it puts `Connection: Upgrade` and `Upgrade: <token>` back on the outbound request. That is what allows an upgrade handshake to traverse a `ReverseProxy` at all: the upstream must see the upgrade request to answer `101 Switching Protocols`, at which point the proxy stops being an HTTP relay and splices the two connections together. ## Diagnosing it The symptom is almost always "a header I set is not arriving" or "a header I did not expect is arriving". Put the proxy's view and the upstream's view side by side: log the outbound header map at the end of your hook, and log what the upstream handler actually received. If a name is present in the first and absent in the second, check whether it is on the hop-by-hop list or named in `Connection`. If it survived and you wished it had not — a stale `X-Internal-*` from a caller, say — the fix is a `Header.Del` in your hook, because the proxy only strips what the protocol calls per-connection, not what your organisation considers internal. ## Why this is the proxy's job and not yours Once you have seen the list, the temptation is to reproduce it in a hook. Don't. The package applies it after your hook, on both directions, and handles the upgrade and trailer exceptions in the right order. Your hook's job is the routing and the end-to-end headers your services agreed on.
- If Upgrade is hop-by-hop, how does an upgrade request get through a ReverseProxy at all?The proxy reads the inbound `Upgrade` token before stripping, and after stripping puts `Connection: Upgrade` and that token back on the outbound request. The upstream therefore sees a real upgrade request and can answer `101 Switching Protocols`, which the proxy recognises and handles.
- You set Connection: close in your Director and the upstream never sees it. Why?Hop-by-hop removal runs over the outbound header map after the hook, and `Connection` is on the fixed list. Connection reuse towards the upstream is decided by the round tripper the proxy dials through, not by a header you write in the hook.
- Does the same stripping apply to the upstream's response?Yes. Before the response headers are copied to the client, the proxy removes the same hop-by-hop set plus whatever the response's own `Connection` header names, so headers describing the proxy-to-upstream connection do not reach the browser.
A hop-by-hop header is like a note for the courier currently holding the parcel. The next courier gets their own note; passing the old one along tells them about a leg of the journey they were never on.
saying these in an interview costs you the question
- Thinks the proxy forwards every request header verbatim
- Believes only Connection itself is removed, not the headers it names
- Sets a Connection header in a hook and expects the upstream to see it
- Assumes protocol upgrades cannot pass through a ReverseProxy
- Thinks the stripping applies to the request but not the response