Programming Intermediate 11 min

How to Set Up Nginx as a Reverse Proxy for a Node App

Your Node app listens on port 3000. Nginx sits in front of it, handles TLS termination, serves static files at full speed, and lets you run multiple apps on one server — all on the standard ports 80 and 443. This guide wires it all up in about 15 minutes.

Step-by-step

  1. 1

    Understand why a reverse proxy

    Running node server.js directly on port 80 requires root — a bad idea. A reverse proxy solves four things at once:

    • TLS termination — Nginx handles HTTPS; your app only sees plain HTTP internally
    • Static asset caching — Nginx serves /public files without touching Node
    • Multiple apps on one host — route api.example.com to port 3001, app.example.com to port 3000
    • No port numbers in URLs — users see https://example.com, not http://example.com:3000
  2. 2

    Install Nginx

    Install Nginx from the official package repo and verify it starts clean.

    bash
    sudo apt update
    sudo apt install -y nginx
    sudo systemctl enable nginx
    sudo systemctl start nginx
    
    # Confirm it's running
    sudo systemctl status nginx
  3. 3

    Create a server block config file

    Drop a new config file in sites-available. Replace example.com with your actual domain (or the server's IP if you're testing without DNS).

    bash
    sudo nano /etc/nginx/sites-available/myapp.conf
  4. 4

    Write the proxy_pass block

    This is the minimal working config. The four proxy_set_header lines are not optional — without them your app sees 127.0.0.1 as every visitor's IP and can't detect the original protocol.

    nginx
    server {
        listen 80;
        server_name example.com www.example.com;
    
        location / {
            proxy_pass         http://127.0.0.1:3000;
            proxy_http_version 1.1;
    
            # Critical headers
            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;
        }
    }
  5. 5

    Add WebSocket upgrade headers

    If your app uses WebSockets (Socket.IO, ws, etc.), add the Upgrade and Connection headers inside the location / block. Without these, the WebSocket handshake fails silently.

    nginx
        # Inside location / — add these two lines alongside the others
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection "upgrade";
  6. 6

    Enable the site and test the config

    Symlink the config into sites-enabled, then always run nginx -t before reloading. A typo in the config can take down every site on the machine.

    bash
    sudo ln -s /etc/nginx/sites-available/myapp.conf /etc/nginx/sites-enabled/
    
    # Test — must say "syntax is ok"
    sudo nginx -t
    
    # Reload (zero downtime — no restart needed)
    sudo systemctl reload nginx
  7. 7

    Keep Node running with pm2

    Nginx is now a system service that survives reboots. Your Node app needs the same treatment. pm2 is the simplest option.

    bash
    npm install -g pm2
    
    # Start the app
    pm2 start server.js --name myapp
    
    # Generate and enable the startup script so pm2 restarts on reboot
    pm2 startup
    # Run the printed command, then:
    pm2 save
    
    # Useful commands
    pm2 status
    pm2 logs myapp
    pm2 restart myapp
  8. 8

    Verify the proxy is working

    With your Node app running and Nginx reloaded, hit the server. The response should come from Node even though you're connecting on port 80.

    bash
    # From your local machine (replace with your server IP or domain)
    curl -I http://example.com
    
    # Expect: HTTP/1.1 200 OK (or whatever your app returns)
    # The "Server: nginx" header confirms Nginx is in the chain
  9. 9

    Next step — add free HTTPS with Certbot

    Once port 80 is working, adding SSL takes one command. Certbot will edit your Nginx config automatically and set up auto-renewal. See the follow-up guide: How to Get a Free SSL Certificate with Let's Encrypt.

    bash
    # Preview only — full steps in the next guide
    sudo apt install -y certbot python3-certbot-nginx
    sudo certbot --nginx -d example.com -d www.example.com

Tips & gotchas

  • Always run <code>sudo nginx -t</code> before reloading. A bad config file takes down every site on the server.
  • Use <code>upstream</code> blocks when load-balancing across multiple Node processes: <code>upstream myapp { server 127.0.0.1:3000; server 127.0.0.1:3001; }</code>
  • Set <code>proxy_read_timeout 300;</code> for long-running requests (file uploads, AI endpoints). The default 60 s will timeout them.
  • Add <code>client_max_body_size 20M;</code> if your app accepts file uploads — Nginx's default 1 MB limit will reject them silently.
  • Remove the default Nginx site: <code>sudo rm /etc/nginx/sites-enabled/default</code> — it can shadow your config.

Wrapping up

Nginx is now intercepting all traffic on port 80 and forwarding it to your Node app. Your app is isolated from the internet, protected behind a process manager, and ready for HTTPS. The next logical step is running Certbot to get a free TLS certificate — that guide picks up exactly where this one ends.

#Nginx #DevOps #Node.js
Back to all guides

Need Help With Your Project?

Book a free 30-minute consultation to discuss your technical challenges and explore solutions together.