Skip to content

Commit 092e88e

Browse files
committed
List and table styles to nginx-haproxy-reverse-proxy-production-tuning article
1 parent 98d2de6 commit 092e88e

2 files changed

Lines changed: 34 additions & 13 deletions

File tree

  • src/content/articles/nginx-haproxy-reverse-proxy-production-tuning

‎src/content/articles/nginx-haproxy-reverse-proxy-production-tuning/index.mdx‎

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff 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
2222

2323
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.
2424

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.
2626

2727
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.
2828

@@ -63,7 +63,7 @@ The per-location overrides are essential. A monolithic timeout configuration for
6363

6464
### HAProxy's Total-Time Semantics
6565

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.
6767

6868
```haproxy title="haproxy-timeouts.cfg"
6969
defaults
@@ -87,8 +87,13 @@ The `timeout queue` setting deserves attention. When all backend servers reach t
8787
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.
8888

8989
<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+
}}
9195
content={{
96+
figure: 'Nginx and HAProxy timeout comparison with recommended production values.',
9297
thead: {
9398
th: ['Phase', 'Nginx', 'HAProxy', 'Recommended'],
9499
},
@@ -116,7 +121,6 @@ When translating configurations between Nginx and HAProxy, the following table m
116121
},
117122
],
118123
},
119-
figure: 'Nginx and HAProxy timeout comparison with recommended production values.',
120124
}}
121125
/>
122126

@@ -178,7 +182,11 @@ If you're seeing 400 errors in HAProxy specifically, check whether `tune.bufsize
178182
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.
179183

180184
<Table
181-
variant="vertical-column-delineation-table"
185+
variant="numbered-review-table"
186+
fullWidth={false}
187+
classes={{
188+
tbody: "[&_tr>td:nth-of-type(2)]:!font-normal",
189+
}}
182190
content={{
183191
thead: {
184192
th: ['Scenario', 'Nginx Setting', 'Recommendation'],

‎src/content/articles/nginx-haproxy-reverse-proxy-production-tuning/pdf.mdx‎

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ Finally, the proxy __forwards the request__, __receives the response__, and __de
6060

6161
<Diagram
6262
src={requestFlowDiagram}
63+
size="full"
6364
title="Request flow through reverse proxy."
6465
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."
6566
/>
@@ -70,9 +71,9 @@ Nginx and HAProxy handle connections differently, and understanding these models
7071

7172
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:
7273

73-
```text
74-
max concurrent requests = (worker_processes × worker_connections) / 2
75-
```
74+
<Inset color="page-offset">
75+
`max concurrent requests = (worker_processes × worker_connections) / 2`
76+
</Inset>
7677

7778
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.
7879

@@ -236,7 +237,11 @@ The `timeout queue` setting deserves special attention. When all backend servers
236237
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.
237238

238239
<Table
239-
variant="vertical-column-delineation-table"
240+
variant="numbered-review-table"
241+
fullWidth={false}
242+
classes={{
243+
tbody: "[&_tr>td:nth-of-type(2)]:!font-normal",
244+
}}
240245
content={{
241246
thead: {
242247
th: ['Timeout', 'Nginx', 'HAProxy', 'Recommended'],
@@ -411,7 +416,11 @@ backend api
411416
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.
412417

413418
<Table
414-
variant="vertical-column-delineation-table"
419+
variant="grid-and-accent-header-table"
420+
fullWidth={false}
421+
classes={{
422+
tbody: '!text-content',
423+
}}
415424
content={{
416425
thead: {
417426
th: ['Scenario', 'Buffer Setting', 'Recommendation'],
@@ -581,6 +590,7 @@ The `slowstart` option is valuable when bringing new servers online. Without it,
581590

582591
<Diagram
583592
src={connectionPoolingDiagram}
593+
size="full"
584594
title="Connection pooling to backends."
585595
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."
586596
/>
@@ -714,7 +724,8 @@ backend canary
714724
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`.
715725

716726
<Table
717-
variant="vertical-column-delineation-table"
727+
variant="numbered-review-table"
728+
fullWidth={false}
718729
content={{
719730
thead: {
720731
th: ['Algorithm', 'Use Case', 'Session Affinity', 'Even Distribution'],
@@ -882,7 +893,8 @@ frontend tcp_passthrough
882893
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.
883894

884895
<Table
885-
variant="vertical-column-delineation-table"
896+
variant="grid-and-accent-header-table"
897+
fullWidth={false}
886898
content={{
887899
thead: {
888900
th: ['Optimization', 'Impact', 'Trade-off'],
@@ -1286,7 +1298,8 @@ http {
12861298
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.
12871299

12881300
<Table
1289-
variant="vertical-column-delineation-table"
1301+
variant="numbered-review-table"
1302+
fullWidth={false}
12901303
content={{
12911304
thead: {
12921305
th: ['Category', 'Default', 'Production', 'Why'],

0 commit comments

Comments
 (0)