The Go standard library hands you a working reverse proxy in one struct. httputil.ReverseProxy does connection pooling, header rewriting and streaming bodies out of the box. What it leaves to you is exactly the interesting part: what happens when the upstream misbehaves.
The core is almost nothing
A director function rewrites the inbound request to point at your backend. That is the whole proxy.
proxy := &httputil.ReverseProxy{
Director: func(r *http.Request) {
r.URL.Scheme = "http"
r.URL.Host = "127.0.0.1:8081"
r.Header.Set("X-Forwarded-Host", r.Host)
},
}
http.ListenAndServe(":8080", proxy)
This forwards everything, streams responses, and handles WebSocket upgrades. For a lot of internal use it is already enough. The production gap is entirely in failure handling.
Retries belong on idempotent requests only
The single most useful addition is retrying a failed upstream connection, but only for methods that are safe to replay. Retrying a POST because the connection dropped can double-charge a customer.
proxy.ModifyResponse = func(resp *http.Response) error {
if resp.StatusCode >= 500 {
return errRetry
}
return nil
}
Pair that with an ErrorHandler that re-dispatches GET, HEAD and PUT to the next healthy backend, and leaves everything else to fail fast.
Circuit breaking keeps a sick backend from taking you down
When an upstream starts timing out, piling more requests onto it makes everything worse. A breaker trips after N consecutive failures and short-circuits to a fast error for a cooldown window, giving the backend room to recover.
- Closed - requests flow normally, failures are counted.
- Open - the breaker returns immediately, no upstream call.
- Half-open - a single probe decides whether to close again.
Tracing is a header and a hook
Generate a request ID at the edge, propagate it downstream, and log it on the way out. Now a single line in your logs ties the client request to every backend hop it touched.
The finished thing is maybe 200 lines, and unlike a black-box proxy you can read all of them. When it misbehaves at 3am, that is worth more than any feature.