Nginx as a Reverse Proxy | Engineering Notes before<br>the browser paints. Runs synchronously, no defer/async.<br>--><br>Skip to content<br>Go back<br>Nginx as a Reverse Proxy<br>15 Jun, 2026
Your app runs on port 3000.
node server.js<br># listening on 0.0.0.0:3000<br>You open http://localhost:3000 and it works.
Then it has to go live. A real domain. HTTPS on port 443. The same app, now reachable from the internet.
The first instinct is to make the app listen on 443 directly. That works for about a day, until the app also needs to serve static files, handle TLS certificates, survive restarts without dropping traffic, limit how fast a single client can hit it, and eventually run as more than one process behind one address.
A single application process is not a good front door.
Table of contents
Open Table of contents
The Problem Nginx Is Solving
The Big Idea
How Nginx Is Built
How A Request Flows
Server Blocks
Location Blocks
proxy_pass And The Trailing Slash
Passing The Real Client Information
WebSockets Need An Upgrade
Buffering And Timeouts
HTTP/2 vs HTTP/1.1
Compressing Responses
Tuning Workers To The Server
Rate Limiting
Hiding Server Information
Restricting Who Can Embed Your Site
Multiple Backends With Upstream
Failure Modes
A Practical Config
Operating Nginx
Final Thoughts
The Problem Nginx Is Solving
An application process is good at running application logic. It is not built to be the thing the public internet talks to directly.
When the app is the only layer, it has to handle:
TLS termination and certificate renewal
serving static files efficiently
slow clients holding connections open
request size limits and timeouts
rate limiting and abuse control
routing different paths to different services
staying reachable while the app itself restarts
Every one of these is work that has nothing to do with the actual business logic.
Nginx takes that work and sits in front. The app moves behind it and goes back to doing one job.
The Big Idea
Nginx becomes the single front door.
The public talks to Nginx. Nginx talks to the app over a private connection.
client -> nginx (443, TLS) -> app (3000, plain http)<br>The app no longer faces the internet. It listens on a local port that only Nginx reaches. Nginx handles the parts that face the outside world: the certificate, the public port, the timeouts, the limits.
This is what “reverse proxy” means here. A normal proxy sits in front of clients and talks to many servers on their behalf. A reverse proxy sits in front of servers and takes requests from many clients on their behalf.
How Nginx Is Built
Nginx runs as one master process and a set of worker processes.
master process<br>├── worker process<br>├── worker process<br>└── worker process<br>The master process reads the config, binds the ports, and manages the workers. It does not handle a single request itself. When you reload the config, the master is what starts new workers and retires old ones.
The workers do the actual work. Each worker is a single process running an event loop.
The older model, used by servers like Apache in its default setup, gave each connection its own thread or process. A thousand idle clients meant a thousand threads sitting around, each using memory and forcing the kernel to switch between them.
A single Nginx worker takes a different approach. It keeps thousands of connections open at once and only touches a connection when something actually happens on it, using the kernel’s event notification (epoll on Linux). When a connection is waiting on the network, the worker is not blocked on it. It moves on and services another.
This is why a slow client holding a connection open is cheap for Nginx and expensive for a thread-per-connection app. It is also the reason Nginx is placed in front of the app: it absorbs slow and idle connections so the app only deals with complete, ready requests.
How A Request Flows
A request to https://example.com/api/users goes through a few steps.
The client opens a TLS connection to Nginx on port 443.
Nginx terminates TLS and now has a plain HTTP request.
Nginx matches the request against its server and location rules.
Nginx opens a connection to the app on 127.0.0.1:3000.
The app responds to Nginx.
Nginx sends the response back to the client over the encrypted connection.
The app sees a plain HTTP request coming from Nginx on the local machine. It never sees the TLS handshake and never sees the client directly.
Server Blocks
A server block tells Nginx how to handle traffic for a given name and port.
server {<br>listen 443 ssl;<br>server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;<br>ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {<br>proxy_pass http://127.0.0.1:3000;<br>listen 443 ssl means this block handles HTTPS.
server_name decides which block answers when a request arrives. One Nginx instance can hold many server blocks for many domains on the same port, and the Host header...