Deploy Node.js on Linux with Nginx and PM2 — a practical beginner’s guide
Source: Dev.to
Hook: why this stack matters
If you’ve built a Node.js app and want it to run reliably in production, you need more than node app.js and crossed fingers. Using Linux + Nginx + PM2 is a pragmatic stack that gives you:
- stability (Linux)
- performance and SSL termination (Nginx)
- automatic process management with monitoring (PM2)
This article walks you through the rationale and a concise, practical path to deploy a real app quickly.
Context: the problem most developers face
New apps fail in production for simple reasons: processes crash, ports are exposed, SSL is missing, or no one set up restarts for server reboots. You need a predictable deployment pattern that:
- Keeps the app running after crashes and reboots
- Hides internal ports from the public internet
- Handles HTTPS, static assets, and load spikes
Nginx + PM2 on a Linux VPS solves all of these without heavy orchestration.
Solution overview
The high‑level flow looks like this:
- Nginx listens on ports 80/443 and proxies to your Node process on an internal port (e.g., 3000).
- PM2 runs and monitors the Node process, restarts on failure, and can resurrect processes after reboot.
- A PM2 ecosystem file versions your process configuration.
These are standard, well‑supported tools and work on common distros such as Ubuntu and CentOS.
Quick checklist before you start
- Provision a Linux server (1–2 GB RAM is fine for small apps).
- SSH into the server and update packages.
- Install Node.js (NodeSource or nvm), Nginx, and PM2.
- Ensure DNS points your domain to the server before issuing SSL.
- Keep your code and ecosystem config in Git.
Implementation: essential steps (short and actionable)
-
Install Node.js (NodeSource or nvm) and verify versions:
node -v npm -v -
Upload or clone your app to a dedicated folder (e.g.,
/home/youruser/my-app) and install production dependencies:cd /home/youruser/my-app npm install --production -
Install PM2 globally and start your app with a friendly name:
npm install -g pm2 pm2 start app.js --name my-app pm2 startup # generates init script pm2 save # saves the process list -
Install Nginx and create a server block that proxies to the Node port (e.g., 3000). Example
/etc/nginx/sites-available/my-app: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 Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; 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_cache_bypass $http_upgrade; } # Optional: serve static assets directly # location /static/ { # alias /home/youruser/my-app/public/; # } }Test and reload:
sudo nginx -t sudo systemctl reload nginx -
Secure the site with Let’s Encrypt:
sudo apt-get install certbot python3-certbot-nginx # Ubuntu example sudo certbot --nginx -d example.com -d www.example.comThe certbot plugin will obtain certificates and configure HTTPS automatically.
Note: Do not expose your Node port to the public internet. Let Nginx handle incoming traffic and SSL.
PM2: make it repeatable
Create an ecosystem.config.js (or .json) to describe your app(s) and environment variables:
module.exports = {
apps: [
{
name: "my-app",
script: "./app.js",
instances: "max", // enable clustering
exec_mode: "cluster",
env: {
NODE_ENV: "development",
PORT: 3000
},
env_production: {
NODE_ENV: "production",
PORT: 3000
}
}
]
};
Start everything with:
pm2 start ecosystem.config.js --env production
pm2 save
Best practice: avoid watch: true in production; trigger restarts via CI/CD instead.
Nginx: reverse proxy basics and tips
-
Proxy target:
http://127.0.0.1:3000(adjust to your app’s port). -
Common headers:
X-Forwarded-For,Host,X-Forwarded-Proto. -
Test config with
nginx -tbefore reloading. -
Enable gzip and appropriate cache headers for static assets.
-
Add basic security headers:
add_header X-Frame-Options "SAMEORIGIN"; add_header X-Content-Type-Options "nosniff"; add_header X-XSS-Protection "1; mode=block"; -
For multiple apps, create separate server blocks per domain, each pointing to its own internal port.
Security, monitoring and validation
-
Firewall: enable UFW (Ubuntu) or firewalld (CentOS) and allow only SSH and Nginx Full (ports 22, 80, 443).
sudo ufw allow OpenSSH sudo ufw allow 'Nginx Full' sudo ufw enable -
HTTPS: use Certbot with the Nginx plugin for automated issuance and renewal.
-
Logs & monitoring:
pm2 logs pm2 monitInstall log rotation to prevent uncontrolled growth:
pm2 install pm2-logrotate -
Load testing: run a quick test with
abor similar tools and watchpm2 monit,top/htop, and Nginx logs.
Quick best practices
- Run Node under a non‑root user.
- Never commit secrets; use environment variables or PM2
envblocks. - Regularly run
npm auditand keep dependencies updated.
Where to learn more and get help
For a ready reference and expanded explanations, see the full guide:
Company and additional resources:
Conclusion
Deploying Node.js on Linux with Nginx and PM2 gives you a reliable, maintainable base without the complexity of Docker or full orchestration. Start small:
- Get Node + PM2 running.
- Put Nginx in front.
- Add HTTPS.
- Automate with an ecosystem file and CI later.
That pattern covers most production needs for indie projects, MVPs, and early‑stage SaaS.