NGINX as a Reverse Proxy: The Config You Actually Need
July 4, 2026 · DevTools
NGINX sits in front of most web apps as a reverse proxy, TLS terminator and static file server. The directives aren't complicated, but a couple of missing lines cause the bugs everyone hits: broken client IPs, redirect loops, and single-page apps that 404 on refresh. Here's the essential server block.
A reverse proxy that forwards the right headers
The most common job is proxying to an app server (Node, Python, whatever) on a local port:
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:3000;
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_set_header X-Forwarded-Proto $scheme;
}
}
Those four proxy_set_header lines matter. Without them:
- Your app sees NGINX as the client, not the real visitor — logs and rate limits break.
- The app can't tell whether the original request was HTTP or HTTPS, which causes redirect loops behind a TLS terminator.
The NGINX Config Builder adds these headers to every reverse-proxy location automatically.
Serving a single-page app
If you're hosting a React/Vue/SPA build, deep links like /dashboard/settings don't map to files on disk. Without a fallback, refreshing that URL returns a 404. The fix is try_files:
location / {
root /var/www/app;
try_files $uri $uri/ /index.html;
}
This serves the file if it exists, otherwise hands index.html back so the client-side router takes over. For a normal static site you'd end with =404 instead of /index.html.
SSL termination and the HTTP→HTTPS redirect
Terminate TLS at NGINX and redirect all plaintext traffic:
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
The first block is a tiny port-80 server whose only job is the permanent redirect. After deploying, verify the certificate chain with the SSL Certificate Checker.
Don't forget the body size
The default client_max_body_size is 1 MB, which silently rejects larger uploads with a 413. If your app accepts file uploads, raise it:
client_max_body_size 10M;
Generate it, then tune it
Every block above is boilerplate that's easy to get subtly wrong. The NGINX Config Builder assembles a correct server block from a form — reverse proxy, static/SPA, SSL, and the HTTP→HTTPS redirect — with the forwarded headers already in place. Copy it into sites-available, run nginx -t, and reload.