NewHalo Edge 3 is here: HTTP/3 everywhere and 42 edge locations.Learn more
Docs / Web servers

Set up an nginx reverse proxy

Web servers8 min readUbuntu 24.04 LTS

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.

Tip: Once the proxy works, add HTTPS with Enable HTTPS with free TLS certificates. Certbot's nginx plugin edits this same file for you.

Troubleshooting

  • 502 Bad Gateway. nginx cannot reach the app. Confirm it is running with the ss command from Step 2, and check the error log with sudo 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-For header. 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