Development
How to Set Up Nginx as a Reverse Proxy for Node.js (Complete Guide)
Step-by-step guide to configuring Nginx as a reverse proxy for a Node.js application: the complete nginx.conf, SSL setup with Certbot, performance tuning, running multiple apps on one server, and common errors explained.
Why Use Nginx in Front of Node.js
Node.js is an application server, not a web server. It is not optimized for serving static files, handling thousands of concurrent slow clients, or managing SSL termination efficiently. Nginx fills these gaps. Putting Nginx in front of Node.js means Nginx handles all the low-level connection management while Node.js focuses on running application logic.
Nginx serves static assets (CSS, JavaScript, images) directly from disk without touching Node.js, which eliminates a class of unnecessary load on your application. It buffers slow client connections so Node.js never has to wait for a slow upload or a mobile client with a bad connection. It handles SSL termination, which keeps SSL configuration out of your Node.js codebase entirely.
Nginx also lets you run multiple Node.js applications on the same server and route traffic to each one by hostname or URL path. Without a proxy, you would need each app to listen on a different port and expose that port publicly, which is both a security risk and a UX problem.
- Nginx serves static files directly, reducing Node.js CPU and memory load
- SSL termination in Nginx keeps TLS config out of your application code
- Buffer slow clients at Nginx level so Node.js event loop stays responsive
- Route multiple Node.js apps on one server by hostname or URL path
- Rate limiting, gzip compression, and caching happen at the proxy layer
Installing Nginx on Ubuntu
On Ubuntu 22.04 or 24.04, install Nginx with: sudo apt update && sudo apt install nginx -y. After installation, Nginx starts automatically and serves the default welcome page on port 80. Verify it is running with: sudo systemctl status nginx.
Enable Nginx to start automatically on reboot: sudo systemctl enable nginx. This is essential for production servers because Nginx does not start automatically after a reboot unless you explicitly enable it. Forgetting this step is a common cause of sites going down after a server restart.
If your server runs UFW (Ubuntu Firewall), allow the Nginx HTTP and HTTPS profiles: sudo ufw allow Nginx HTTP and sudo ufw allow Nginx HTTPS. Without this step, external traffic cannot reach Nginx even though it is running.
The Complete Nginx Configuration for Node.js
Create a site configuration file at /etc/nginx/sites-available/myapp.conf. The core of the configuration is the proxy_pass directive inside a location block, which forwards all requests to your Node.js application running on localhost at its port (commonly 3000, 4000, or 8080).
The proxy headers are critical. Without them, your Node.js app cannot see the real client IP address or know whether the original request used HTTP or HTTPS. Add these inside the location block: proxy_set_header Host, proxy_set_header X-Real-IP, proxy_set_header X-Forwarded-For, and proxy_set_header X-Forwarded-Proto. These pass the original request context through to your application.
For WebSocket support, add: proxy_http_version 1.1, proxy_set_header Upgrade, and proxy_set_header Connection Upgrade. Without these three lines, WebSocket connections will fail to upgrade and fall back to polling or disconnect entirely. Enable the config with: sudo ln -s /etc/nginx/sites-available/myapp.conf /etc/nginx/sites-enabled/ and test with sudo nginx -t before reloading.
Setting Up SSL with Certbot and Let Encrypt
Let Encrypt provides free SSL certificates that renew automatically. Install Certbot: sudo apt install certbot python3-certbot-nginx -y. Then run: sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com. Certbot reads your Nginx configuration, obtains a certificate for your domain, and automatically modifies the Nginx config to enable HTTPS.
Certbot sets up a systemd timer that auto-renews certificates before they expire. Verify the timer is active with: sudo systemctl status certbot.timer. Certificates expire after 90 days, but the auto-renewal runs twice daily and renews any certificate with less than 30 days remaining.
After Certbot runs, your Nginx config will have an HTTPS server block listening on port 443 with the certificate path filled in automatically. It also adds a redirect from HTTP (port 80) to HTTPS so all traffic uses the secure connection. Test that the redirect and certificate work by visiting your domain in a browser and checking for the padlock icon.
Nginx Performance Settings for Node.js Applications
Set worker_processes to auto in the main nginx.conf to match the number of CPU cores automatically. Add worker_connections 1024 inside the events block so each worker process can handle 1024 simultaneous connections. These two settings together mean Nginx can handle a large number of concurrent connections without configuration changes as you scale the server.
Enable gzip compression in the http block: gzip on, gzip_types with common mime types (text/html, application/json, text/css, application/javascript), and gzip_min_length 256. Gzip reduces the size of JSON API responses and HTML pages by 60 to 80 percent, which directly improves response time for clients on slower connections.
Add proxy_read_timeout 120 to the location block that proxies to Node.js. The default timeout is 60 seconds, which is too short for any operation that involves heavy computation, database queries, or external API calls. If your Node.js app takes longer than 60 seconds on any request, Nginx will return a 504 Gateway Timeout without this setting.
Running Multiple Node.js Apps on One Server
Each Node.js application runs on a different local port. App A listens on port 3000, App B listens on port 4000. In Nginx, you create a separate server block for each app with a different server_name directive. Nginx routes requests to the correct app based on the incoming hostname.
Use the upstream block to define each backend as a named group. An upstream block named app_a containing server 127.0.0.1:3000 can be referenced with proxy_pass http://app_a in the location block. Using named upstreams makes it easier to add multiple Node.js instances for load balancing later without rewriting the proxy_pass line.
Create separate Nginx config files for each app in /etc/nginx/sites-available/ and symlink each one to /etc/nginx/sites-enabled/. This keeps each app configuration isolated so you can disable, update, or remove one app without touching the others.
Debugging Nginx and Node.js Proxy Errors
The Nginx error log at /var/log/nginx/error.log contains the most useful information for diagnosing proxy failures. The most important error messages to know: connect() failed (111: Connection refused) means your Node.js app is not running on the port Nginx expects. upstream timed out (110: Connection timed out) means the request reached Node.js but took too long to respond.
recv() failed (104: Connection reset by peer) means the Node.js process crashed mid-request. Check your PM2 logs with pm2 logs to find the crash reason. upstream sent invalid header means Node.js sent an HTTP response that does not start with a valid status line, which usually means Node.js is printing something to stdout before the HTTP headers (remove any console.log calls that run at startup).
Run sudo nginx -t before every reload to catch configuration syntax errors without affecting live traffic. Run sudo nginx -s reload to apply configuration changes without restarting Nginx or dropping existing connections. Never kill and restart Nginx on a live server when a graceful reload will do.
- Connection refused (111): Node.js is not running on the expected port
- upstream timed out (110): request reached Node.js but took too long
- Connection reset by peer (104): Node.js process crashed mid-request
- invalid header: Node.js output before HTTP headers (remove startup console.log calls)
- Run nginx -t to check config syntax before every reload
FAQ
Why use Nginx in front of Node.js instead of running Node.js on port 80 directly?
Running Node.js on port 80 requires root privileges (Linux restricts ports below 1024 to root), exposes your application process directly to the internet, and does not handle SSL termination, static file serving, or connection buffering. Nginx handles all of these at the proxy layer, which keeps your Node.js app simpler and more secure.
What is proxy_pass in Nginx?
proxy_pass is the Nginx directive that forwards an incoming HTTP request to a backend server. proxy_pass http://127.0.0.1:3000 tells Nginx to forward all requests in that location block to a Node.js process listening on localhost port 3000. Nginx waits for the response from Node.js and forwards it back to the original client.
How do I add SSL to an Nginx and Node.js setup?
Install Certbot (sudo apt install certbot python3-certbot-nginx) and run sudo certbot --nginx -d yourdomain.com. Certbot obtains a free SSL certificate from Let Encrypt and automatically modifies your Nginx configuration to use it. It also sets up auto-renewal so the certificate renews automatically before it expires.
What port should Node.js listen on behind Nginx?
Any port above 1024 that is not already in use on the server. Common choices are 3000 (Next.js default), 4000, 5000, or 8080. The port only needs to be accessible from localhost (127.0.0.1) since Nginx is the only thing connecting to it. Block external access to Node.js ports with UFW: sudo ufw deny [port].
How do I serve multiple Node.js apps on one Nginx server?
Run each Node.js app on a different local port. Create a separate Nginx server block for each app with a unique server_name directive matching the domain or subdomain. Each server block uses proxy_pass to forward to that app local port. Nginx routes requests to the correct app based on the incoming hostname.
What is the difference between proxy_pass and upstream in Nginx?
proxy_pass specifies a single backend address to forward requests to. upstream defines a named group of backend servers, which enables load balancing between multiple instances. When proxy_pass references an upstream name (proxy_pass http://my_upstream), Nginx distributes requests across all servers in that group using round-robin by default.
How do I fix 502 Bad Gateway in Nginx and Node.js?
Check /var/log/nginx/error.log for the specific error. Connection refused (111) means Node.js is not running on the expected port: restart it with pm2 restart or node server.js. Upstream timed out (110) means Node.js is running but the request is taking too long: increase proxy_read_timeout in Nginx and investigate the slow operation in Node.js.
Related free tools
If you want to turn this topic into action, use one of ShortIQ's free tools for campaign planning, UTM structure, or QR distribution.
Continue Reading
Explore more guides on link shortener SaaS strategy, Bitly alternatives, and white label link management.
Free newsletter
Get new guides in your inbox
We publish practical guides on dev tooling, prompt engineering, marketing workflows, and deployment. No fluff — straight to the point.
No spam. Unsubscribe any time.
Was this article helpful?
Tell us if this guide solved the problem or what was still missing. We use this to improve the blog and only follow up if you explicitly allow it.