How to Host Multiple Node Apps with nginx and pm2
I’ve done this before but someone asked me the other day on my YouTube channel how to host multiple Node apps on one server.
This is basically a case of making sure your Node apps are running independently (for example using pm2) on different internal ports and configuring a reverse proxy (like nginx) to route traffic for a particular path or subdomain to the locally hosted Node apps.
Here’s how you can do it.
What you’ll need on your server #
I’m assuming you’ve got a server already setup that you have access to. In addition you’ll need:
- Nginx installed (tutorial here)
- Node.js availble (tutorial here)
- PM2 installed (tutorial here)
Login and setup your first app #
So the first thing is to actually get a Node app up and running on your server.
If you already have one ready to go then you can skip to the next step.
We’ll just create a really basic Node.js app to host initially and then we can copy this project for the other apps we’ll be hosting.
ssh user@myhost
cd ~
mkdir app1
cd app1
nano app1.js
Inside the app1.js
file let’s add a really basic Node server that responds with a bit of HTML.
const http = require('http');
const hostname = '127.0.0.1';
const port = 3000;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end('<h1>App 1</h1>');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
With the code for the Node app created, let’s set this running using pm2
.
Run the Node app with PM2 #
So you might be tempted to just run node app1.js
as you might do on your local computer but that would only run it whilst your current shell session is active.
Instead we’ll use PM2 to run it which will detach it from the current shell and allow us to run more.
pm2 start app1.js
You can check the app is running by using pm2 list
which should give you some output like this:
┌────┬────────────────────┬──────────┬──────┬──────────┬──────────┬──────────┐
│ id │ name │ mode │ ↺ │ status │ cpu │ memory │
├────┼────────────────────┼──────────┼──────┼──────────┼──────────┼──────────┤
│ 0 │ app1 │ fork │ 0 │ online │ 0% │ 18.1mb │
└────┴────────────────────┴──────────┴──────┴──────────┴──────────┴──────────┘
The Node app just be up and running and receving requests (and returning that HTML fragment) on http://localhost:3000
now.
We can check this with a quick cURL
command:
curl http://localhost:3000
<h1>App 1</h1>
Of course this won’t be available on the public IP/domain of the server so let’s configure Nginx to point requests for a particular URL to the Node app.
Configuring nginx as a reverse proxy #
Open up your nginx config file for your default service or domain:
sudo nano /etc/nginx/sites-available/default
You might need to change ‘default’ for whatever site you currently have configured in nginx.
Then add a location
block that proxies the requests for a particular path to the Node app (i’ve included a more complete nginx config file here so you can see exactly where it should go).
server {
root /var/www/html/mydomain.com;
server_name mydomain.com;
index index.html index.htm index.nginx-debian.html;
location /app1 {
proxy_pass http://localhost:3000/;
}
listen [::]:443 ssl; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/mydomain.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/mydomain.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
Here’s what your nginx config file should roughly look like with the bottom lines being used for managing secure connections with Let’s Encrypt.
The key things here are we’re saying any requests for mydomain.com/app1
will be routed to the local Node app on port 3000
.
Let’s then test the nginx config:
sudo nginx -t
If that all looks good, reload the nginx service:
sudo systemctl reload nginx
Once that’s done, you should be able to browse to the that path on your domain and see the HTML being sent back from the Node app:
Hosting multiple Node apps #
Now we’ve got the basic mechanism setup for hosting a Node app, it’s basically a case of copy and paste/rinse and repeat to host more.
First off, let’s just copy the existing app1
app to a new folder.
cp -R ~/app1 app2
In reality this would be a proper app but this will do for demonstration purposes.
Then, we need to make sure that the second app is served on a different port otherwise we’ll get a clash.
cd app2
mv app1.js app2.js # Rename just so we can tell the difference
nano app2.js
const http = require('http');
const hostname = '127.0.0.1';
const port = 4000; // New port number
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end('<h1>App 2</h1>'); // Some different output to tell the apps apart
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
Then we just setup this app running on the different port with pm2
.
pm2 start app2.js
And once the app is up and running, we can simply add another location
block in our nginx config.
sudo nano /etc/nginx/sites-available/default
# Using a different path
location /app2 {
proxy_pass http://localhost:4000/; # And making sure the port number matches the Node app
}
Then test and reload nginx:
sudo nginx -t
sudo systemctl nginx reload
And then browse to the new path in the browser to see the second Node app output.
Rinse and repeat! #
As you can see it’s pretty simple to host multiple Node apps on one server.
You just need to make sure each Node app is running (in the background with PM2) and that you have the appropriate config setup in nginx to ensure requests for a certain path are routed to one of your running Node apps.
Thanks for reading.
Still stuck? Check out the video for more detail!