Nginx Reverse Proxy Explained: Setup, HTTPS, Load Balancing and Security
When you run a web application, you rarely want users to connect to it directly. Your app may listen on a private port, it may not handle HTTPS well, and you may want to run more than one copy of it. A reverse proxy solves all of this. Nginx is the most widely used reverse proxy, and this guide shows how it works and how to set it up step by step on Ubuntu or Debian.
What is a reverse proxy?
A forward proxy sits in front of clients and hides them from the internet, like a corporate proxy. A reverse proxy does the opposite: it sits in front of your servers. Visitors connect only to the proxy, and the proxy forwards each request to the right backend and sends the answer back. The visitor never sees your backend servers.
The diagram below shows the flow.
How a request travels
Follow one visit step by step. The browser looks up your domain name and connects to your server on port 443. Nginx decrypts the HTTPS traffic, reads the host name and the path, and decides which backend should handle the request. It then opens a separate plain HTTP connection to that backend and waits for the answer. When the backend replies, Nginx passes the response back to the browser, optionally adding headers or caching it. To the visitor it looks like one single website, even when several servers are working behind it.
Why use a reverse proxy?
- One public entry point: only Nginx is exposed, while your applications stay on private ports.
- HTTPS in one place: Nginx handles TLS certificates, so each app can speak plain HTTP internally.
- Load balancing: requests are spread across several copies of your app.
- Speed: Nginx serves static files and can cache responses quickly.
- Security: you can add rate limits, hide server details and block unwanted traffic before it reaches your app.
- Several sites on one server: different domain names can point to different backends.
Step 1: Install Nginx
sudo apt update
sudo apt install nginx
sudo systemctl status nginx
If the service is active, opening your server's IP address in a browser shows the Nginx welcome page.
Step 2: Create the proxy configuration
Assume your application is running on port 3000 on the same machine. Create a file called /etc/nginx/sites-available/myapp:
server {
listen 80;
server_name example.com www.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;
}
}
The proxy_pass line tells Nginx where to send requests. The proxy_set_header lines pass useful information to your app. Without them, the backend would see every visitor as coming from 127.0.0.1 and would not know the original host name or whether the visitor used HTTPS.
Step 3: Enable the site and reload
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Always run nginx -t before reloading. It checks the configuration for mistakes, so a typo does not take your site down. If the default welcome page still appears, remove the default site link from sites-enabled.
Step 4: Add free HTTPS with Let's Encrypt
Certbot can obtain a certificate and update the Nginx configuration for you. Your domain must already point to the server.
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
sudo certbot renew --dry-run
The last command confirms that automatic renewal works. Certificates are valid for a limited time, and certbot renews them in the background.
Step 5: Load balancing across several servers
To run two copies of your app, define an upstream group and point proxy_pass at its name:
upstream myapp {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://myapp;
proxy_set_header Host $host;
}
}
By default Nginx shares requests between the servers in turn (round robin). Adding least_conn; inside the upstream block sends each request to the server with the fewest active connections, and Nginx temporarily skips a server that stops responding.
Serving static files
Letting Nginx serve images, CSS and JavaScript directly takes load off your application:
location /static/ {
root /var/www/myapp;
expires 7d;
}
Security basics
- Bind your application to
127.0.0.1so it can only be reached through Nginx, and allow only ports 80 and 443 in the firewall. - Add
server_tokens off;in thehttpblock to hide the Nginx version number. - Use rate limiting to slow down abusive clients. Define a zone in the
httpblock withlimit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;and apply it in a location withlimit_req zone=perip burst=20;. - Keep Nginx and the operating system updated.
The server itself needs protection too, so follow my guide on securing Linux servers and log in with SSH keys instead of passwords.
Troubleshooting common errors
502 Bad Gateway usually means the backend is not running or proxy_pass points to the wrong port. Test it directly with curl -I http://127.0.0.1:3000. 504 Gateway Timeout means the backend answered too slowly. For anything else, read the error log with sudo tail -f /var/log/nginx/error.log, and check the service with sudo journalctl -u nginx -n 50. If you need a refresher on these commands, see my guide to essential Linux commands.
Summary
A reverse proxy gives your application a single, secure front door. Install Nginx, forward traffic with proxy_pass, add HTTPS with certbot, and use an upstream group when you need more than one backend. It also pairs well with containers, so you may want to read my Docker for beginners guide next.