DevOps line
Nginx reverse proxy
Every visitor meets one mailroom first, so it can open the envelopes, hand out brochures and share letters between desks.
Stops explain each step. The film plays the whole ride.
- A letter going in
- Stuck waiting
- A reply coming back
- Something went wrong
A letter reaches the mailroom
You post a letter asking for the company logo. Every letter reaches one place first, the mailroom, so that is where we start.
Next stop: The mailroom opens the envelope
All stops
The everyday version
A company mailroom works the same way. Every part of Nginx has a twin in the building.
- Youis the Browser
Post letters and wait for the replies.
- The mailroomis the Nginx
Opens the envelopes, hands out brochures itself and shares real questions between the desks.
- The brochure rackis the Static files
Holds pictures and pages that never change, ready to hand out.
- Desk 1is the App server 1
Reads each letter it is given and writes the answer.
- Desk 2is the App server 2
An identical desk, so the mailroom always has a spare.
- A closed deskis a 502 or 504 error
The mailroom finds every desk dark, or gets tired of waiting. Either way, you get a note instead of a reply.
Interview questions
Say the short answer first. Go deeper only if they ask.
What is a reverse proxy, and how is it different from a forward proxy?
A forward proxy acts for clients. A reverse proxy acts for servers.
RFC 9110 calls a reverse proxy a gateway: it acts as the origin server for the outbound connection and forwards requests inward. Clients only ever talk to it, so TLS, load balancing and static files can live in one place.
“A reverse proxy and a forward proxy are the same thing.”
Why put Nginx in front of Uvicorn?
It ends TLS, serves static files, buffers slow clients and shares the load.
Uvicorn’s docs say a proxy in front “may not be necessary, but is recommended for additional resilience”. Nginx buffers requests and responses by default (proxy_request_buffering and proxy_buffering are on), so slow clients do not tie up an app worker.
“Because Uvicorn cannot serve HTTP.”
How does Nginx choose between app servers?
Round-robin by default: each request goes to the next server in turn.
The default is weighted round-robin (weight=1 each). least_conn picks the server with the fewest active connections, ip_hash keeps a client on one server, and weight favours bigger machines. Plain round-robin ignores how busy a server is.
“Round-robin sends traffic to the least busy server.”
How does the app learn the real client IP and scheme?
Nginx has to pass them in headers such as X-Forwarded-For and X-Forwarded-Proto.
Nginx adds neither by default. Set proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; and proxy_set_header X-Forwarded-Proto $scheme;. Uvicorn only trusts them from IPs in --forwarded-allow-ips (default 127.0.0.1 and ::1), so trust just your proxy.
“request.client.host is always the user.”
What do nginx -t and nginx -s reload do?
nginx -t checks the config. nginx -s reload applies it without a restart.
On reload the master process checks the new config and tries to apply it. If that fails, nginx keeps running with the old config. If it works, nginx starts new workers and asks the old ones to shut down gracefully. Run nginx -t first.
“Edit the file and it applies automatically.”
What happens when an app server fails or is slow?
By default nginx tries the next server on an error or a timeout.
The default is proxy_next_upstream error timeout. Failures count toward max_fails (1) within fail_timeout (10s), and a failed server is skipped meanwhile. If no server answers, you get 502 or 504. Open-source nginx has passive checks only; active health checks need NGINX Plus.
“Nginx probes every app server in the background.”
What must you add to proxy WebSockets?
Pass the Upgrade and Connection headers explicitly.
They are hop-by-hop headers, so nginx does not forward them unless told: proxy_set_header Upgrade $http_upgrade; and proxy_set_header Connection "upgrade";. Before 1.29.7 you also needed proxy_http_version 1.1;. An idle WebSocket closes after 60 seconds unless you raise proxy_read_timeout or send pings.
“WebSockets work through nginx with no extra config.”
Cheat-sheet
Commands you will actually type.
nginx -t && nginx -s reloadCheck the config first, then reload it gracefully.
upstream backend { server 127.0.0.1:8001; server 127.0.0.1:8002; }A pool of two app servers, shared round-robin by default.
location / { proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }Forward to the pool, keep the original host, and pass the client IP along.
listen 443 ssl; ssl_certificate /etc/ssl/site.crt; ssl_certificate_key /etc/ssl/site.key;Terminate TLS: decrypt HTTPS here and talk plain HTTP to the pool.
return 301 https://$host$request_uri;In the port 80 server block: redirect plain HTTP to HTTPS.
location /static/ { root /var/www; expires 30d; }Serve /var/www/static/... straight from disk, with a 30 day cache lifetime.
proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";Let WebSockets through (before nginx 1.29.7, also add proxy_http_version 1.1;).
tail -f /var/log/nginx/error.logWatch errors live. A 502 or 504 usually leaves a clue here.
Sources
- RFC 9110: HTTP semantics (gateway, 502, 504)
- nginx: HTTP load balancing
- nginx: ngx_http_upstream_module
- nginx: ngx_http_upstream_hc_module (active checks)
- nginx: ngx_http_proxy_module
- nginx: ngx_http_core_module
- nginx: ngx_http_headers_module (expires)
- nginx: ngx_http_rewrite_module (return)
- nginx: beginner’s guide
- nginx: controlling nginx
- nginx: command-line parameters
- nginx: configuring HTTPS servers
- nginx: WebSocket proxying
- nginx source: upstream error statuses
- NGINX docs: reverse proxy
- NGINX docs: terminating SSL/TLS
- Uvicorn: deployment
- Uvicorn: settings