All posts

NGINX as a Reverse Proxy: The Config You Actually Need

July 4, 2026 · DevTools

nginx
reverse-proxy
ssl
devops
web-server

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.

Tools mentioned in this post