Running Node.js in Production with PM2 and Nginx
A Node.js application that works on your laptop is not automatically ready for production. Development environments often rely on a terminal session, manually started processes and local configuration. A production application must keep running through process failures, server restarts and software updates.
A reliable setup needs more than a command such as npm start. It needs process management, a reverse proxy, HTTPS, secure configuration, logging and a deployment process that can recover from failures.
For many small teams hosting Node.js applications on a VPS, PM2 and Nginx provide a practical starting point. PM2 manages the application process, while Nginx handles incoming web traffic and forwards requests to the application.
This guide explains how to configure the two tools and prepare your Node.js application for production.
1. Use PM2 to Manage Your Node.js Application
PM2 is a process manager that can restart applications after crashes, manage application logs and restore configured processes after a server reboot.
Without a process manager, an application started directly from a terminal may stop when the session ends or when the process crashes.
Install PM2
First, ensure Node.js and npm are installed on your VPS. Then install PM2 using npm:
npm install -g pm2Move into your application directory and start your application. For an application whose entry point is server.js, for example:
pm2 start server.js --name my-node-appReplace server.js with your actual entry point.
Check the process status:
pm2 statusTo inspect application logs, use:
pm2 logs my-node-appThese commands help you check whether the process is running and investigate errors without relying on a development terminal.
Configure PM2 to start after a reboot
Starting an application with PM2 does not, by itself, guarantee that it will return after a server reboot.
Generate the startup configuration:
pm2 startupFollow the command PM2 prints for your operating system and user account. Then save the current process list:
pm2 savePM2 can use the saved list to restore processes when its startup integration runs.
Make sure the startup configuration uses the intended Linux user. If you change users or run PM2 under a different account, the process list and startup service may not match.
Production tip: Test the recovery process in a controlled maintenance window before relying on it. Confirm that the application returns after a reboot and that its environment variables and permissions remain correct.
2. Put Nginx in Front of Node.js
Nginx acts as a reverse proxy between visitors and your Node.js application.
Instead of exposing the Node.js application port directly to the internet, you can configure the application to listen on a local interface and let Nginx accept public HTTP and HTTPS requests.
This setup provides a central place to manage TLS certificates, request limits, timeouts and other web-server settings.
A typical request flow looks like this:
<box border radius="lg" padding={3} gap={2} align="center">
<box background="surface-secondary" radius="md" padding={3} width="100%" align="center">
**Website visitors**
<text color="secondary" size="sm">HTTPS requests</text> </box>
<icon name="arrow-down" size="xl" color="secondary" />
<box background="surface" border radius="md" padding={3} width="100%" align="center">
**Nginx reverse proxy**
<text color="secondary" size="sm">TLS termination, routing and request handling</text> </box>
<icon name="arrow-down" size="xl" color="secondary" />
<box background="surface" border radius="md" padding={3} width="100%" align="center">
**Node.js + PM2**
<text color="secondary" size="sm">Application logic on a local port</text> </box>
</box>
Configure Node.js to listen locally
For an application on port 3000, configure the server to bind to 127.0.0.1 when Nginx runs on the same machine.
For example, in an Express application:
const port = process.env.PORT || 3000;
app.listen(port, "127.0.0.1", () => {
console.log(`Application listening on port ${port}`);
});This prevents direct access to the application through its port from other network interfaces. If your application uses a different framework, configure its listener accordingly.
Configure Nginx as a reverse proxy
An illustrative Nginx server block might look like this:
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_connect_timeout 10s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
}Replace example.com with your domain and adjust the timeouts to suit your application.
This example illustrates HTTP reverse proxying. Before exposing the service publicly, configure HTTPS and redirect HTTP traffic to HTTPS. If the application uses WebSockets, add the required upgrade headers. Upload endpoints may also need a suitable client_max_body_size and request-body timeout.
Validate the Nginx configuration before reloading the service:
sudo nginx -tIf the test passes, reload Nginx using the service-management command appropriate for your operating system.
Configure HTTPS
Use a trusted TLS certificate for your domain. Let's Encrypt is a common option, and Certbot can automate certificate issuance and renewal for supported Nginx configurations.
After setting up HTTPS:
Confirm that the certificate covers the required hostnames.
Redirect HTTP requests to HTTPS.
Test certificate renewal.
Ensure the application correctly handles proxy headers.
Avoid trusting forwarded headers from arbitrary external clients.
Nginx can also serve static assets directly, but whether that is beneficial depends on the application's architecture and caching requirements.
3. Manage Environment Variables and Secrets
Production applications commonly require database credentials, API keys, authentication secrets and service URLs.
Keep these values out of the source-code repository. Instead, load configuration through environment variables or an appropriate secrets-management system.
For example, application code can read a configured port with:
const port = process.env.PORT || 3000;PM2 also supports ecosystem configuration files for defining application settings, working directories and process options.
A basic ecosystem file might look like this:
module.exports = {
apps: [{
name: "my-node-app",
script: "./server.js",
instances: 1,
exec_mode: "fork",
env_production: {
NODE_ENV: "production",
PORT: 3000
}
}]
};Save this as ecosystem.config.js and start the application using:
pm2 start ecosystem.config.js --env productionThis example sets non-secret configuration values. Store sensitive credentials securely and do not commit them to a public repository.
Also verify the effective environment of the running process. Setting NODE_ENV in one shell does not guarantee that a process started by a service manager receives the same value.
4. Set Up Logging and Log Rotation
When an application fails in production, logs are often the first place to look.
PM2 provides commands for inspecting standard output and error logs:
pm2 logs my-node-appFor longer-running services, configure log rotation so that log files do not consume all available disk space.
PM2 supports log rotation through its ecosystem of modules, including pm2-logrotate. Alternatively, you can use an established system logging service where appropriate.
A useful logging setup should help you identify:
Application startup failures.
Unhandled exceptions and rejected promises.
Slow or failing external requests.
Database connection errors.
Repeated process restarts.
Unexpected increases in memory usage.
Avoid logging passwords, session tokens, API keys or unnecessary personal data. Protect access to log files and establish a retention policy that balances troubleshooting needs with storage limits.
5. Deploy Updates Without Unnecessary Downtime
Updating a production application should not depend on someone remembering every command in the correct order.
A repeatable deployment process typically:
Retrieves the intended version of the code.
Installs the required dependencies.
Runs build steps and tests.
Checks application configuration.
Restarts or reloads the application.
Verifies that the new version responds correctly.
Records the deployment result.
For applications configured to support it, PM2 provides a reload command:
pm2 reload my-node-appPM2 can reload applications in supported cluster configurations to reduce downtime. However, a reload is not a universal guarantee of zero downtime. A single process, a slow startup, incompatible application state or failed health checks can still interrupt service.
For more resilient deployments, consider running multiple application instances behind Nginx and using readiness checks before directing traffic to a new instance. Ensure that the application handles shutdown signals gracefully and that long-running requests are allowed to finish appropriately.
If you use a build step, include it in the deployment script rather than relying on manual execution.
SteboHost's Git deployment guide provides a starting point for planning an automated deployment workflow.
6. Monitor Memory, Restarts and Application Health
A process can appear to be running while the application is failing to serve useful requests. Monitoring should therefore cover more than process status.
Pay attention to:
Memory usage: Look for sustained growth that may indicate a memory leak.
Restart counts: Frequent restarts can point to crashes, resource exhaustion or configuration errors.
CPU utilization: High CPU usage may indicate inefficient code or unexpected traffic.
Response times: Slow responses can indicate application, database or network problems.
Health checks: Confirm that the application can serve requests successfully.
Disk usage: Logs, uploaded files and deployment artefacts can gradually consume storage.
Queue depth: If the application uses background workers, monitor jobs waiting to be processed.
PM2 provides process-level visibility, but external uptime monitoring can detect problems from outside the server.
SteboHost's monitoring service is relevant when planning external availability checks. Configure alerts to reach someone who can respond, and test that those alerts are delivered.
7. Common Node.js Production Mistakes
MistakeBetter approachRunning the app in a terminal sessionUse a process manager and configure startup recovery.Exposing the application port publiclyBind locally and route public traffic through Nginx.Forgetting to configure HTTPSInstall a trusted certificate and test renewal.Storing secrets in GitUse protected environment configuration or secrets management.Ignoring log rotationConfigure retention and monitor disk space.Assuming reload guarantees zero downtimeTest the deployment strategy and add health checks.Deploying without a rollback planRetain known-good releases and test recovery.Monitoring only whether the process existsMonitor application health and external availability.
Frequently Asked Questions
Is PM2 suitable for production Node.js applications?
Yes. PM2 is commonly used to manage Node.js processes, restart crashed applications, collect logs and restore configured processes after reboots. It still requires correct configuration, monitoring and a recovery plan.
Do I need Nginx for a Node.js application?
Not always, but Nginx is useful when you need a reverse proxy, HTTPS termination, routing or static-file handling. Other architectures, such as managed load balancers or platform-level proxies, can provide similar capabilities.
Does PM2 guarantee zero-downtime deployment?
No. PM2 can support lower-downtime reloads, especially in suitable cluster configurations, but the application must support the reload behaviour and handle startup and shutdown correctly. Test the deployment process under realistic conditions.
Can I host a Node.js application on a VPS?
Yes, provided the VPS supports the required runtime and gives you sufficient resources and permissions. You are responsible for application configuration, updates, security, process management and monitoring unless your hosting arrangement explicitly covers those tasks.
How do I keep a Node.js application running after a server reboot?
Configure PM2's startup integration for the correct operating-system user, save the process list and test the recovery procedure. Also verify that the application's environment variables and dependencies are available when it starts.
Build a More Reliable Node.js Deployment
Node.js can be a strong production runtime, but reliability depends on how the application is managed around it. PM2 provides process management, Nginx handles incoming web traffic, and monitoring helps identify failures before they become prolonged outages.
Start with a working process configuration, secure reverse proxy, protected environment variables and a tested deployment procedure. Then add health checks, log rotation and external monitoring as your workload grows.
Explore SteboHost Node.js hosting, review the Git deployment guide and plan a production environment that fits your application's needs.
Written by Denis Makokha
SteboHost engineering and operations team. We write practical guides on hosting, domains, email and security for African businesses — based on what we see in the field, not marketing copy.
Ready to get started?
Browse our hosting plans or contact our team for help choosing the right one.