Hosting an Express Server NodeJS Application with Linode

Published Nov 20, 2017

So you want to build an express app and run it on Linode and you’ve found a surprising lack of guidance and walkthroughs? You’ve come to the right place… Step by step instructions for just that.

Proceed at your own risk. This setup is not a guarantee of server security.

Buy a domain

I’ll be using a fake domain of for this write up. Where you see in this guide, replace with your domain name.

Get your information together

Linode host is an Ubuntu Linux operating system. As of time of writing this guide the LTS version was 16.04.
You access this server with SSH. Here’s some fake info we’ll use in this example:

  • Linode username will be: linode-username
  • Linode Email account:
  • Linode host IP address*:
  • Linode host root username: root
  • Linode host username that is not root**: not_root
    Linode web portal -> linodes -> Remote Access tab -> SSH Access field
    not_root could be anything but I like descriptive names.

Connect via SSH

Note: Initially you’ll connect with the root user, once security settings are in place you won’t use root anymore.

(personal computer at home)
ssh root@

At this point you’ll still be in a terminal, but running commands remotely on the Linode host server.

Set hostname

(linode host)
hostnamectl set-hostname photo-blog

Check hostname to ensure it is set correctly

(linode host)
// => photo-blog

Update the /etc/hosts file to put the site on the internet

(linode host)
sudo nano /etc/hosts

Edit/Add these lines in the hosts file:

(linode host) photo-blog photo-blog

Setup the timezone

(linode host)
dpkg-reconfigure tzdata

Add a not root user

(linode host)
useradd not_root
// Follow prompts…

Then add them to sudo so they can do stuff

(linode host)
useradd not_root sudo

Send your public key to the server (from local machine)

In another tab on your physical computer (not the SSH’d into Linode terminal window).
Note: There is definitely more to this than just this command, you need to have already set up a private key on your home machine. Not in scope for this guide.

(personal computer at home)
scp ~/.ssh/ not_root@

Now that you’ve set up some basics for the not_root user, you really never need to use the root user again. Root user will be locked out and disallowed after these steps.

Logout from root user

Note: Back in the SSH linode terminal window.

(linode host)

Log back in with new not_root user

(personal computer at home)
ssh not_root@

Configure the sshd_config file

sh (linode host):~$
sudo nano /etc/ssh/sshd_config 

Set the port to 900

It doesn’t have to be 900 just don’t be the default (44) as that is an obvious attack vector.

>nano (linode host)
port 900

Disallow root logins at all via ssh with:

PermitRootLogin no 

Use Ctrl+X to exit nano editor, (y) to save changes, and Enter to overwrite the current file.

After making any changes to sshconfig run

(linode host)
sudo systemctl restart sshd

Install fail2ban

(linode host)
sudo apt-get install fail2ban -y

Note: Fail2ban auto bans an ip for 1 week if you fail to login with password via ssh (3 attempts)

Copy the original config files to local copies

As the originals are overwritten with any updates. Fail2ban knows to cascade directives using jail.conf first and then overwriting any rules in jail.local last ensuring your rules are applied.

(linode host)
sudo cp /etc/fail2ban/fail2ban.conf /etc/fail2ban/fail2ban.local
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Edit the jail.local file

(linode host)
sudo nano /etc/fail2ban/jail.local

Set the [sshd] and [sshd-ddos] ports to 900 (anything <1024)

nano> (linode host)
enabled = true
port = 900
logpath = %(sshd_log)s
# This jail corresponds to the standard configuration in Fail2ban.
# The mail-whois action send a notification e-mail with a whois request
# in the body.
enabled = true
port = 900
logpath = %(sshd_log)s

Use Ctrl+X to exit nano editor, (y) to save changes, and Enter to overwrite the current file.

Restart the service after making changes to the file. Exit the SSH session (for the last time with default settings).

(linode host)
sudo service fail2ban restart

Note: At this point you can test the new settings were applied and are working by attempting to log back in. This attempt should fail unless port 900 and the not_root user are specified.

Log back in as the not_root user using the approved SSH port

(personal computer at home)
ssh not_root@ -p 900

Set up port forwarding for web (443/80) to be routed to express (3000)

(linode host)
sudo iptables -A PREROUTING -t nat -i eth0 -p tcp — dport 80 -j REDIRECT — to-port 3000
sudo iptables -A PREROUTING -t nat -i eth0 -p tcp — dport 443 -j REDIRECT — to-port 8000

This will allow your Express server (serving on port 3000) to have data forwarded to the world wide web.

Configure UFW (uncomplicated firewall)

Edit the ufw rules file to include IPV6

(linode host)
sudo nano /etc/default/ufw
// Change no to yes

Setup the default blocking

This allows internal but blocks all external requests (info can be sent out only as a baseline)

(linode host)
sudo ufw default deny incoming
sudo ufw default allow outgoing

As of now all SSH is disallowed.

Allow SSH connections to our previously set SSH port (900)

(linode host)
sudo ufw allow 900

NOTE: You can check current enforced rules with: sudo ufw status verbose

Allow HTTP and HTTPS connections

(linode host)
sudo ufw allow http
// equivalent to: sudo ufw allow 80
sudo ufw allow https 
// equivalent to: sudo ufw allow 443

Allow HTTP forwarding from Express (port 3000) to HTTP (80/443) and back

(linode host)
sudo ufw allow proto tcp from any to  any  port  80,443,3000

Enable the firewall

(linode host)
sudo ufw enable

Check the status rules with sudo ufw status verbose
NOTE: Recommendations on further server hardening are appreciated. Pull request or message me.

At this point you can import (e.g.: git clone, secure FTP) any Express server project as long as it’s listening for connections on port 3000.
If you want to see a demo project, continue:


Install mongo and enabled startup mongod daemon running

(linode host)
sudo apt-key adv — keyserver hkp:// — recv EA312927
echo “deb xenial/mongodb-org/3.2 multiverse” | sudo tee /etc/apt/sources.list.d/mongodb-org-3.2.list
sudo apt-get update
sudo apt-get install mongodb-org -y

Create directory for database and make it writeable

(linode host)
sudo mkdir /data
sudo mkdir /data/db
sudo chown mongodb:mongodb /data/db
sudo systemctl enable mongod
// The last command enables mongod to run at startup if the server resets
service mongod status
// This command should show Active: active (running) for the DB

Download and install git, node, and other dependencies

ForeverJS should help keep the server running if something happens like a reboot.

(linode host)
sudo apt-get install git -y
sudo apt install nodejs-legacy
sudo apt-get install npm
sudo npm install -g forever

Clone repo and install npm dependencies

(linode host)
git clone
cd paparanni-web-server/
sudo npm install

Creating thumbnails requires imagemagick

(linode host)
sudo apt-get install imagemagick -y
sudo apt-get install graphicsmagick -y
mkdir public/images/uploads
mkdir public/images/uploads/thumbs
sudo chmod 776 public/images/uploads -R


Note: ForeverJS with production node env, start command runs as a daemon so you can exit ssh without it stopping the server.

(linode host)
NODE_ENV=production forever start index.js

The linode environment requires that the ‘use strict’; directive be at the top of all server files (index.js and routes/index.js).

Right now the site can be accessed with HTTP and HTTPS and SSH (SSH on the specified port only).
Please let me know if I’ve overlooked anything. I welcome suggestions and comments!

Discover and read more posts from Tom Geraghty
get started