How7o
  • Home
  • Tools
  • Prank Screens
  • Learn
  • Blog
  • Contact
Reading: How to Set Up Nginx as a Reverse Proxy for Node.js on Ubuntu
Share
How7oHow7o
Font ResizerAa
  • OS
Search
  • Home
  • Tools
  • Prank Screens
  • Learn
  • Blog
  • Contact
Follow US
© 2024–2026 How7o. All rights reserved.
How7o > Free Laravel, PHP, WordPress & Server Tutorials > Server Management > How to Set Up Nginx as a Reverse Proxy for Node.js on Ubuntu
Server Management

How to Set Up Nginx as a Reverse Proxy for Node.js on Ubuntu

how7o
By how7o
Last updated: May 23, 2026
11 Min Read
Nginx as a reverse proxy for a Node.js app on Ubuntu
SHARE

To set up Nginx as a reverse proxy for a Node.js app on Ubuntu, run your Node process on a high port (typically 3000), then create an Nginx server block on port 80/443 that proxy_passes every request to http://localhost:3000. The result is one public-facing Nginx for TLS termination and static serving, with Node handling the application logic in its private port.

Contents
  • Why front Node with Nginx
  • Step 1 — get Node and the app running
  • Step 2 — install Nginx
  • Step 3 — create the reverse-proxy server block
  • Step 4 — point DNS and verify
  • Step 5 — process management with PM2
  • Step 5 (alternative) — systemd unit
  • Step 6 — add SSL
  • Step 7 — multiple apps on one server
  • Tune for production
  • Frequently asked questions
  • Related guides
  • References

Last verified: 2026-05-17 on Ubuntu 24.04 with Nginx 1.24 and Node.js 20 LTS.

Why front Node with Nginx

  • TLS termination. Nginx does the cert handshake, decrypts, and passes plain HTTP upstream. Faster than Node’s TLS, and Certbot’s --nginx plugin handles renewal automatically.
  • Static-file serving. Public files (images, CSS, the built JS bundle) serve directly from Nginx without ever touching Node — much faster and uses less memory.
  • Slow-client protection. Nginx buffers the request from a slow client before passing it on, so Node’s event loop isn’t tied up waiting for a 5 KB POST to dribble in over a flaky network.
  • Multiple apps, one server. Run App A on port 3000, App B on 3001, route each by hostname or path in Nginx.
  • Process isolation. Node runs as an unprivileged user on a non-public port; Nginx is the only thing facing the internet.
Nginx as a reverse proxy for Node.js — Nginx terminates TLS, proxy_pass to localhost:3000, PM2 manages process

Step 1 — get Node and the app running

Install Node if you haven’t already (see How to Install the Latest Node.js on Ubuntu) and start the app on a high port:

cd /var/www/myapp
node server.js
# App listening on http://localhost:3000

Confirm the app responds on its port from the server itself:

curl -sI http://localhost:3000
# HTTP/1.1 200 OK

If curl can reach it, Nginx can too. Leave Node running for the moment; we’ll wire up process management in Step 5.

Step 2 — install Nginx

sudo apt update
sudo apt install -y nginx

sudo systemctl enable --now nginx
sudo ufw allow 'Nginx Full'                # opens 80 and 443

Visit http://your-server-ip in a browser — you should see the default Nginx landing page. That’s the proof Nginx is listening on port 80; we’ll replace that landing page with the reverse-proxy config next.

Step 3 — create the reverse-proxy server block

sudo tee /etc/nginx/sites-available/myapp.conf >/dev/null << 'EOF'
server {
    listen 80;
    listen [::]:80;
    server_name myapp.example.com;

    # Generous request body — bump if your app uploads large files
    client_max_body_size 50M;

    location / {
        proxy_pass         http://127.0.0.1:3000;

        # Pass the original request details upstream
        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;

        # WebSocket / long-lived connections
        proxy_http_version 1.1;
        proxy_set_header   Upgrade    $http_upgrade;
        proxy_set_header   Connection "upgrade";

        # Sensible timeouts (raise for long-running endpoints)
        proxy_connect_timeout 60s;
        proxy_send_timeout    60s;
        proxy_read_timeout    60s;
    }
}
EOF

# Enable the site
sudo ln -s /etc/nginx/sites-available/myapp.conf /etc/nginx/sites-enabled/

# Disable the default landing page
sudo rm -f /etc/nginx/sites-enabled/default

# Test the config
sudo nginx -t
# nginx: configuration file /etc/nginx/nginx.conf test is successful

# Reload (no dropped connections)
sudo systemctl reload nginx

