A Node.js app listens on a local port such as 3000. Visitors expect your domain on ports 80 and 443. A reverse proxy sits in front, accepts the public traffic, handles SSL and passes requests to your app. This guide gives a working config for each of the three common web servers.
Before you start
- Your app running and reachable locally:
curl http://127.0.0.1:3000returns a page. Keep it running with PM2. - Your domain's A record pointing to the VPS IP.
- One web server installed: Nginx, Apache or OpenLiteSpeed.
Tip: Make the app listen on 127.0.0.1 only, not 0.0.0.0, so nobody can bypass the proxy by visiting http://your-ip:3000.
Nginx
- Create
/etc/nginx/sites-available/example.com(on AlmaLinux use/etc/nginx/conf.d/example.com.conf):server { listen 80; server_name example.com www.example.com; location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; 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_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } - Enable it and reload:
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx
More Nginx options are in Nginx reverse proxy setup.
Apache
- Enable the proxy modules (Debian/Ubuntu):
sudo a2enmod proxy proxy_http proxy_wstunnel headers rewrite - Create
/etc/apache2/sites-available/example.com.conf:<VirtualHost *:80> ServerName example.com ServerAlias www.example.com ProxyPreserveHost On RequestHeader set X-Forwarded-Proto "http" RewriteEngine On RewriteCond %{HTTP:Upgrade} websocket [NC] RewriteRule ^/(.*) ws://127.0.0.1:3000/$1 [P,L] ProxyPass / http://127.0.0.1:3000/ ProxyPassReverse / http://127.0.0.1:3000/ </VirtualHost> - Enable the site and reload:
sudo a2ensite example.com sudo apachectl configtest sudo systemctl reload apache2
On AlmaLinux and Rocky the modules are loaded by default; put the file in /etc/httpd/conf.d/ and reload httpd. See also Apache reverse proxy.
OpenLiteSpeed
- Open the WebAdmin console on port 7080 and go to Virtual Hosts → your host → External App. Add a Web Server app named
node3000with address127.0.0.1:3000. - Go to Context, add a Proxy context with URI
/and Web Servernode3000. - Click Graceful Restart.
LiteSpeed passes WebSocket upgrades through proxy contexts automatically.
Check that it works
curl -I http://example.comYou should get your app's response headers, not a default web server page. Then add HTTPS as shown in Add SSL to a Node.js app.
Troubleshooting
- 502 Bad Gateway — the app is not running or listens on another port. Check
pm2 listandss -ltnp | grep 3000. See Fix Nginx 502 Bad Gateway. - Redirect loops after SSL — the app does not see
X-Forwarded-Proto. In Express setapp.set('trust proxy', 1). - All visitors show 127.0.0.1 — read the
X-Forwarded-Forheader and enable trust proxy.
