You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 092e88e
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: src/content/articles/nginx-haproxy-reverse-proxy-production-tuning/index.mdx
+13-5Lines changed: 13 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -22,7 +22,7 @@ Here's a scenario I've seen play out more times than I'd like. A team deploys th
22
22
23
23
It turns out `proxy_read_timeout` defaults to 60 seconds, which sounds generous until you realize a few slow endpoints occasionally take 65 seconds. Database queries, external API calls, report generation — any of these can push response times past that threshold. Meanwhile, the team is chasing ghosts in their application code.
24
24
25
-
Nginx and HAProxy ship with defaults optimized for getting started quickly, not for handling production traffic. Default timeouts assume fast backends. Default buffer sizes assume small requests. When real load arrives — slow clients on mobile networks, large authentication headers, backends that occasionally need extra time — these defaults fail in ways that are hard to diagnose.
25
+
<Highlighter>Nginx and HAProxy ship with defaults optimized for getting started quickly, not for handling production traffic.</Highlighter> Default timeouts assume fast backends. Default buffer sizes assume small requests. When real load arrives — slow clients on mobile networks, large authentication headers, backends that occasionally need extra time — these defaults fail in ways that are hard to diagnose.
26
26
27
27
The good news: two configuration areas account for most proxy-related outages. Fix your timeouts and buffers, and you'll eliminate the majority of mysterious 502s and 400s. Let's start with the more common culprit.
28
28
@@ -63,7 +63,7 @@ The per-location overrides are essential. A monolithic timeout configuration for
63
63
64
64
### HAProxy's Total-Time Semantics
65
65
66
-
HAProxy's timeout model differs in one critical way: `timeout client` and `timeout server` cover the __entire__ request or response, not per-read operations. If you set `timeout server 60s` and the backend takes 30 seconds to send the first byte, then another 35 seconds to send the body, the connection times out — even though data was flowing the whole time.
66
+
HAProxy's timeout model differs in one critical way: `timeout client` and `timeout server` cover the _entire_ request or response, not per-read operations. If you set `timeout server 60s` and the backend takes 30 seconds to send the first byte, then another 35 seconds to send the body, the connection times out — even though data was flowing the whole time.
67
67
68
68
```haproxy title="haproxy-timeouts.cfg"
69
69
defaults
@@ -87,8 +87,13 @@ The `timeout queue` setting deserves attention. When all backend servers reach t
87
87
When translating configurations between Nginx and HAProxy, the following table maps the key timeout settings. They're not exact equivalents — Nginx's per-read semantics differ from HAProxy's total-time semantics — but this helps when translating configurations.
88
88
89
89
<Table
90
-
variant="vertical-column-delineation-table"
90
+
variant="numbered-review-table"
91
+
fullWidth={false}
92
+
classes={{
93
+
tbody: "[&_tr>td:nth-of-type(2)]:!font-normal",
94
+
}}
91
95
content={{
96
+
figure: 'Nginx and HAProxy timeout comparison with recommended production values.',
92
97
thead: {
93
98
th: ['Phase', 'Nginx', 'HAProxy', 'Recommended'],
94
99
},
@@ -116,7 +121,6 @@ When translating configurations between Nginx and HAProxy, the following table m
116
121
},
117
122
],
118
123
},
119
-
figure: 'Nginx and HAProxy timeout comparison with recommended production values.',
120
124
}}
121
125
/>
122
126
@@ -178,7 +182,11 @@ If you're seeing 400 errors in HAProxy specifically, check whether `tune.bufsize
178
182
The following table summarizes buffer settings by scenario. Note that the directive names are Nginx-specific; for HAProxy, adjust `tune.bufsize` to accommodate larger headers or bodies.
Copy file name to clipboardExpand all lines: src/content/articles/nginx-haproxy-reverse-proxy-production-tuning/pdf.mdx
+21-8Lines changed: 21 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -60,6 +60,7 @@ Finally, the proxy __forwards the request__, __receives the response__, and __de
60
60
61
61
<Diagram
62
62
src={requestFlowDiagram}
63
+
size="full"
63
64
title="Request flow through reverse proxy."
64
65
description="Sequence diagram with three participants: Client, Proxy, and Backend. Phase 1 is Client Connection, where the client performs a TCP handshake with the proxy and then a TLS handshake. Phase 2 is Request Receipt, where the client sends HTTP request headers followed by the HTTP request body to the proxy. Phase 3 is Backend Connection, where the proxy opens or reuses a TCP connection to the backend. Phase 4 is Request Forward, where the proxy sends the HTTP request to the backend. Phase 5 is Response Receipt, where the backend returns the HTTP response to the proxy. Phase 6 is Response Delivery, where the proxy sends the HTTP response back to the client. A final note indicates that the client connection may remain open for keep-alive reuse. The diagram maps the full path of a proxied request and highlights where connection, timeout, and buffering settings influence behavior."
65
66
/>
@@ -70,9 +71,9 @@ Nginx and HAProxy handle connections differently, and understanding these models
70
71
71
72
Nginx uses an __event-driven, single-threaded worker__ model. Each worker process handles thousands of connections using non-blocking I/O, but each connection — whether from a client or to a backend — uses one slot from the `worker_connections` pool. Since a proxied request needs both a client connection and a backend connection, each request consumes at least two slots. The formula for maximum concurrent requests is roughly:
72
73
73
-
```text
74
-
max concurrent requests = (worker_processes × worker_connections) / 2
With 4 workers and 4096 connections each, you get approximately 8,192 concurrent requests. Keep-alives complicate this — idle connections still consume slots, so you may hit connection limits before CPU or memory becomes a bottleneck.
78
79
@@ -236,7 +237,11 @@ The `timeout queue` setting deserves special attention. When all backend servers
236
237
The following table maps Nginx and HAProxy timeout settings to each other. They're not exact equivalents — Nginx's per-read semantics differ from HAProxy's total-time semantics — but this helps when translating configurations between the two.
The `http-request deny if { req.body_size gt 104857600 }` line is worth noting: it rejects oversized requests __before__ buffering them. Without this, HAProxy would accept a 10GB upload attempt before rejecting it — wasting bandwidth and potentially filling disk.
@@ -581,6 +590,7 @@ The `slowstart` option is valuable when bringing new servers online. Without it,
581
590
582
591
<Diagram
583
592
src={connectionPoolingDiagram}
593
+
size="full"
584
594
title="Connection pooling to backends."
585
595
description="Top-down flowchart with three grouped sections. The first section, Client Connections, contains three separate clients labeled Client 1, Client 2, and Client 3. All three connect to a central proxy component labeled HAProxy or Nginx in the second section, Proxy keepalive pool. That proxy feeds into a highlighted Keep-alive Pool node labeled 32 connections. The keep-alive pool then connects to two backend servers in the third section, Backend Servers, labeled Backend 1 and Backend 2, each with maxconn 500. The diagram shows that many client connections can be funneled through a smaller reusable pool of backend connections, which reduces repeated TCP or TLS handshake overhead and lowers backend connection churn."
586
596
/>
@@ -714,7 +724,8 @@ backend canary
714
724
The canary deployment example shows a common pattern: routing 5% of traffic to a new version while 95% goes to the stable version. If the canary starts failing health checks or returning errors, you can quickly shift traffic back to stable by adjusting weights or marking the canary server as `disabled`.
The TCP passthrough frontend (`mode tcp`) is useful when you need to route encrypted traffic without terminating TLS. HAProxy inspects just the SNI hostname in the TLS handshake to make routing decisions, then passes the encrypted connection directly to the backend. This preserves end-to-end encryption when required.
883
894
884
895
<Table
885
-
variant="vertical-column-delineation-table"
896
+
variant="grid-and-accent-header-table"
897
+
fullWidth={false}
886
898
content={{
887
899
thead: {
888
900
th: ['Optimization', 'Impact', 'Trade-off'],
@@ -1286,7 +1298,8 @@ http {
1286
1298
Note the `buffer=16k flush=2m` on the access log — this buffers log writes to reduce disk I/O, flushing every 2 minutes or when the buffer fills. At high request rates, unbuffered logging becomes a bottleneck.
0 commit comments