The four proxy_set_header lines are the canonical reverse-proxy preamble. They tell the Node app:

  • Host: the hostname the client requested (so the Node app can route by host).
  • X-Real-IP: the client’s actual IP. Without this, every request looks like it came from 127.0.0.1.
  • X-Forwarded-For: the chain of proxies the request passed through.
  • X-Forwarded-Proto: http or https — Node needs this to know whether to emit secure cookies.

In Express, read these via req.ip and req.protocol — they work correctly after you call app.set('trust proxy', 1) to tell Express to trust the headers Nginx sets.

Step 4 — point DNS and verify

Set an A record for myapp.example.com pointing at the server’s public IP. After DNS propagates:

curl -sI http://myapp.example.com
# HTTP/1.1 200 OK
# Server: nginx/1.24.0

The Server: nginx/... header confirms Nginx is serving the request. The body of the response is whatever Node returned.

Step 5 — process management with PM2

# Install PM2 globally
sudo npm install -g pm2

# Start the app under PM2
cd /var/www/myapp
pm2 start server.js --name myapp

# Save the process list and install a startup script
pm2 startup systemd                          # prints a command to run
# Run that command (it varies by OS)
pm2 save

From now on PM2 keeps the Node process running, restarts it if it crashes, and brings it back on boot. Useful commands:

pm2 list                  # what's running
pm2 logs myapp            # tail logs
pm2 restart myapp         # restart
pm2 reload  myapp         # zero-downtime reload (uses cluster mode)
pm2 stop myapp
pm2 monit                 # live CPU/memory dashboard

Step 5 (alternative) — systemd unit

If you’d rather skip PM2, a tiny systemd unit does the same job:

sudo tee /etc/systemd/system/myapp.service >/dev/null << 'EOF'
[Unit]
Description=My Node.js App
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/myapp
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=5s
Environment=NODE_ENV=production

# Logs go to journald
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp

# Tail logs
journalctl -u myapp -f

Systemd handles restart-on-crash and start-on-boot. No extra dependency. Logs go to journald, queryable with the same commands you use for every other service.

Step 6 — add SSL

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d myapp.example.com

Certbot picks up the existing Nginx config, fetches a Let’s Encrypt certificate, adds the SSL server block, and rewrites the port-80 block to redirect to HTTPS. See How to Set Up Let’s Encrypt SSL with Certbot for the full walkthrough.

After the cert is in place, your Node app sees X-Forwarded-Proto: https on every request — make sure the app honours it (in Express: app.set('trust proxy', 1)).

Step 7 — multiple apps on one server

Run each Node app on its own port (3000, 3001, 3002…) and create one Nginx server block per hostname:

