A reverse proxy sits in front of your application, handling incoming traffic on standard ports (80/443) and forwarding it to your app running on an internal port. This is the standard pattern for exposing Node.js apps, Docker containers, or any backend service that doesn't handle HTTP directly on port 80/443.
Why Use a Reverse Proxy
- Terminate SSL/TLS once at Nginx instead of inside every application
- Run multiple apps on one server, routed by domain
- Add caching, compression, and rate limiting at the proxy layer
- Keep your application bound to
localhost, not directly exposed to the internet
Prerequisites
- Nginx installed
- A backend application already running (e.g. Node.js on port 3000, or a Docker container publishing to
127.0.0.1:3000)
Step 1 — Confirm the Backend Is Reachable Locally
curl http://127.0.0.1:3000
Step 2 — Create the Nginx Server Block
sudo nano /etc/nginx/sites-available/myapp
server {
listen 80;
server_name myapp.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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;
proxy_cache_bypass $http_upgrade;
}
}
The Upgrade/Connection headers are required for WebSocket support; the X-Forwarded-* headers let your app see the real client IP and protocol.
Step 3 — Enable the Site
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Step 4 — Add HTTPS
sudo certbot --nginx -d myapp.example.com
Making Your App Read the Real Client IP
Your backend app should read X-Forwarded-For instead of the raw connection IP (which will otherwise always show 127.0.0.1), since Express/most frameworks support this with a "trust proxy" setting.
Proxying a Docker Container
If your app runs in Docker with -p 127.0.0.1:3000:3000, the same proxy_pass http://127.0.0.1:3000; configuration works unchanged — Nginx doesn't need to know it's a container.
Common Errors
502 Bad Gateway — the backend app isn't running, or is listening on a different port/interface than configured. See How to Fix Common Nginx Errors (502/504/403).
WebSocket connections fail — confirm the Upgrade/Connection headers are present in the config.
Best Practices
- Bind your application to
127.0.0.1, not0.0.0.0, so it's only reachable through Nginx - Always pass the
X-Forwarded-*headers - Terminate SSL at Nginx rather than inside the application
FAQ
Do I still need a firewall rule for the app's internal port?
No, if the app only binds to 127.0.0.1 — it's unreachable from outside the server regardless of firewall rules, since it's not listening on a public interface.
Related Articles
- How to Install Nginx on Ubuntu & Debian
- How to Set Up Basic Load Balancing with Nginx
- How to Fix Common Nginx Errors (502/504/403)
