Configure Nginx as a reverse proxy

2 min read07 November 2026

A reverse proxy puts Nginx in front of your application: visitors talk to Nginx on port 80 or 443, and Nginx forwards the request to a process listening locally. The application is no longer exposed directly, certificates and logs are centralised, and you can change the internal port without touching DNS.

Prepare the application

The application must listen on loopback, for example 127.0.0.1:3000, not on 0.0.0.0. Check before configuring Nginx:

Bash
curl -I http://127.0.0.1:3000
sudo ss -tlnp 'sport = :3000'

An app bound to 127.0.0.1 is intentionally unreachable from the Internet. The domain must already point to the VPS with an A or AAAA record before testing the proxy.

Create the server block

Create /etc/nginx/sites-available/app.votre-domaine.fr:

Nginx
server {
    listen 80;
    listen [::]:80;
    server_name app.votre-domaine.fr;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        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;
    }
}

Enable it:

Bash
sudo ln -s /etc/nginx/sites-available/app.votre-domaine.fr /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Host keeps the requested name, which the application uses to generate URLs. X-Real-IP and X-Forwarded-For pass the client address for logs and rate limiting. X-Forwarded-Proto tells the original scheme, useful when TLS terminates at Nginx.

WebSocket, timeouts and size

For an application using WebSocket, add a map table in the http context, for example in /etc/nginx/conf.d/upgrade.conf:

Nginx
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

Then, in the location block:

Nginx
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;

Without these headers, the WebSocket handshake fails and the connection falls back to polling or closes. proxy_read_timeout defaults to 60 seconds: an idle connection longer than that is cut. For streaming or long requests, raise it and consider proxy_buffering off;.

For uploads, client_max_body_size 10m; prevents a 413 Request Entity Too Large. Put it in the server or location block.

Test and diagnose

Bash
sudo nginx -t
sudo systemctl reload nginx
curl -I -H 'Host: app.votre-domaine.fr' http://127.0.0.1
curl -I https://app.votre-domaine.fr
  • 502 Bad Gateway: the application is not running, listens on another port or another interface.
  • 504 Gateway Timeout: the application exceeds proxy_read_timeout.
  • 413: request body too large.
  • 404: wrong location or root, not a proxy problem.

Check /var/log/nginx/error.log: Nginx reports the port it contacted and the exact error. Also verify that the application reads X-Forwarded-* headers correctly and does not blindly trust X-Forwarded-For coming from the Internet.

  • Keep the application on 127.0.0.1 and never open its port in a firewall.
  • Terminate TLS with Certbot on Nginx, then redirect HTTP to HTTPS.
  • Add a health route and watch 5xx codes.
  • After every change: nginx -t, reload, then a real test.

Verify

Bash
sudo nginx -T | grep -A5 'server_name app.votre-domaine.fr'
curl -s -o /dev/null -w '%{http_code}\n' https://app.votre-domaine.fr

The code must be 200, 301 or 302, never 502. If you changed the internal port, confirm with ss -tlnp that the application still listens and that proxy_pass points to the right port.

Advertisement

Was this article helpful?

Read next