# /etc/nginx/sites-available/app-a.conf
server {
    listen 80;
    server_name app-a.example.com;
    location / { proxy_pass http://127.0.0.1:3000; /* ...same proxy_set_header block... */ }
}

# /etc/nginx/sites-available/app-b.conf
server {
    listen 80;
    server_name app-b.example.com;
    location / { proxy_pass http://127.0.0.1:3001; /* ... */ }
}

Enable both with ln -s into sites-enabled/, reload Nginx, and you’re hosting two distinct Node apps on the same box.

Tune for production

# Inside the server { } block, beyond the basics:

# Serve static assets directly from Nginx (skip Node entirely)
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2)$ {
    root /var/www/myapp/public;
    expires 1y;
    add_header Cache-Control "public, immutable";
    try_files $uri @nodejs;
}

location @nodejs {
    proxy_pass http://127.0.0.1:3000;
    # (proxy_set_header lines as before)
}

# Gzip text content
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 1000;

The static-file location block intercepts requests for assets and serves them from disk without ever invoking Node. With a year-long cache header, CDN-edge caching makes the second visit essentially free.

Frequently asked questions

Why Nginx in front of Node — can’t Node serve HTTPS itself?

It can. https.createServer() in Node accepts a cert and key. The reasons to front it with Nginx anyway: (1) Nginx is faster and lower-memory for TLS handshakes and static-file serving; (2) Certbot’s --nginx plugin automates renewal — wiring that up against a custom Node server is a project; (3) Nginx buffers slow client requests so Node’s event loop isn’t tied up; (4) you can run multiple Node apps on the same box behind different subdomains. The Node-only path is fine for hobby projects; for anything user-facing, the reverse proxy pays for itself.

PM2 vs systemd unit for Node — which?

PM2 is process-manager-as-application. It handles auto-restart, clustering, log rotation, and zero-downtime deploys — comes with an interactive CLI and a web dashboard. Great for solo developers managing their own deploys. systemd is the OS-native way. A 15-line unit file gets you auto-restart and integration with journald, no extra dependency. Use systemd for servers you’ll hand off to ops; use PM2 when the convenience features (zero-downtime reload, easy clustering) save real time.

How do I handle multiple Node apps on one server?

One Nginx server block per app, each proxy_passing to a different upstream port. App A on 3000, App B on 3001, etc. Use distinct hostnames (subdomains or path prefixes) or distinct server_name directives. Run each Node process under its own user for isolation. PM2 makes managing many processes easier; with systemd, write one unit file per app.

Why are file uploads timing out or truncating?

Nginx has a default 1 MB client_max_body_size — uploads larger than that get rejected with 413. Bump it: client_max_body_size 50M; (or your real upload ceiling) inside the server block. Also check proxy_read_timeout if your Node app takes more than 60 seconds to process the upload — raise it to proxy_read_timeout 300s; for long-running endpoints.

Related guides

  • How to Install the Latest Node.js on Ubuntu
  • How to Configure Nginx for a Subdirectory
  • How to Redirect www to non-www in Nginx
  • How to Set Up Let’s Encrypt SSL with Certbot on Ubuntu
  • How to Run a Node.js Application from a Windows .bat File

References

Nginx HTTP proxy module: nginx.org/en/docs/http/ngx_http_proxy_module.html. PM2 documentation: pm2.keymetrics.io/docs. Node.js documentation: nodejs.org/en/docs. Express trust proxy: expressjs.com/en/guide/behind-proxies.html.

TAGGED:Nginxnodejspm2reverse-proxysslUbuntu

Sign Up For Daily Newsletter

Be keep up! Get the latest breaking news delivered straight to your inbox.
[mc4wp_form]
By signing up, you agree to our Terms of Use and acknowledge the data practices in our Privacy Policy. You may unsubscribe at any time.
Share This Article
Facebook Copy Link Print
Previous Article Install and configure Redis on Ubuntu for Laravel and WordPress How to Install and Configure Redis on Ubuntu (for Laravel & WordPress)
Next Article Run Laravel queue workers with Supervisor How to Run Laravel Queue Workers in Production with Supervisor
Leave a Comment

Leave a Reply Cancel reply

You must be logged in to post a comment.

FacebookLike
XFollow
PinterestPin
InstagramFollow
Most Popular
Run Laravel queue workers with Supervisor
How to Run Laravel Queue Workers in Production with Supervisor
May 23, 2026
Nginx as a reverse proxy for a Node.js app on Ubuntu
How to Set Up Nginx as a Reverse Proxy for Node.js on Ubuntu
May 23, 2026
Install and configure Redis on Ubuntu for Laravel and WordPress
How to Install and Configure Redis on Ubuntu (for Laravel & WordPress)
May 23, 2026
Harden a fresh Ubuntu VPS with UFW, Fail2Ban, and SSH key auth
How to Harden a Fresh Ubuntu VPS: UFW + Fail2Ban + SSH Key Auth
May 23, 2026
Set up Let's Encrypt SSL with Certbot on Ubuntu
How to Set Up Let’s Encrypt SSL with Certbot on Ubuntu (Apache & Nginx)
May 23, 2026

You Might Also Like

aaPanel domain and Let's Encrypt SSL setup — secure the control panel
Server Management

How to Access aaPanel with a Domain and Let’s Encrypt SSL

8 Min Read
Fix missing PHP Authorization header on Apache and cPanel
Server Management

How to Fix Missing Authorization Header in PHP Requests

5 Min Read
mysqldump all databases — export and import commands
Server Management

How to Export and Import All MySQL Databases at Once

6 Min Read
How to temporarily disable Imunify360 service for testing (cPanel/WHM)
Server Management

How to Temporarily Disable Imunify360 Service (Safe Testing + Fix 503)

5 Min Read
How7o

We provide tips, tricks, and advice for improving websites and doing better search.

Tools

  • Age Calculator
  • Word Counter
  • Image Upscaler
  • Password Generator
  • QR Code Generator
  • See all tools→

Pranks

  • Fake Blue Screen Prank
  • Hacker Typer
  • Fake iMessage Generator
  • Windows XP Crash Prank
  • Windows 11 Update Prank
  • See all prank screens →

Company

  • About Us
  • Blog
  • Contact
  • Privacy Policy
  • Terms of Service
  • Sitemap
© 2024–2026 How7o. All rights reserved.
Welcome Back!

Sign in to your account

Username or Email Address
Password

Lost your password?