Hosting a Node.js Express application on a VPS using Apache, PM2, and Systemd
DevOps14 min read

By Akshay Singh

Share this article:

How to Host a Node.js Express App on a VPS Using Apache, PM2, and Systemd

When developing a Node.js application locally, running node server.js is perfectly fine. But when it's time to move to production, that approach falls short. If your application crashes, it won't restart. If the server reboots, your app remains offline. Furthermore, running Node.js directly on port 80 or 443 poses security risks and lacks the advanced routing and caching features of a dedicated web server.

While Nginx is highly popular for modern Node.js setups, Apache HTTP Server remains an incredibly powerful, rock-solid alternative. If you are already running an Apache server (perhaps for a PHP/WordPress site) or simply prefer its ecosystem and .htaccess flexibility, you can easily configure Apache to serve as a reverse proxy for your Node.js application.

In this guide, I will walk you through the end-to-end process of deploying a Node.js Express app on a Ubuntu VPS. We will cover process management using both PM2 and native Systemd, and configure Apache to securely route traffic to your application.


Prerequisites

Before we begin, ensure you have:

  • A Linux VPS (Ubuntu 22.04 LTS or 24.04 LTS recommended).
  • SSH access to your server.
  • Node.js and npm installed on your server.
  • A registered domain name pointing to your server's public IP address.

Step 1: Set Up Your Express Application

First, connect to your server via SSH:

ssh your-username@your-server-ip

Create a directory for your application in the standard web root /var/www/:

sudo mkdir -p /var/www/my-express-app
sudo chown -R $USER:$USER /var/www/my-express-app
cd /var/www/my-express-app

Let's initialize a basic Express application. (If you have an existing app, you can clone it here instead).

npm init -y
npm install express

Create a server.js file:

nano server.js

Paste the following simple Express server code:

const express = require('express');
const app = express();
const port = process.env.PORT || 3000;

app.get('/', (req, res) => {
  res.send('Hello from Express behind Apache!');
});

app.listen(port, () => {
  console.log(`App running on http://localhost:${port}`);
});

Save and exit.


Step 2: Process Management (Keep Your App Alive)

We need a mechanism to ensure your Node.js application restarts automatically if it crashes or if the server reboots. You have two primary options: PM2 or Systemd. Choose the one that fits your workflow.

Option A: Using PM2

PM2 is a robust, widely-used production process manager specifically built for Node.js.

  1. Install PM2 globally:
sudo npm install -g pm2
  1. Start your application:
pm2 start server.js --name "my-express-app"
  1. Enable PM2 on server boot:

Run the startup script generator:

pm2 startup

PM2 will output a specific command (looking something like sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u your-username...). Copy and run that exact command in your terminal.

Finally, save the current PM2 process list:

pm2 save

Option B: Using Native Systemd

If you prefer to avoid global npm packages, you can create a native Linux Systemd service.

  1. Create the service file:
sudo nano /etc/systemd/system/my-express-app.service
  1. Add the following configuration: (Ensure you replace your-username with your actual Linux user).
[Unit]
Description=My Express App
After=network.target

[Service]
Environment=NODE_ENV=production
Environment=PORT=3000
Type=simple
User=your-username
WorkingDirectory=/var/www/my-express-app
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
  1. Enable and start the service:
sudo systemctl daemon-reload
sudo systemctl start my-express-app
sudo systemctl enable my-express-app

Step 3: Install and Configure Apache as a Reverse Proxy

Your Express app is now running securely on port 3000. Next, we will configure Apache to listen on port 80 and forward incoming traffic to port 3000.

  1. Install Apache:
sudo apt update
sudo apt install apache2 -y
  1. Enable Required Apache Modules:

To act as a reverse proxy, Apache requires specific modules to be enabled. Run the following commands:

sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod headers

Restart Apache to apply the new modules:

sudo systemctl restart apache2
  1. Create an Apache Virtual Host:

Create a new configuration file for your domain:

sudo nano /etc/apache2/sites-available/my-express-app.conf

Add the following configuration, making sure to replace example.com with your actual domain name:

<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com

    ProxyPreserveHost On
    ProxyPass / http://localhost:3000/
    ProxyPassReverse / http://localhost:3000/

    # Optional: Log files for this specific site
    ErrorLog ${APACHE_LOG_DIR}/my-express-app-error.log
    CustomLog ${APACHE_LOG_DIR}/my-express-app-access.log combined
</VirtualHost>

Understanding the Configuration:

  • ProxyPreserveHost On ensures that the original Host header sent by the client is passed to your Express app.
  • ProxyPass and ProxyPassReverse map the root URL (/) to your local Node.js server running on port 3000.
  1. Enable the Virtual Host:
sudo a2ensite my-express-app.conf
  1. Test and Restart Apache:

Always verify your configuration syntax before restarting:

sudo apache2ctl configtest

If it says Syntax OK, restart Apache:

sudo systemctl restart apache2

If you visit http://example.com in your browser, you should see "Hello from Express behind Apache!".


Step 4: Secure the App with HTTPS (Let's Encrypt)

Serving traffic over HTTP is insecure. We will use Certbot to provision a free SSL certificate from Let's Encrypt and automatically configure Apache to use it.

  1. Install Certbot and the Apache Plugin:
sudo apt install certbot python3-certbot-apache -y
  1. Run Certbot:
sudo certbot --apache -d example.com -d www.example.com

Certbot will ask for an email address and prompt you to agree to the terms of service. It will then automatically locate your Virtual Host, provision the SSL certificate, and update your configuration to force HTTPS redirection.

  1. Verify Auto-Renewal:

Let's Encrypt certificates expire every 90 days. Certbot automatically adds a systemd timer to renew them. You can test this process by running:

sudo certbot renew --dry-run

Conclusion

By placing your Node.js Express application behind an Apache reverse proxy and using PM2 or Systemd for process management, you have created a highly reliable production environment.

Apache excels at handling complex routing, serving static assets, and managing SSL certificates, allowing your Node.js application to focus entirely on its core logic. This setup scales beautifully, allowing you to run multiple different Express apps on different ports, all securely proxied by a single Apache server.

nodejsexpressapachepm2systemdvpsdevops
TheDailyDevsTheDailyDevs
TheDailyDevs is a developer-first blog and knowledge hub created by passionate engineers to share real-world development tips, deep-dive tutorials, industry insights, and hands-on solutions to everyday coding challenges. Whether you're building apps, exploring new frameworks, or leveling up your dev game, you'll find practical, no-fluff content here, updated daily.