Deploy Node.js on a Linux Server with Nginx and PM2 (Beginner Deployment Guide)

Introduction: Why Deploy Node.js on Linux with Nginx and PM2?
Deploying your Node.js application on a Linux server using Nginx and PM2 is a common, production-ready approach embraced by startups and enterprises alike. This stack leverages the strengths of each technology: Linux’s stability, Node.js’s speed, Nginx’s power as a reverse proxy, and PM2’s robust process management. If you’re aiming to take your Node.js project from development to the real world, mastering this deployment workflow is essential.
In this beginner-friendly deployment guide, you’ll learn how to “deploy nodejs on linux server” step by step. We’ll focus on practical, actionable instructions suitable for developers new to production deployments—but with enough detail to give you confidence for real-world scenarios.
By the end of this guide, you’ll understand why so many teams choose this stack and what advantages it brings:
- Linux is the most popular OS for servers, known for its security, efficiency, and flexibility.
- Node.js allows you to build fast, scalable backend applications in JavaScript.
- Nginx acts as a high-performance reverse proxy and load balancer, shielding your app and improving reliability.
- PM2 ensures your Node.js app stays running, even after crashes or reboots.
What You'll Achieve
- Grasp the advantages of deploying on Linux (Ubuntu and CentOS)
- Learn the roles and interplay of Node.js, Nginx, and PM2
- Identify each stage necessary for a smooth deployment
- Set realistic expectations for your first live Node.js launch
Whether you’re launching a new SaaS, a personal site, or a client project, this guide will help you:
- Avoid common pitfalls in Node.js deployments
- Secure your application and server
- Use industry-standard tools for reliability and performance
Ready to dive in? Let’s break down each component of our deployment stack and see how they fit together.
Further Reading
- Node.js Official Website — Learn about Node.js and its ecosystem.
- DigitalOcean Community — Hands-on guides for Linux, Nginx, and cloud deployment basics.
Understanding the Deployment Stack: Node.js, Nginx, PM2, and Linux
Before you start running commands on your server, it’s crucial to understand how each piece of the deployment puzzle fits together. This section provides a clear overview of what each technology does, why it matters, and how data flows from a web browser to your Node.js application and back.
The Building Blocks
1. Linux Server: The Foundation
Linux is the operating system powering most cloud infrastructure. It’s known for its stability, performance, and extensive community support. Both Ubuntu and CentOS are popular distributions for Node.js deployment:
- Ubuntu: User-friendly, widely supported, and a common choice for cloud VMs.
- CentOS: Enterprise-focused, stable, and a favorite for long-term support scenarios.
2. Node.js: The Runtime
Node.js allows you to run JavaScript code on the server, building backend APIs, web servers, or real-time apps. When you deploy Node.js on a Linux server, you’re leveraging a fast, event-driven runtime that can handle thousands of connections efficiently.
3. Nginx: The Reverse Proxy
Nginx is a high-performance web server and reverse proxy. In a production stack, Nginx sits in front of your Node.js app, handling incoming HTTP/HTTPS requests. Nginx can:
- Route traffic to your Node.js app (or multiple apps)
- Serve static files (images, CSS, JS)
- Terminate SSL (handle HTTPS)
- Load balance between multiple Node.js processes
- Protect your app from direct exposure to the internet
4. PM2: The Process Manager
PM2 is a production process manager for Node.js applications. It keeps your app running forever, restarts it if it crashes, and can handle zero-downtime reloads. PM2 also offers monitoring and simple log management.
How Do These Components Work Together?
Here’s a simplified data flow:
- Client (browser or API consumer) sends a request to your server’s IP or domain.
- Nginx receives the request on port 80 (HTTP) or 443 (HTTPS).
- Nginx forwards (proxies) the request to your Node.js app running on an internal port (e.g., 3000).
- Node.js processes the request and sends a response back to Nginx.
- Nginx returns the final response to the client.
- PM2 ensures the Node.js process stays alive and restarts it automatically if needed.
Visualizing the Stack
[Client] <---> [Nginx (ports 80/443)] <---> [Node.js app (port 3000)]
^
|
[PM2 manages process]
Why This Stack?
- Security: Nginx shields your Node.js app from direct attacks.
- Performance: Nginx can handle thousands of concurrent connections and efficiently serve static files.
- Reliability: PM2 keeps your app running, even if it crashes or the server restarts.
- Scalability: You can run multiple Node.js app instances and load balance with Nginx.
- Maintainability: Each layer has a clear responsibility.
When Would You Use Something Else?
- For extremely high-traffic sites, you may introduce Docker/Kubernetes or advanced load balancers.
- For static sites, you might use only Nginx or a CDN.
But for most Node.js web applications, this stack is the gold standard.
Further Reading
- Nginx Docs: Reverse Proxy — Deep dive into reverse proxy configurations.
- PM2 Documentation — Learn PM2’s basic commands and features.
Preparing Your Linux Server (Ubuntu & CentOS)
Before you can deploy your Node.js application, you need a properly set up Linux server. In this section, you’ll learn how to choose a server, connect to it securely, and update it for a clean, stable deployment environment. We’ll cover both Ubuntu and CentOS, the two most common choices for Node.js server setup.
1. Choose and Provision Your Server
You can use a cloud provider (like DigitalOcean, AWS, Linode, or Google Cloud) or your own physical machine. For most beginners, a cloud VPS is easiest.
Recommended starting specs:
- 1 CPU core
- 1–2 GB RAM
- 25+ GB SSD disk
- Ubuntu 22.04 LTS or CentOS 8/Stream
2. Connect Securely via SSH
Once your server is provisioned, connect using SSH. You’ll need the server’s IP address and the username (often root or ubuntu).
On macOS/Linux:
ssh username@your_server_ip
On Windows:
- Use PuTTY, Windows Terminal, or WSL.
Optional: Set Up SSH Keys (Highly Recommended)
- Generate an SSH key on your local machine:
ssh-keygen -t ed25519 -C "your_email@example.com" - Copy the public key to your server:
ssh-copy-id username@your_server_ip
3. Update Your Server’s Packages
Keeping your system up-to-date ensures you have the latest security patches and features.
For Ubuntu:
sudo apt update
sudo apt upgrade -y
For CentOS:
sudo dnf update -y # (CentOS 8/Stream)
4. Install Basic Utilities (Optional but Helpful)
# For both Ubuntu and CentOS
sudo apt install curl git unzip -y # Ubuntu/Debian
sudo dnf install curl git unzip -y # CentOS/Fedora
Quick Checklist
- Can you SSH into your server as root or a sudo-enabled user?
- Did you update and upgrade all system packages?
- (Optional) Did you set up SSH key authentication?
- Do you have basic tools like
curlandgitinstalled?
With a prepared server, you’re now ready to install Node.js and start deploying your app.
Further Reading
- Ubuntu Server Guide — For more on Ubuntu server setup and maintenance.
- CentOS Documentation — For CentOS-specific administration tips.
Installing Node.js on Linux (Ubuntu & CentOS)
With your Linux server ready, the next step is to install Node.js and npm (Node Package Manager). This process is slightly different between Ubuntu and CentOS, and there are several methods to choose from. Here, you’ll learn the most reliable, production-friendly options—including how to use nvm (Node Version Manager) for flexibility.
1. Why Use Official Sources or nvm?
- Official repositories often have outdated Node.js versions. The NodeSource repository or nvm offers the latest releases and better control.
- nvm is perfect if you want to manage multiple Node.js versions (for different projects) or easily upgrade/downgrade.
2. Installing Node.js on Ubuntu (with NodeSource)
- Add the NodeSource APT repository (replace
18.xwith your needed version):curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - - Install Node.js and npm:
sudo apt install -y nodejs - Verify installation:
node -v npm -v
3. Installing Node.js on CentOS (with NodeSource)
- Add the NodeSource Yum repository:
curl -fsSL https://rpm.nodesource.com/setup_18.x | sudo bash - - Install Node.js and npm:
sudo dnf install -y nodejs - Verify installation:
node -v npm -v
4. (Recommended) Managing Node.js Versions with nvm
If you want to switch Node.js versions easily (helpful for different projects), install nvm:
- Install nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash # Restart your shell or source your profile: source ~/.bashrc # or ~/.zshrc - Install a Node.js version (e.g., latest LTS):
nvm install --lts nvm use --lts - Set the default version:
nvm alias default lts/* - Verify installation:
node -v npm -v
When to Use nvm vs. System Packages
- Use nvm if you need to manage multiple Node.js versions or want an unprivileged (non-root) installation.
- Use system packages for simplicity or if you prefer managing everything as root.
5. Troubleshooting Common Installation Issues
- Command not found? Make sure your shell profile (
~/.bashrc,~/.zshrc) is sourced after installing nvm. - Permission errors? Run commands as your user (not root) when using nvm.
- Wrong Node.js version? Check with
node -v; usenvm useto switch.
Quick Checklist
- Did
node -vandnpm -vshow the expected versions? - Did you install with nvm if you want version flexibility?
- Can you run a simple test script?
Test Node.js installation:
node -e "console.log('Node.js is working!')"
Further Reading
- NodeSource Node.js Binary Distributions — For advanced, up-to-date install instructions.
- nvm GitHub Repository — Learn everything about Node Version Manager.
Uploading and Setting Up Your Node.js Application
With Node.js installed on your server, you’re ready to deploy your application code. In this section, you’ll learn how to securely transfer files, configure your app’s environment variables, and set permissions for a production-ready deployment. These steps are crucial for a smooth "nodejs production deployment."
1. Transfer Your Application Files
There are several ways to upload your app to the server:
Option A: Using scp (Secure Copy)
From your local machine:
scp -r ./my-node-app username@your_server_ip:/home/username/
-rcopies directories recursively.- Place your app in
/home/username/or a dedicated/var/www/my-node-app/directory.
Option B: Using SFTP (GUI Tools)
- FileZilla or WinSCP allow drag-and-drop file uploads over SFTP.
- Connect with the same username and server IP.
Option C: Clone from Git
If your app is in a Git repository:
git clone https://github.com/yourusername/your-node-app.git
2. Organize Your Application Directory
- Place each app in its own folder (e.g.,
/home/username/my-node-app/). - Keep
node_modulesout of your version control; install dependencies on the server. - Structure example:
/home/username/my-node-app/ ├── app.js ├── package.json ├── .env └── ...
3. Install Dependencies
SSH into your server, switch to the app directory, then:
cd ~/my-node-app
npm install --production
- The
--productionflag skips dev dependencies, reducing attack surface.
4. Set Environment Variables for Production
Never hardcode sensitive config (like database passwords) in your code. Use environment variables, typically via a .env file:
Example .env file:
NODE_ENV=production
PORT=3000
DATABASE_URL=postgres://user:pass@localhost:5432/dbname
SECRET_KEY=your_secret_key
Load .env in your app: (with dotenv)
require('dotenv').config();
5. Set File & Directory Permissions
- Ensure your app is owned by your user (not root):
sudo chown -R $USER:$USER ~/my-node-app - Permissions: directories
755, files644are usually safe defaults. - For security, never store sensitive files (like private keys) in world-readable locations.
Quick Checklist
- Application files uploaded to the server
- Dependencies installed with
npm install --production - Environment variables configured securely
- File permissions set correctly
Mini Project: Deploy a Test App
As a quick test, try uploading a simple Express.js “Hello World” app, install dependencies, and run it with node app.js to verify everything works before moving on.
Further Reading
- OpenSSH Manual — For details on SSH, SCP, and SFTP usage.
- Twelve-Factor App: Config — Best practices for environment-based configuration management.
In the next part, you’ll learn how to install and use PM2 to run your Node.js app as a background service, and configure Nginx as a secure and scalable reverse proxy. You’re well on your way to mastering beginner Node.js deployment!
Installing and Configuring PM2 for Node.js Process Management
In Part 1, you prepared your Linux server and ensured that your Node.js application was up and running. However, running a Node.js app with node app.js is not suitable for production. If your app crashes, it won’t restart automatically. If your server reboots, your app won’t start with it. This is where PM2 shines—a production-grade process manager for Node.js that makes your app reliable, monitorable, and easy to manage.
In this section, we'll walk through installing PM2 globally, launching your Node.js app with it, ensuring your process stays alive, and managing logs and status checks. This is a crucial step in any beginner Node.js deployment guide, and a must for anyone aiming to deploy Node.js on a Linux server with confidence.
1. Install PM2 Globally
PM2 is installed as an npm package. To manage your app system-wide, install it globally:
sudo npm install -g pm2
Verify the installation:
pm2 --version
You should see a version number, confirming PM2 is ready.
2. Start and Monitor Your Node.js App with PM2
Switch to your app’s directory. Suppose your main file is app.js (adjust as needed):
cd /path/to/your/app
pm2 start app.js --name "my-app"
--nameassigns a friendly label to your process (helpful if you run multiple apps).- PM2 will fork and daemonize your app, detaching it from your shell.
Check running processes:
pm2 list
You’ll see a table showing your app’s status, uptime, and memory usage.
Monitor logs in real time:
pm2 logs
To view logs for a specific app:
pm2 logs my-app
3. Configure Automatic Restarts (on Crash or Reboot)
PM2 automatically restarts your app if it crashes or is killed. To also restart your app when the server reboots, set up PM2’s startup script:
a. Generate a Startup Script
pm2 startup
- This will print a command—copy and run it with
sudoto register PM2 with your OS’s init system (systemd, upstart, etc.).
b. Save Your Process List
After starting your app(s) with PM2:
pm2 save
This saves the current process list, so PM2 can resurrect it after a reboot.
c. Reboot Test
- Reboot your server:
sudo reboot - Log in again and run:
Your app should be running automatically.pm2 list
4. Access Logs and Check Process Status
- View logs:
- Combined output:
pm2 logs - Error logs only:
pm2 logs my-app --err - Output logs only:
pm2 logs my-app --out
- Combined output:
- Restart an app:
pm2 restart my-app
- Stop an app:
pm2 stop my-app
- Delete an app:
pm2 delete my-app
Mini-Project: Deploy a Sample App with PM2
Try deploying a simple Express.js server and managing it with PM2. This is a great way to practice before deploying your main application.
Common PM2 Tasks Cheat Sheet
- List processes:
pm2 list - Show detailed info:
pm2 show my-app - Reload app (zero-downtime):
pm2 reload my-app - Save process list:
pm2 save - Kill all processes:
pm2 kill
Further Reading
- PM2 Documentation (Quick Start) — Official PM2 install and usage guide.
- DigitalOcean: How To Use PM2 to Setup a Node.js Production Environment — Clear, beginner-friendly walkthrough.
Installing and Configuring Nginx as a Reverse Proxy for Node.js
With your Node.js application running reliably under PM2, it’s time to expose it to the world. For security, scalability, and performance, it’s best practice to deploy Node.js on a Linux server behind a robust web server like Nginx, using it as a reverse proxy. This lets Nginx handle incoming HTTP/HTTPS traffic and forward requests to your Node.js process.
This section covers installation of Nginx on Ubuntu or CentOS, writing a basic reverse proxy configuration, testing the setup, and troubleshooting common issues. This is a cornerstone of any professional Node.js server setup.
1. Install Nginx on Your Server
Ubuntu (20.04/22.04+)
sudo apt update
sudo apt install nginx
CentOS (7/8/Stream)
sudo yum install epel-release
sudo yum install nginx
sudo systemctl enable nginx
Start and enable Nginx:
sudo systemctl start nginx
sudo systemctl enable nginx
Check status:
sudo systemctl status nginx
Open your server’s IP in a web browser—you should see the default Nginx welcome page.
2. Write a Basic Reverse Proxy Configuration
By default, Node.js apps run on ports like 3000, 4000, etc., not port 80/443. Nginx listens on 80/443 and proxies traffic to your app.
a. Find Your Node.js App’s Internal Port
Suppose your app listens on port 3000. Adjust as needed for your setup.
b. Create a New Nginx Server Block (Ubuntu: /etc/nginx/sites-available/yourapp)
server { listen 80; server_name your_domain.com www.your_domain.com;location / { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; }
}
- Replace
your_domain.comwith your domain or server IP. - Adjust port
3000as needed.
Enable the config (Ubuntu/Debian):
sudo ln -s /etc/nginx/sites-available/yourapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
CentOS: Edit /etc/nginx/nginx.conf or add to /etc/nginx/conf.d/yourapp.conf with similar content, then reload Nginx.
c. Firewall Settings
Allow HTTP/HTTPS:
- Ubuntu (UFW):
sudo ufw allow 'Nginx Full' sudo ufw reload - CentOS (firewalld):
sudo firewall-cmd --permanent --add-service=http sudo firewall-cmd --permanent --add-service=https sudo firewall-cmd --reload
3. Test Nginx and Node.js Integration
- Visit
http://your_domain.comor your server’s IP in a browser. - You should see your Node.js app’s output (not Nginx’s welcome page).
- If your app has a
/healthendpoint, test it:http://your_domain.com/health.
Check for errors:
sudo tail -f /var/log/nginx/error.log
sudo tail -f /var/log/nginx/access.log
Common issues:
- 502 Bad Gateway = Node.js app not running or wrong port
- 404 Not Found = Wrong
locationblock or file path
4. Troubleshooting Nginx-Node.js Setups
- Port conflicts: Ensure your Node.js app is not set to listen on 80/443 if Nginx is using those ports.
- Permissions: Make sure Nginx can connect to localhost:3000 (default SELinux/AppArmor settings can block this; consult OS docs).
- Reload after changes: Always run
sudo nginx -t(to test configs) followed bysudo systemctl reload nginx.
Mini-Checklist: Basic Nginx Reverse Proxy for Node.js
- Nginx installed and running
- Node.js app running under PM2
- Nginx server block points to correct port
- Firewall allows HTTP/HTTPS
- Browser shows your Node.js app via Nginx
Further Reading
- Nginx Official Documentation — Everything from basics to advanced configs.
- DigitalOcean: Set Up a Node.js Application for Production on Ubuntu — Detailed, beginner-friendly setup guide.
Securing Your Node.js Deployment: Basic Best Practices
Now that your Node.js app is accessible through Nginx, it’s essential to lock things down. Security is a core part of any "nodejs production deployment". Even a simple misconfiguration can expose your server to attacks. This section covers basic security: firewall setup, enabling HTTPS, and hardening your Nginx and Node.js configuration, with practical steps for both Ubuntu and CentOS.
1. Configure a Firewall (UFW or firewalld)
A firewall restricts unwanted network traffic, protecting your server.
Ubuntu (UFW)
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status
OpenSSHkeeps your SSH access open so you don’t lock yourself out.'Nginx Full'opens HTTP and HTTPS ports (80, 443).
CentOS (firewalld)
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --reload
sudo firewall-cmd --list-all
2. Set Up HTTPS with Let’s Encrypt (Free SSL)
Serving your app over HTTPS is critical. Let’s Encrypt makes it free and easy.
a. Install Certbot
- Ubuntu:
sudo apt install certbot python3-certbot-nginx - CentOS:
sudo yum install certbot python3-certbot-nginx
b. Obtain and Install a Certificate
Replace your_domain.com with your domain:
sudo certbot --nginx -d your_domain.com -d www.your_domain.com
- Certbot will auto-configure Nginx for HTTPS, including redirection from HTTP to HTTPS.
- Follow prompts to choose if you want HTTP-to-HTTPS redirect.
Test renewal:
sudo certbot renew --dry-run
3. Harden Nginx and Node.js Settings
- Disable server tokens in Nginx:
Add this inserver_tokens off;http { ... }block in/etc/nginx/nginx.conf. - Limit request size:
client_max_body_size 1M; - Restrict allowed HTTP methods:
if ($request_method !~ ^(GET|POST)$ ) { return 444; } - Run Node.js as a non-root user. Never run your app as root.
- Update your dependencies and Node.js version regularly.
4. Linux Permissions and User Roles Basics
- Files and folders: Set correct permissions. For most apps,
755for folders and644for files is sufficient. - PM2: Run
pm2as your normal user, not as root. If you usesudo pm2, your apps might not start on reboot for your regular user. - Limit sudo access: Only give sudo privileges to trusted users.
Mini-Project: Test Your Security
- Try accessing your server on ports other than 80/443/22—they should be blocked.
- Test HTTPS by visiting
https://your_domain.comand ensuring you get a secure lock icon in your browser.
Further Reading
- Let’s Encrypt Documentation — Official SSL/TLS deployment help.
- DigitalOcean: UFW Essentials — Ubuntu firewall basics and best practices.
Testing and Verifying Your Production Node.js Deployment
You’ve now completed your basic Linux server Node.js deployment: your app is managed by PM2, traffic is routed through Nginx, and security basics are in place. Before you celebrate, it’s crucial to rigorously test your deployment. This section guides you through browser-based checks, log analysis, and simple load simulation to ensure your app is stable and correctly served.
1. Test Your Node.js App Through Nginx from a Browser
- Open a browser and enter your domain name (e.g.,
https://your_domain.com). - Confirm your app loads and works as expected.
- If you have a health check route (e.g.
/health), visit it to ensure backend connectivity. - Try both
http://andhttps://versions—ensure HTTP redirects to HTTPS.
2. Check Logs for Node.js, PM2, and Nginx
Log files are your first stop for troubleshooting.
- PM2 logs:
pm2 logs my-app - Nginx logs:
sudo tail -f /var/log/nginx/access.log /var/log/nginx/error.log - Application logs: If your app writes its own logs, review them as well.
3. Simulate Traffic to Verify Stability
You can use tools like ab (Apache Benchmark) or curl to simulate traffic:
ab -n 100 -c 10 https://your_domain.com/
-n 100sends 100 requests;-c 10means 10 concurrent requests.- Monitor your server’s CPU/memory with
toporhtopwhile testing. - Watch PM2 and Nginx logs for errors or performance bottlenecks.
4. Identify and Resolve Common Deployment Errors
- 502 Bad Gateway:
- Node.js app not running or wrong port in Nginx config
- 403 Forbidden:
- File/folder permissions issue; check Nginx and app directory permissions
- SSL/TLS errors:
- Certificate missing or expired; re-run Certbot
- PM2 not starting apps after reboot:
- Forgot
pm2 saveor didn’t runpm2 startupas instructed
- Forgot
Mini-Checklist: Production Deployment Works!
- App loads via domain name (and HTTPS)
- HTTP redirects to HTTPS
- No critical errors in Nginx, PM2, or app logs
- Server stands up to basic load testing
What’s Next?
In future parts, we’ll cover advanced topics such as scaling Node.js apps, rolling deployments, environment variables, and production monitoring.
Further Reading
- Google Chrome DevTools — Inspect network traffic and debug web apps.
- PM2 Monitoring Docs — Learn how to monitor your app’s health and performance.
Automating Node.js Deployment with PM2 Ecosystem File
In previous parts, you learned to run your Node.js app on a Linux server with PM2 and Nginx. Now, let's elevate your deployment workflow by introducing the PM2 ecosystem file. This powerful feature lets you define multiple apps, set environment-specific variables, and automate process management — all in a single, version-controlled config file.
The ecosystem file is essential for scalable, repeatable deployments. It also makes managing complex production environments much simpler, especially as your projects grow or when you need to deploy multiple Node.js applications on the same server.
1. Create a PM2 Ecosystem File
PM2 supports configuration files in either JSON or JS (JavaScript) format. The JS format (ecosystem.config.js) is more flexible and widely used.
To generate a sample ecosystem file:
pm install pm2 -g # if you haven't already
yarn global add pm2 # or use yarn
pm2 init # creates ecosystem.config.js
This creates a basic ecosystem.config.js in your project directory.
2. Customize Your ecosystem.config.js
Open the file in your favorite editor. Here’s a simple example for a single app:
module.exports = {
apps: [
{
name: 'my-app',
script: './app.js',
instances: 1,
env: {
NODE_ENV: 'development',
PORT: 3000
},
env_production: {
NODE_ENV: 'production',
PORT: 8080
}
}
]
};
What you can configure:
name: Name of your process as it appears in PM2.script: Entry point file (e.g.app.js).instances: Use"max"or a number —maxauto-scales to CPU cores.env: Default environment variables.env_production: Vars for production. PM2 loads these with the--env productionflag.
3. Managing Multiple Node.js Apps
To deploy more than one app on your Linux server, simply add more entries in the apps array:
module.exports = {
apps: [
{
name: 'api-server',
script: './api.js',
env_production: { NODE_ENV: 'production', PORT: 4000 }
},
{
name: 'web-client',
script: './client.js',
env_production: { NODE_ENV: 'production', PORT: 5000 }
}
]
};
This is ideal for microservices or hosting multiple projects on one server.
4. Running and Managing Apps with PM2
To start all apps defined in your ecosystem file:
pm2 start ecosystem.config.js --env production
Common PM2 commands:
pm2 status— View all processes.pm2 restart <name>— Restart a specific app.pm2 reload all— Graceful reload of all apps (zero-downtime for HTTP servers).pm2 stop <name>andpm2 delete <name>— Stop or remove an app.
5. Automate Restarts and Updates
PM2 will automatically restart your app if it crashes. You can also set up other behaviors:
module.exports = {
apps: [
{
name: 'my-app',
script: './app.js',
watch: false, // Set to true for auto-restart on file changes (not recommended in production)
max_restarts: 5, // Limit restarts
restart_delay: 1000 // Delay between restarts (ms)
}
]
};
watch: Auto-restart on file changes (great for dev, risky in prod).max_restarts: Prevents endless crash loops.restart_delay: Adds a pause between restarts.
6. Environment Variables for Production
Store sensitive settings like database URLs or API keys in the environment variable blocks. This keeps your codebase clean and configuration flexible.
Example:
env_production: {
NODE_ENV: 'production',
PORT: 8080,
DATABASE_URL: 'postgres://user:pass@localhost/dbname'
}
7. Micro-Project: Version Control Your Ecosystem File
- Add
ecosystem.config.jsto your project root. - Commit it to your Git repository (omit secrets, or use placeholders).
- Share with your team for consistent deployments.
8. Recap
Using an ecosystem file with PM2 is a best practice for any serious Node.js production deployment on Linux. It enables:
- Repeatable, automated deployments
- Easy management of multiple apps
- Environment-specific configs
- Hassle-free restarts and scaling
Further Reading
- PM2 Configuration Reference — Comprehensive guide to PM2 ecosystem files.
Best Practices for Node.js Production Deployment on Linux
Now that you have a robust deployment with PM2 and Nginx, it's crucial to follow industry best practices for performance, security, and maintainability. Whether your Node.js app is public-facing or internal, applying these practices will help you avoid downtime, data loss, and security issues.
1. Security Best Practices
1. Keep Node.js and Dependencies Updated
- Regularly update Node.js, Nginx, PM2, and all app dependencies.
- Use tools like
npm auditoryarn auditto check for vulnerabilities. - Subscribe to security mailing lists for your Linux distribution.
2. Secure Environment Variables
- Never commit secrets (API keys, DB passwords) to version control.
- Use
.envfiles (with dotenv) or PM2’s environment blocks. - Restrict file permissions (
chmod 600 .env).
3. Harden Nginx and Linux
- Use HTTPS (see Let's Encrypt or a commercial CA).
- Set strong HTTP security headers in your Nginx config:
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
- Enable a firewall (e.g., UFW on Ubuntu) to allow only necessary ports (80, 443).
4. Drop Privileges
- Run Node.js apps as a non-root user.
- Only allow root for Nginx if absolutely necessary (e.g., to bind to ports <1024), then drop privileges with
userdirective.
2. Performance Tuning
1. Use PM2 Clustering
- Leverage multi-core CPUs by running your app in cluster mode:
pm2 start ecosystem.config.js -i max --env production
- PM2 will fork as many processes as you have CPU cores, balancing requests.
2. Enable Gzip Compression in Nginx
gzip on;
gzip_types text/plain application/json application/javascript text/css;
3. Cache Static Assets
- Set long
Cache-Controlheaders for static files in your Nginx config.
3. Monitoring and Logging
1. Monitor Node.js and Nginx
- Use PM2’s dashboard:
pm2 monitfor real-time stats. - Enable PM2 log rotation:
pm2 install pm2-logrotate
- Monitor Nginx logs:
/var/log/nginx/access.logand/var/log/nginx/error.log.
2. Consider External Monitoring Tools
- Services like Keymetrics, New Relic, or Datadog offer advanced metrics, uptime checks, and alerts.
4. Backups and Disaster Recovery
1. Automate Backups
- Regularly back up your database and any important files.
- Use cron jobs or backup services.
2. Test Restores
- Periodically validate that your backups can be restored successfully.
3. Document Your Recovery Steps
- Maintain a runbook with step-by-step instructions in case of disaster.
5. Ongoing Maintenance
- Schedule regular software updates (OS, Node.js, Nginx, PM2).
- Rotate logs and monitor disk usage.
- Review access logs for suspicious activity.
- Remove unused packages and close unnecessary ports.
Micro-Checklist: Node.js Production Deployment on Linux
- Node.js app runs under a non-root user
- PM2 ecosystem file in version control
- Nginx reverse proxy with HTTPS and security headers
- Firewall enabled, only HTTP/HTTPS allowed
- Monitoring and log rotation configured
- Regular backups and disaster recovery plan
Further Reading
- Node.js Production Practices — Covers production deployment and maintenance strategies.
- PM2 Advanced Monitoring — Explains advanced monitoring tools for Node.js apps.
Troubleshooting Common Node.js Deployment Issues
Even with a solid deployment strategy, issues can arise when deploying Node.js apps with Nginx and PM2 on a Linux server. This section covers the most frequent problems and provides practical steps for diagnosing and resolving them.
1. Diagnosing Nginx and Node.js Integration Issues
Symptom: You visit your domain, but see a 502 Bad Gateway or 504 Gateway Timeout.
Steps to troubleshoot:
-
Check PM2 App Status
- Run
pm2 statusto ensure your app is online. - If it's not, check logs with
pm2 logs <app-name>.
- Run
-
Verify App is Listening on the Expected Port
- In your
ecosystem.config.js, confirm thePORTmatches your Nginx proxy config. - Use
netstat -tlnporss -tlnpto see which ports Node.js is binding to.
- In your
-
Inspect Nginx Error Logs
- Look at
/var/log/nginx/error.logfor clues. - Common errors: "Connection refused", "upstream timed out", or permission denied.
- Look at
-
Test Direct Connection
- Use
curl http://localhost:PORTto check if your Node.js app responds directly.
- Use
2. Resolving PM2 Process and Log Errors
Symptom: PM2 apps crash, restart endlessly, or logs aren't updating.
Steps to troubleshoot:
-
Read PM2 Logs
pm2 logs <app-name>for real-time output.- Check for syntax errors, uncaught exceptions, or missing environment variables.
-
Check File Permissions
- Ensure PM2 and your app have read/write access to log files and any directories used.
-
Address Memory Leaks or High CPU
- Use
pm2 monitto watch resource usage. - Consider limiting memory with
max_memory_restartin your ecosystem file:
- Use
max_memory_restart: '300M'
3. Addressing Firewall and Port Conflicts
Symptom: App runs, but cannot be reached from the outside, or Nginx can't reach Node.js.
Steps to troubleshoot:
-
Check UFW/Firewall Rules
sudo ufw status(Ubuntu) orsudo firewall-cmd --list-all(CentOS).- Ensure required ports (e.g., 80, 443 for Nginx; your Node.js app port if accessed directly) are open.
-
Find Port Conflicts
- Use
netstat -tlnporss -tlnpto find if another process is using your desired port. - Change your app's port if needed and update Nginx config accordingly.
- Use
4. Fixing Permission and Ownership Problems
Symptom: PM2 or Nginx fails to start, or there's a "permission denied" error in logs.
Steps to troubleshoot:
-
Check File Ownership
- App files and logs should be owned by the user running PM2 (avoid root).
- Use
chownto reset ownership:sudo chown -R deployuser:deployuser /path/to/app
-
Set Correct File Permissions
- App code:
chmod 644, scripts:chmod 755if executable. - Sensitive files (like
.env):chmod 600.
- App code:
-
Restart Services After Permission Changes
- Reload PM2:
pm2 restart all - Reload Nginx:
sudo systemctl reload nginx
- Reload PM2:
Case Checklist: General Troubleshooting
- PM2 app running and healthy (
pm2 status) - App responds directly on its port
- Nginx logs show no critical errors
- Port and firewall rules allow desired traffic
- Correct file and directory permissions
Further Reading
- Stack Overflow — Community-driven troubleshooting for Node.js, Nginx, and PM2.
- Nginx Error Log Documentation — Explains how to use Nginx logs for troubleshooting.
Case Study: Deploying Multiple Node.js Apps on One Server
It's common to host more than one Node.js application on a single Linux server, whether for microservices, internal tools, or separate client projects. Let's walk through a realistic scenario: deploying two apps, api-server and web-client, each with its own domain, using PM2 and Nginx.
1. Prepare Your Project Structure
Assume the following directory layout:
/var/www/
├── api-server/
│ └── app.js
└── web-client/
└── app.js
Each app should have its own Node.js project, ideally with separate package.json files.
2. Create a Unified PM2 Ecosystem File
You can place a single ecosystem.config.js at /var/www/ to control both apps:
module.exports = {
apps: [
{
name: 'api-server',
script: '/var/www/api-server/app.js',
env_production: { NODE_ENV: 'production', PORT: 4000 }
},
{
name: 'web-client',
script: '/var/www/web-client/app.js',
env_production: { NODE_ENV: 'production', PORT: 5000 }
}
]
};
Start both apps:
pm2 start ecosystem.config.js --env production
3. Configure Nginx for Multiple Domains
Suppose you want api.example.com to point to api-server, and www.example.com to web-client.
Create two Nginx server blocks:
# /etc/nginx/sites-available/api.example.com server { listen 80; server_name api.example.com; location / { proxy_pass http://127.0.0.1:4000; 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; } }/etc/nginx/sites-available/www.example.com
server { listen 80; server_name www.example.com; location / { proxy_pass http://127.0.0.1:5000; 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; } }
Enable both configs:
sudo ln -s /etc/nginx/sites-available/api.example.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/www.example.com /etc/nginx/sites-enabled/
sudo systemctl reload nginx
4. Set Up Domains and DNS
- Point
api.example.comandwww.example.comto your server’s public IP in your DNS provider's dashboard. - Allow propagation time (may take minutes to hours).
5. Monitor and Control Multiple Apps with PM2
pm2 statusshows both apps.- Restart one app without affecting the other:
pm2 restart api-server
- View logs for each app:
pm2 logs web-client
6. Avoiding Common Pitfalls
- Port Conflicts: Ensure each Node.js app listens on a unique port.
- Domain Misrouting: Double-check Nginx
server_nameandproxy_passsettings. - Resource Contention: Monitor server CPU and memory. Consider upgrading or splitting to multiple servers if needed.
- Permission Issues: Each app should run under the correct user, with isolated directories.
Mini-Project: Add a Third App
Try deploying a third Node.js app (admin-dashboard) on port 6000, with its own domain (admin.example.com). Update your PM2 ecosystem file and add a new Nginx server block. Test that all three apps are reachable by their domains!
Further Reading
- DigitalOcean: Host Multiple Websites — Shows how to configure Nginx for multiple sites.
Conclusion and Next Steps
You’ve now completed a full, beginner-friendly guide to deploying Node.js on a Linux server with Nginx and PM2. By following these steps, you’ve set up a production-ready environment, automated your deployments, and learned how to troubleshoot and manage multiple applications with confidence.
Let’s quickly recap what you’ve accomplished:
- Installed Node.js and PM2 on your Linux server.
- Configured Nginx as a reverse proxy to handle incoming traffic and serve your Node.js apps securely.
- Automated deployments using PM2’s powerful ecosystem file.
- Applied best practices for security, monitoring, and reliability.
- Troubleshot common deployment issues and learned how to scale to multiple apps on a single server.
Where to Go Next
- Add SSL/TLS (HTTPS): Secure your apps with free certificates from Let’s Encrypt.
- Automate deployments: Explore CI/CD tools (GitHub Actions, Jenkins, etc.) to push code automatically.
- Advanced monitoring: Integrate external monitoring for uptime, analytics, and alerts.
- Scaling out: Consider Docker, Kubernetes, or cloud platforms for larger projects with high availability.
- Performance tuning: Dive deeper into Node.js profiling, load testing, and caching strategies.
Further Reading
- Node.js Deployment Best Practices — Offers advanced tips for production Node.js deployments.
With these foundations, you’re ready to launch and maintain robust Node.js apps on Linux. Continue to refine your deployment workflows, monitor your systems, and explore advanced topics as your needs grow. Good luck!
About Prateeksha Web Design
Prateeksha Web Design helps businesses turn tutorials like "Deploy Node.js on a Linux Server with Nginx and PM2 (Beginner Deployment Guide)" into real-world results with custom websites, performance optimization, and automation. From strategy to implementation, our team supports you at every stage of your digital journey.
Chat with us now Contact us today.