Express.js with Nginx on Linux Server
By: Tobias Schneider
Tags: Nodejs, Hosting, Linux Tutorial, Expressjs, Linux

Requirements

First of all we need a server, this can be a VPS like an AWS EC2, Google Compute Engine, DigitalOcean Droplet or even a Dedicated box somewhere in a datacenter, but the latter would definitely be a bit much for our purposes.

Then we need to pick an OS, for this tutorial we’re using Debian 10, but it should be more or less the same for Ubuntu.

SSH into the server and we’re good to go.

Make sure it’s up to date

Before we get to installing the software we need we have to make sure that our server is up to date by running the following commands.

sudo apt update

sudo apt upgade -y

Install Software

The Webserver

Installing the webserver is as simple as running a singe command.

sudo apt install nginx -y

Node.js

To install node.js we need to add it’s repository to our package manager.

curl -sL https://deb.nodesource.com/setup_lts.x | sudo bash -

And install it.

sudo apt install nodejs -y

This gives us node.js and npm.

The Process Manager

In order to run and restart the node.js process in case of a crash we need a process manager and we can install it as a global npm package.

sudo npm i -g pm2

Create a User

Now we need to create a user ( joe ) who will run our app. This command creates the user and it’s home directory under /var/www .

sudo useradd -b /var/www -mUs /bin/bash joe

To be able to upload our app we need to give him a password, but please choose a secure one.

sudo passwd joe

Setting up the App

Next we upload our app to /var/www/joe/app using the username joe , the password we just set and a SFTP client like Cyberduck.

For the purpose of this tutorial I’m using the Hello World example express.js provides.

const express = require('express')

const app = express()

const port = 3000 app.get('/', (req, res) => {

res.send('Hello World!')

})

console.log(`Example app listening at

}) app.listen(port, () => {console.log(`Example app listening at http://localhost:${port}` })

Next we need to go back to our termial and login as joe by running these commands.

sudo su joe

cd ~/app

Since the Hello World example doesn’t come with a package.json I’m going to create one using these commands, but since you already have an app you want to host, you should be good.

npm init -y

npm i express

You would just need to run npm i to install your dependencies or npm i --only=prod to prevent dev-dependencies from installing.

If you don’t have it already add a start script to the package.json file.

{

"name": "joe",

"version": "1.0.0",

"description": "",

"main": "app.js",

"scripts": {

"start": "node app.js",

"test": "echo \"Error: no test specified\" && exit 1"

},

"keywords": [],

"author": "",

"license": "ISC",

"dependencies": {

"express": "^4.17.1"

}

}

Make sure main is set to the js file you’re tying to start. In my case it’s app.js .

Now we can just check if everythings fine so far by running npm start and opening http://[YOUR_SERVERS_IP]:3000/ , but we’re not done yet.

Enabling Socket Mode

We need a way for nginx, our reverse proxy, to talk to our app. This can be done in two ways, either using a local port nginx redirects the requests to, or by a socket file, which is the method I like more, because we save ourselfs from the internal networking overhead, but this requires some small changes to our code.

const fs = require('fs')

const path = require('path')

const express = require('express')

const app = express() app.get('/', (req, res) => {

res.send('Hello World!')

}) const socketDir = path.join(__dirname, 'run')

const socketFile = path.join(socketDir, 'express.sock') // Create socket dir

if (!fs.existsSync(socketDir)) {

fs.mkdirSync(socketDir)

} // Remove old socket file if exists

if (fs.existsSync(socketFile)) {

fs.unlinkSync(socketFile)

} server = app.listen(socketFile, () => {

fs.chmodSync(socketFile, '777')

})

So what did we do there, we required fs and path to be able to interact with the filesystem and build a path string. Then we check if the socket directory run/ exists and create it if it doesn’t. We clean up an old, dead socket file if it still exists (This can sometimes happen if the app crashes) and after that we run our app on the socket file.

Configuring NGINX

Now we need to configure nginx to send all requests to that socket file. Therefore we need to leave joe ‘s account with the exit command and create our nginx config file.

sudo nano /etc/nginx/sites-available/joe

With the following content.

server {

listen 80;

listen [::]:80; server_name example.com;

proxy_pass

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;

}

} location ~ ^(.*)$ {proxy_pass http://unix:/var/www/joe/app/run/express.sock 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;

Don’t forget to change example.com to your domain.

Now we need to link it for nginx to use it.

sudo ln -s /etc/nginx/sites-available/joe /etc/nginx/sites-enabled/

And restart nginx for our changes to take effect.

sudo systemctl restart nginx

Setting up the Process Manager

To set up pm2 we need to login to joe 's account again.

sudo su joe

cd ~/app

And run.

pm2 --name app .

In order to automatically start after a server reboot we need do dump the running process states and enable the startup hook.

pm2 dump

pm2 startup

The latter gives us another command we need to run, but since joe doesn’t have sudo privileges we have to exit and run it with our sudo enabled user.

sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u joe --hp /var/www/joe

That’s it, we’re up and running.