Operate the proxy
Set upstream time bounds
Bound connection and response-header waits according to the application contract.
10 minute lesson
Without time bounds, a stuck upstream can hold proxy and client resources. Every hung request keeps a connection open, a client waiting, and proxy memory allocated. Enough of them and a single misbehaving backend drags the proxy — and every other site it serves — down with it.
Timeouts should distinguish connecting from waiting for response headers, because those failures mean different things. A connect that takes more than a couple of seconds on a healthy network means the host is unreachable or overwhelmed — waiting longer won’t help. A backend that accepted the connection but hasn’t sent headers is doing work, and how long that legitimately takes depends entirely on your application.
Add explicit transport limits
reverse_proxy 127.0.0.1:4001 {
transport http {
dial_timeout 3s
response_header_timeout 10s
}
}
dial_timeout 3s bounds the TCP connection attempt. response_header_timeout 10s bounds the wait between sending the request and receiving the upstream’s response headers. Two knobs, two distinct failure modes.
Verify the bound
Create a backend route that never responds:
if (request.url === '/hang') {
return // accept the request, answer nothing
}
Then confirm the proxy returns within the expected window, and time it from the client side:
curl -o /dev/null -s -w "%{http_code} %{time_total}s\n" http://127.0.0.1:8080/hang
# 502 10.003s
Ten seconds, then a 502 — that’s response_header_timeout firing. Without it, this curl would sit forever, and so would every real client stuck behind that route. Check the access log too: the entry logs the boundary, showing the duration and the upstream error, which is how you’ll tell “timeout” from “connection refused” during a real incident.
Test dial_timeout separately by pointing the proxy at a port nobody listens on and watching it give up in three seconds.
Choose values from evidence
Do not choose arbitrary tiny timeouts. A 2-second header timeout feels snappy until it kills every legitimate report generation and large query your application performs — you’ve converted your slowest features into guaranteed failures. Measure legitimate slow operations first, from access logs, then set the bound above the honest worst case.
And when one endpoint genuinely needs minutes, the fix usually isn’t a huge timeout. Design asynchronous work when a request should not wait: accept the job, return immediately, let the client poll or get notified. Timeouts protect the request path; long work belongs off it.
Lesson completed