Set up an nginx reverse proxy
A reverse proxy lets nginx accept visitor requests on ports 80 and 443, then forward them to an application listening on a local port such as 3000. The application stays private, while nginx handles the public face of the server.
Before you begin
- nginx installed and running. See Install and configure nginx.
- A sudo user, such as deploy, and a second SSH session so you can keep the example app running while you configure nginx.
- A DNS record for the name you want, such as app.example.com, pointing at 203.0.113.10. You can also test with a Host header.
Step 1: Start a example app on a local port
To practice without writing an application, use Python's built-in web server. The --bind 127.0.0.1 option makes it listen only on the server itself, so the outside world cannot reach it directly. That is exactly the setup a reverse proxy expects.
mkdir -p ~/example-app
echo "<h1>Hello from the app behind nginx</h1>" > ~/example-app/index.html
cd ~/example-app
python3 -m http.server 3000 --bind 127.0.0.1
Serving HTTP on 127.0.0.1 port 3000 (http://127.0.0.1:3000/) ...
Leave this running. Open a second SSH session for the next steps. Press Ctrl+C in this window when you are finished.
Step 2: Confirm the app listens locally
From the second session, check that something is listening on port 3000 and only on the loopback address 127.0.0.1. Then request the page directly.
sudo ss -tlnp | grep 3000
curl -I http://127.0.0.1:3000
LISTEN 0 5 127.0.0.1:3000 0.0.0.0:*
HTTP/1.0 200 OK
Step 3: Write the proxy configuration
Create a site file with sudo nano /etc/nginx/sites-available/app.example.com and enter the following. The proxy_pass line sends requests to the app. The proxy_set_header lines pass the original host name and visitor details along, so the app can see them.
server {
listen 80;
listen [::]:80;
server_name app.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;
}
}
Step 4: Enable the site and reload
Link the file into sites-enabled, test the configuration, and reload nginx.
sudo ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Step 5: Test through nginx
Request the site on port 80 with the Host header. You should see the example app's page, served through nginx.
curl -H "Host: app.example.com" http://203.0.113.10/
<h1>Hello from the app behind nginx</h1>
Step 6: Confirm the app port stays closed
The only public ports should be SSH and web traffic. Check the firewall, which should not list port 3000:
sudo ufw status
Because the app is bound to 127.0.0.1 and no firewall rule opens port 3000, visitors can reach the app only through nginx.
Troubleshooting
- 502 Bad Gateway. nginx cannot reach the app. Confirm it is running with the
sscommand from Step 2, and check the error log withsudo tail -n 20 /var/log/nginx/error.log. - The app stops when you close SSH. A foreground process ends with its terminal. For a long-running app, run it as a systemd service so it starts at boot and restarts on failure.
- The app sees 127.0.0.1 as every visitor's address. The app must read the
X-Forwarded-Forheader. Check its documentation for a proxy or trusted-proxy setting. - nginx -t reports an error. The message names the file and line. Fix it and test again.
Next steps
To run a real application, make sure it listens on a local port and set it up as a service. WebSocket apps need extra Upgrade headers in the location block. For HTTPS, see Enable HTTPS with free TLS certificates.
Something not working? Chat or email our support team and we will help you get unstuck.
Contact support