Why Does Nginx Reverse Proxy Boost Website Performance?
The article explains how Nginx reverse proxy improves website performance by preventing backend threads from being blocked by slow clients, using an event‑driven epoll model, buffering, connection pooling, SSL termination, and proper configuration settings.
Why do backend threads get dragged by slow clients?
Whether the backend is Tomcat, Gunicorn or Node, a request follows the steps: receive request, execute business logic, return response. Business logic may finish in ~50 ms, but sending a 200 KB response over a poor 4G or Wi‑Fi link can take hundreds of milliseconds to seconds. During this time the backend thread remains occupied, unable to handle other requests. If 200 threads are blocked by 200 slow clients, the thread pool is exhausted and new requests cannot be processed.
Why doesn’t Nginx get slowed down by slow clients?
Nginx does not use a thread‑per‑connection model; it relies on an event‑driven architecture built on Linux’s epoll mechanism. A single Nginx worker can maintain tens of thousands of TCP connections, each consuming only a few kilobytes of memory. All connections are registered with epoll; when data is ready, epoll notifies Nginx, and idle connections consume no CPU.
Why is epoll more efficient than multithreading?
Thread overhead comes from three sources:
Memory : Linux gives each thread an 8 MB stack, occupying virtual address space even if not fully used. 1 000 threads would reserve 8 GB of virtual memory.
Context switches : Switching threads requires saving/restoring registers, stack pointers, and flushing TLB caches. In high‑concurrency scenarios, tens of thousands of switches per second waste CPU cycles.
Scheduling overhead : More threads increase kernel scheduler complexity and lock contention.
epoll avoids all of these because it is a pure event notification mechanism: the kernel maintains a ready‑event list, and the application calls epoll_wait to retrieve a batch of ready file descriptors, processing them in a single thread without context switches or stack allocation.
What does Nginx’s buffering mechanism actually do?
After adding Nginx, the connection between backend and Nginx is an internal network with sub‑millisecond latency and high bandwidth. The backend finishes its 50 ms business logic and hands the 200 KB response to Nginx instantly, freeing its thread.
Nginx buffers the response in memory; if the body is too large it spills to a temporary file and then streams to the client at the client’s network speed. This turns a direct, slow public‑internet transfer into a fast LAN transfer, reducing the backend thread’s occupied time from hundreds of milliseconds to a few tens of milliseconds, effectively multiplying the concurrent request capacity of the same number of threads.
The feature is called proxy buffering and is enabled by default. Relevant directives include proxy_buffering on, proxy_buffer_size (header buffer size) and proxy_buffers (number and size of body buffers).
Why does connection management also improve performance?
Thousands of client connections are multiplexed onto a small pool of keep‑alive connections to the backend. This reduces the number of TCP sockets and associated kernel buffers (each socket buffer ~87 KB) and file descriptors, cutting resource usage by two orders of magnitude.
Reusing keep‑alive connections also eliminates repeated TCP (and optional TLS) handshakes. The upstream block can specify keepalive 32 to keep up to 32 idle connections per worker.
Why don’t backends use an event‑driven model directly?
Some backends, such as Node.js, are already event‑driven, but placing Nginx in front still brings benefits. Nginx performs SSL termination, handling CPU‑intensive TLS handshakes with highly optimized implementations that support session reuse and OCSP stapling, reducing the backend’s CPU load.
Additionally, Nginx can enforce request‑level rate limiting, access control, and basic security checks (e.g., limiting request body size, limiting connections per IP), keeping such logic out of application code.
For traditional thread‑based backends like Java Spring Boot/Tomcat or Python Django/Gunicorn, Nginx’s front‑end isolates slow clients, effectively amplifying the backend’s processing capacity.
Typical Nginx reverse‑proxy configuration
upstream backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
keepalive 32;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 16k;
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;
}
}Common configuration pitfalls
proxy_http_version 1.1and proxy_set_header Connection must be set together; otherwise keep‑alive does not work because Nginx defaults to HTTP/1.0 when communicating with the backend, which does not support keep‑alive. proxy_read_timeout defines how long Nginx waits for a backend response. For long‑running endpoints (e.g., report generation) this timeout should be increased per location, not set globally too high.
Although proxy_buffering is on by default, it should be turned off for Server‑Sent Events (SSE) or long‑polling endpoints, otherwise data may be buffered and not reach the client. Use proxy_buffering off or send the header X-Accel-Buffering: no from the backend.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer XiaoFu
xiaofucode.com – a programmer learning guide driven by the pursuit of profit
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
