Nginx is a web server that also works as a reverse proxy, load balancer, and HTTP cache. Passing requests to backend application servers like Node.js, Python, or Go is one of its most common uses.
Here is a basic configuration to get a reverse proxy running.
Why use a reverse proxy?
When running a web application, you typically don't want to expose your application server (like a Node.js Express app running on port 3000) directly to the internet. A reverse proxy sits in front of your application server and handles all incoming traffic.
This setup hides your backend servers and filters malicious requests. It allows Nginx to handle HTTPS connections, offloading the cryptographic work from your application. If you have multiple backend instances, Nginx can distribute the traffic among them. It also serves static assets (images, CSS, JS) much faster than most application servers, freeing up your app to handle dynamic requests.
Basic Configuration
Let's assume you have a backend application running on localhost:3000 and you want Nginx to listen on port 80 and forward requests to it.
First, you'll need to create a new configuration file in Nginx's sites-available directory (on Ubuntu/Debian) or the appropriate config directory for your OS.
server {
listen 80;
server_name example.com www.example.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
# Forward original client IP
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;
}
}
Understanding the directives
listen 80: Tells Nginx to listen for HTTP traffic on port 80.server_name: The domain names this server block should respond to.location /: This block applies to all requests to the root path and below.proxy_pass http://localhost:3000: The core of the reverse proxy setup. It forwards the request to your backend.proxy_set_header: These lines ensure that important information about the original request (like the client's IP address, protocol, and the requested hostname) is passed along to the backend server. Without these, your backend app might think all requests are coming fromlocalhost.
Enabling the Configuration
Once you've saved the configuration file (e.g., /etc/nginx/sites-available/my_app), you need to enable it by creating a symlink to the sites-enabled directory:
sudo ln -s /etc/nginx/sites-available/my_app /etc/nginx/sites-enabled/
Test Nginx syntax
Before restarting Nginx, always test your configuration for syntax errors. If you have a typo and restart, Nginx will fail to start and your site will go down.
sudo nginx -t
You should see output similar to this:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
If the test passes, safely reload Nginx to apply the changes without dropping active connections:
sudo systemctl reload nginx
Testing your reverse proxy
To make sure everything works end-to-end, run a complete test.
1. Spin up a dummy backend
If you don't already have an app running on port 3000, you can quickly spin one up using Python's built-in HTTP server or a simple Node script. Here is a quick Node.js example:
Create server.js:
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end(`Hello from the backend! You requested: ${req.url}\n`);
// Log the request headers to see what Nginx is sending us
console.log('Received request for:', req.url);
console.log('Headers from Nginx:', req.headers);
});
server.listen(3000, () => {
console.log('Dummy backend listening on port 3000');
});
Run it:
node server.js
2. Send a request through Nginx
Now, open a new terminal tab and use curl to make a request to Nginx (which is listening on port 80). If you're testing locally, you can use localhost or your server's IP address.
curl -i http://localhost
You should see an HTTP 200 OK response from Nginx, and the body should be our dummy backend's message:
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Tue, 07 Jul 2026 12:00:00 GMT
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive
Hello from the backend! You requested: /
3. Verify the headers
If you look at the terminal running your Node.js dummy backend, you should see the headers that Nginx forwarded. Notice how the x-real-ip and x-forwarded-for headers contain the IP of the actual client (in this case, probably 127.0.0.1), rather than just showing the proxy's IP.
Dummy backend listening on port 3000
Received request for: /
Headers from Nginx: {
host: 'localhost',
connection: 'upgrade',
'x-real-ip': '127.0.0.1',
'x-forwarded-for': '127.0.0.1',
'x-forwarded-proto': 'http',
accept: '*/*'
}
Troubleshooting with logs
If things aren't working (for instance, you get a 502 Bad Gateway), Nginx's error logs are your best friend.
By default, on Ubuntu/Debian, you can check them here:
sudo tail -f /var/log/nginx/error.log
A 502 Bad Gateway usually means Nginx is running fine, but it can't reach your backend on port 3000. Double check that your backend app is actually running and listening on the correct port!