Deploying NodeJS, Express, MongoDB application on AWS EC2

with Google Domain, Let’s Encrypt, NGINX and PM2

Chinmay Patil
9 min readJul 31, 2019
Image from WordArt.com

Introduction

Express, NodeJS and MongoDB are one of the most popular choices to quickly build and deploy applications these days. While we find abundant resources on how to build an application with these platforms and deploy them, there are not a lot of resources which collate all the steps together.

In this article, we will try to collate the different steps one needs to take in order to successfully deploy an application.

Prerequisites:

  1. An application built on NodeJS, Express and MongoDB which is completely functional in your local environment.
  2. Basic understanding of AWS (Amazon Web Services) and EC2 instance creation.

Agenda:

  1. Registering a domain. We will use Google Domains (https://domains.google.com) as it is simple and convenient to use and cheaper than most other alternatives.
  2. Creating a EC2 instance (Ubuntu 18.04) and preparing it for deployment.
  3. Binding domain and Elastic IP of EC2 instance.
  4. Installing Node, NPM in AWS EC2 instance.
  5. Installing MongoDB, enabling access control.
  6. Installing and configuring NGINX to run with Node server.
  7. Setting SSH as per your requirement.
  8. Installing and configuring PM2 to use different environments with respective environment variables.
  9. Setting up ssl certificate using Let’s Encrypt.

1. Domain

Head over to https://domains.google.com and search for the domain that you want. If you need help in generating a domain name with combination of a few words, you can take help of one of the following domain generators:

  1. https://instantdomainsearch.com/domain/generator/
  2. https://www.namemesh.com/
  3. https://www.nameboy.com/
  4. https://domainwheel.com/
  5. https://leandomainsearch.com

Once you have finalised a domain, buy it from Google Domains. After the purchase, you will see the dashboard for your domain which should look similar to this.

Google Domains Dashboard

2. AWS EC2

There are a lot of good resources available on the internet to understand the process of creating an EC2 instance e.g. you can refer to the article here and here.

Create an account with AWS (https://aws.amazon.com/). To complete the verification process, you will need an email, a mobile number and a working credit card (don’t worry, you will not be charged!).

After completing the registration, head over to EC2 section and create a free tier instance of Ubuntu. In the scope of this article, we’ll be using Ubuntu 18.04 with 30 GB (maximum allowed under free tier) storage.

3. DOMAIN to IP Binding

Head over to “Elastic IP” section in AWS console. Allocate a new Elastic IP and associate it with the instance created in the step above.

Once the association is done, you should be able to see that IP in ‘Instances section’ in the places highlighted in red in the image below.

Now it is time to associate this IP with your domain name. Head over to Google Domains dashboard and select the DNS option for your domain from the menu on the left side of the screen.

Here we will add a ‘Custom resource records’ of Type ‘A’ (Address Mapping Record) It associates an IP with a domain/sub-domain.

In the below example you can see that I have mapped ‘@’ with the elastic IP address from AWS (cut out from the image for privacy) in the type ‘A’ record. The ‘@’ represents that all the traffic to the top level domain. You can add ‘www’ in a second A type record and associate it with the same IP as above.

It will make sure that all the requests made to your domain (example.com) and ‘www’ subdomain (www.example.com) are routed to the IP associated with it.

4. NODE and NPM

The official website of NodeJS redirects to this documentation for installation instructions on Ubuntu.

We will install NodeJS version 12.x on our Ubuntu 18.04 EC2 instance.

# Using Ubuntu
$ sudo apt-get update
$ curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
$ sudo apt-get install -y nodejs

# Check the installation
$ node --version
$ npm --version

5. MongoDB

Installation instructions for Mongodb on Ubuntu can be found in the documentation. Make sure you select the right version from the menu in the top left. In this article, we will be using MongoDB version 4.0.

Make sure you add MongoDB to startup after installation using:

$ sudo systemctl enable mongod

By default, MongoDB allows anonymous access which means that user can access mongo shell by simply entering mongo in the terminal.

To take care of basic security, we can take following steps:

  1. Add an admin user with all privileges on all databases. (Point 1 through 5 in this documentation)
  2. Create our database.
  3. Add a database level user for it. (Point 6 though 8 in this documentation)
  4. Disable anonymous access in config file. MongoDB config file is located in /etc/mongod.conf .
  5. Change ‘authorization’ to ‘enabled’ under ‘security’ in config file. It will disable anonymous access.
security:
authorization: enabled

If your web-server and database hosts are different, (and they should be as per good design practise!) block all requests to database host in firewall (security group in aws) except those coming from web-server and on port 27017 (default mongodb operation port).

6. NGINX

We will use NGINX as a reverse proxy server. This is essential if we have our application running on not a well-known port (e.g. 3000) and want the traffic to be proxied to it from a well-known port (e.g. 80 for HTTP, 443 for HTTPS). Following are the steps for installing it on Ubuntu.

  1. Update the Ubuntu repository information:
$ sudo apt-get update

2. Install the package:

$ sudo apt-get install nginx

3. Verify the installation:

$ sudo nginx -v
nginx version: nginx/1.4.6 (Ubuntu)

Next step is to configure NGINX to accept the requests on port 80 and redirect them to the port where our NodeJS application is listening.

We will remove the default file in the /etc/nginx/sites-available folder and add a new file with our configuration in it.

$ cd /etc/nginx/sites-available
$ sudo mv default default.backup //Removing the file by renaming it
$ sudo vi default // Adding a new file

Add the following configuration lines in it to enable proxying the traffic received on port 80 to the port 3000 where our application is running.

server {
listen 80;
server_name YOUR_DOMAIN_NAME;
location / {
proxy_pass http://127.0.0.1:3000;//Enter your IP and port.
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;
}
}

Test the configuration of NGINX using the command:

$ sudo nginx -t

Enable NGINX to run on System Startup:

$ sudo systemctl enable nginx

Restart NGINX for the configuration to take effect.

$ sudo systemctl stop nginx
$ sudo systemctl start nginx

That’s it, now our NGINX server is all set to receive the requests on port 80 and proxy them.

7. SSH

By default, SSH password authentication is disabled for Ubuntu machine created in AWS EC2.

You have to SSH using key-pair generated/used while creating the instance.

While it is a good security practise to keep password authentication disabled, sometimes, you get in a situation where you need to authenticate using password.

Password authentication for SSH can be enabled by following steps.

  1. SSH into the EC2 instance.
  2. Set a password for user. For ubuntu, user is ‘ubuntu’.
$ sudo passwd ubuntu
Changing password for user ubuntu.
New password:
Retype new password:

3. Update the PasswordAuthentication parameter in the /etc/ssh/sshd_config file.

PasswordAuthentication yes

4. Restart the SSH service.

sudo service ssh restart

8. PM2

We will use PM2 to deploy our application. PM2 empowers your process management workflow. It allows you to fine-tune the behaviour, options, environment variables, logs files of each application via a process file. It’s particularly useful for micro-service based applications.

We will create an ecosystem file for PM2 and run our application through it. You can define your app and different environments along with their

The benefit of using an ecosystem file is that you can define all the different parameters in one place along with the ability to define more than one apps (especially in a microservices architecture).

The ecosystem file looks similar to this.

{
"apps": [
{
"name" : "Hello",
"script" : "./index.js", "time" : true,
"log-date-format": "YYYY-MM-DDTHH:MM:SSZ",

"env": {
"JWT_KEY": "XXXXXXXXXXXXX"
},
"env_develop": {
"NODE_ENV" : "develop",
"PORT" : 4000
},
"env_production" : {
"NODE_ENV" : "production",
"PORT" : 3000
},
"env_test" : {
"NODE_ENV" : "test",
"PORT" : 5000
}
}
]
}

Once you have defined the ecosystem file, you can start the app as:

pm2 start app.json --env production --update-env

Here ‘app.json’ is the name of the ecosystem file. ‘production’ is the name of the environment that we are launching. ‘ — update-env’ tells PM2 to update the environment variables for the mentioned environment. Make sure that you have ‘env_production’ defined in the ecosystem file.

The way PM2 launches environments is that it will accept the parameter XYZ mentioned after ‘ — env’ and then look for the environment ‘env_XYZ’ in the ecosystem file.

Further tips: PM2 startup, PM2 save, PM2 cluster

9. Let’s Encrypt

We will achieve 3 things in this section.

  1. Generating and installing the (default/non-wildcard) ssl certificate.
  2. Configuring NGINX to use the ssl certificate.
  3. Redirecting an request received as HTTP to HTTPS.

To generate a free ssl certificate using Let’s Encrypt, we will use the certbot client.

Go to https://certbot.eff.org/ and select the software and system. I’ll select NGINX and Ubuntu 18.04. It will redirect me to the documentation.

  1. SSH into the server.

2. Add certbot PPA

$ sudo apt-get update
$ sudo apt-get install software-properties-common
$ sudo add-apt-repository universe
$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update

3. Install certbot

$ sudo apt-get install certbot python-certbot-nginx

4. Generate and install certificate

sudo certbot --nginx -d YOUR-DOMAIN-NAME

Complete instruction after giving above command. Once finished, you will get the path of the certificate. Keep it handy. We will need it shortly. It will look similar to this:

ssl_certificate/etc/letsencrypt/live/<YOUR-DOMAIN-NAME>/fullchain.pem;ssl_certificate_key/etc/letsencrypt/live/<YOUR-DOMAIN-NAME>/privkey.pem;

Now we need to change the settings of NGINX to use the generated ssl certificate.

$ cd /etc/nginx/conf

Remove any configuration file available in this folder by renaming it.

Add a new configuration file i.e. server.conf

$ sudo nano server.conf

Enter the following contents in the file:

server {
listen 80;
listen [::]:80;
server_name YOUR-DOMAIN-NAME www.YOUR-DOMAIN-NAME;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
server_name YOUR-DOMAIN-NAME www.YOUR-DOMAIN-NAME;
location / {
proxy_pass http://localhost:3000;
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;
}
ssl_certificate /etc/letsencrypt/live/cardsthatmove.com/fullchain.pem;ssl_certificate_key /etc/letsencrypt/live/cardsthatmove.com/privkey.pem;ssl_protocols TLSv1 TLSv1.1 TLSv1.2;ssl_prefer_server_ciphers on;ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;ssl_session_cache shared:SSL:5m;ssl_session_timeout 1h;add_header Strict-Transport-Security "max-age=15768000" always;
}

Now reload NGINX for new settings to take effect.

$ sudo nginx -s reload

At this point you can remove the ‘default’ file in ‘/etc/nginx/sites-available’ that we created in the NGINX step above.

Run the following command from the directory where application resides. It uses the same ecosystem file(app.json) that we created in the PM2 step. It restarts the application.

pm2 restart app.json --env production --update-env

Congratulations! Now your application is ready to be served over HTTPS. Enter your domain name/IP address in the browser and you should see your application being served over HTTPS.

If you like this article, please feel free to share it with your friends and colleagues. Any constructive feedback and criticism is welcome. There is always a scope for improvement.

If you face any issues, feel free to reach out to me. I’m available at patilchinmay01@gmail.com

--

--

Chinmay Patil

Co-Organizer at Bangalore Blockchain Application Development. Network Consultant Engineer at Cisco by day. Blockchain Developer. linkedin.com/in/patilchinmay01