Deploying a NodeJS Application on AWS EC2 Instance — Step by Step

Prachi Said
12 min readJun 21, 2024

--

In the world of cloud computing, Amazon Web Services (AWS) stands out as a leader, offering a wide range of services to meet the diverse needs of businesses and developers. One of the cornerstone services of AWS is Amazon Elastic Compute Cloud (EC2). In this blog, we will explore what Amazon EC2 is, its features and benefits, and provide a detailed guide on “How to host a NodeJS application on EC2 instance”.

In order to understand what EC2 is we need to know two basic things — virtual server and hypervisor —

Virtual Server — A virtual server / virtual machine is a type of software enabled server that can be created by dividing a physical server referred to as host into smaller segments sharing the same resources, each having a different operating system and networking options. All these virtual servers are isolated from each other.

Hypervisor — Hypervisor also known as virtual machine manager is a specialized software that enables a physical machine to run multiple virtual machines simultaneously. As we know each VM operates as an independent computer with its own OS and applications, despite sharing the physical resources. Hypervisor allocates and manages these resources, and ensures each machine operates in isolation from the other.

What is an EC2?

In EC2 we request the AWS to provide us with a scalable virtual server also known as an instance. It provides resizable compute services in the cloud. They are resizable because you can quickly scale up and down the number of server instances you are using in the computing requirements.

Why we use EC2?

Why don’t we buy our own set of servers and work independently?

The reason is suppose you’re a developer, and since you want to work independently you buy some servers, you estimated the correct capacity, and the computing power was also enough to run your application. Now you have to look after the updation of security patches everyday you have to troubleshoot any problem which might occur at a back end level in the server and so many reasons. These are all extra tasks that you may need to do on a regular basis or maybe you’ll hire someone else to do these things for you.

But if you buy an EC2 instance you don’t have to worry about any of those things as it will be managed by Amazon, you just have to focus on your application. That to at a fraction of cost you were incurring earlier.

Types of EC2 instances:

  1. General purpose — In Amazon EC2, a general purpose instance refers to a type of virtual server (instance) that provides a balanced mix of compute, memory, and networking resources. These instances are designed to be versatile and suitable for a wide range of workloads, from small applications to enterprise-grade workloads.
  2. Compute optimized — Amazon EC2 instances are designed to deliver high-performance computing power. These instances are optimized to provide a high ratio of compute power (CPU) to memory, ensuring that applications that rely heavily on computational tasks can perform efficiently. E.g — Gaming
  3. Memory optimized — Amazon EC2 instances are specifically designed to provide high memory-to-CPU ratios, making them ideal for workloads that require substantial amounts of RAM to process large datasets or run memory-intensive applications efficiently. E.g — Big data analytics
  4. Storage optimized — Amazon EC2 instances are tailored to meet the demands of workloads that require high storage capacity and fast access to data. These instances prioritize providing ample local storage resources and high disk throughput.

Note — Charges for AWS EC2 instance types can vary based on which type you select.

Steps for deploying the application

Step 1 — Navigate to EC2 Dashboard

In the AWS Management Console, type “EC2” in the search bar and select “EC2” from the dropdown menu. This will take you to EC2 Dashboard.

EC2 Dashboard

Step 2 — Click on launch instance

Launch Instance

Step 3: Configure the Instance

3.1 — Select Name

Select Name

3.2 — Choose Amazon Machine Image — For our purpose, we can use the Ubuntu Server

3.3 — Choose an Instance type

Select the “t2.micro” instance type. This instance type is part of the free tier for eligible users, offering 750 hours per month for the first 12 months.

3.4 Select a Key Pair

You need a key pair to SSH into your instance.

If you already have a key pair, select it. If not, create a new key pair and download it (.pem file). Keep this file secure as you’ll need it to access your instance.

Create key pair

3.5 Configure Security Group

A security group acts as a virtual firewall to control inbound and outbound traffic. Create a new security group with following rules.

Security group rules

3.6 — Launch Instance — Click on launch instance

Launch Instance

Step 4: Connect to EC2 instance using EC2 Instance Connect

Connect to instance

This should connect you to the virtual machine.

You can also connect to the instance using the SSH key we generated earlier (Optional)

(Optional)

Step 5: Update package list and install dependencies

  1. Setup the EC2 instance
sudo apt update
sudo apt upgrade

2. Install NodeJS and npm

Add the Node.js PPA (Personal Package Archive) to get the latest version of Node.js:

curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -

Then install Node.js and npm:

sudo apt install nodejs -y

Verify Installation of Node.js and npmCheck Node.js version — Check npm version

node -v
npm -v

If npm is not installed for some reason, you can install it separately.

sudo apt install npm -y

3. Install git

You need to install git to bring your files into virtual machine. If you are going to scp (Secure copy protocol) for transferring your files, you can skip this step

sudo apt install git -y

Step 6: Transfer the project files

In this step, you need to transfer all the project files using either git or scp or any other mechanism. For this example, we’ll use git.

Navigate to the directory where you want to clone your project. For example, your home directory:

cd ~

Clone your repository

git clone https://github.com/your-username/your-repo-name.git

Replace your-username and your-repo-name with your GitHub username and repository name respectively.

Step 7: Install project dependencies

  1. Navigate to your Project Directory
cd your-repo-name

2. Install Project Dependencies

Use npm to install the project dependencies defined in your package.json file:

npm install

You cannot directly run your application due to the following reasons:

1. We need to set up the environment variables

2. We need the application to run in the background. If we close the terminal, the server will shut down

3. Our application is running on port 3000. The Firewall does not accept connections to that port as we haven’t enabled it in our security group settings.

Step 8: Setup environment variables

  1. Create the Environment File
sudo vim /etc/app.env

2. Add your variables

In Vim, add your variables in the format VARIABLE=value. For example:

PORT=3000

Press i to enter insert mode in vim Paste the environment variables Press Escape to exit the mode Enter: :x to save and exit

3. Restrict the file permissions for security.

sudo chmod 600 /etc/app.env
sudo chown ubuntu:ubuntu /etc/app.env

Step 9: Run the server in background

We want our node.js server to run in the background, i.e.: when we close our terminal we want our server to keep running

For this purpose, we need a Daemon Service A daemon is a background process or program that is designed to perform tasks without any direct user intervention

We can create a daemon service using systemd.

Nearly every Linux distro comes with systemd, which means forever, monit, PM2, etc are no longer necessary — your OS already handles these tasks.

Creating a daemon service with systemd involves writing a script for the daemon process and creating a service file for systemd to manage it.

A systemd service file is a unit configuration file that defines how systemd starts and manages a certain background service.

  1. Create the systemd Service File

Navigate to the systemd directory and create a new service file, myapp.service.

sudo vim /etc/systemd/system/myapp.service

2. Define the service settings

Add the following content in Vim, modifying as needed for your application.

[Unit]
Description=Node.js App
After=network.target multi-user.target

[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu/app
ExecStart=/usr/bin/npm start
Restart=always
Environment=NODE_ENV=production
EnvironmentFile=/etc/app.env
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=myapp

[Install]
WantedBy=multi-user.target

Change the Description as per your app name.
Change the WorkingDirectory to /home/ubuntu/your-repo-name

There are different ways to start a Node.js application, depending on your project setup. Change the ExecStart to /usr/bin/project_start_command For example: npm start or node server.js or nodemon server.js

Change the SyslogIdentifier to project name for logging purposes

3. Reload systemd and start your service.

sudo systemctl daemon-reload
sudo systemctl enable myapp.service
sudo systemctl start myapp.service

Verify that the service is running properly.

sudo systemctl status myapp.service

Step 10: Set Up a Reverse Proxy with Nginx

A reverse proxy is a server that sits between client devices and web servers, forwarding client requests to the appropriate server.

When a client sends a request to a website, the reverse proxy intercepts this request and forwards it to the backend server (in this case, the Node.js application running on port 3000).

The backend server processes the request and sends the response back to the reverse proxy, which then forwards it to the client.

Why a reverse proxy is commonly used and why the Node.js application was not directly run on ports 80 or 443:

  1. On many operating systems, binding to ports below 1024 (such as 80 and 443) requires elevated privileges (root access)
  2. A reverse proxy can distribute incoming requests across multiple instances of the Node.js application, improving load balancing and allowing the system to scale horizontally
  3. Running the application behind a reverse proxy helps isolate it from direct exposure to the internet. This provides an additional layer of security.

A reverse proxy like Nginx or Apache is more efficient at handling a large number of incoming requests, static content, and SSL/TLS encryption.

In this tutorial, we’ll use Nginx for reverse proxy

  1. Install Nginx
sudo apt install nginx

2. Start and enable Nginx

sudo systemctl start nginx
sudo systemctl enable nginx

3. Verify that Nginx is installed

Go to the VM’s public IP Address. You can find that in network of the instance dashboard

If you see this, it means that nginx is installed correctly

4. Configure Nginx as a Reverse Proxy

Create a new configuration file for your Node.js application. You can place this file in the /etc/nginx/sites-available directory. For example, create a file called nodeapp

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

Add the following configuration to the file:

server {
listen 80;
server_name your_domain_or_IP;

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;
}
}

5. Enable the Configuration

Create a symbolic link to enable the configuration:

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

Test the Nginx configuration for syntax errors:

sudo nginx -t

If there are no errors, restart Nginx to apply the changes:

sudo systemctl restart nginx

6. Adjust Firewall Rules

Ensure that your firewall allows HTTPS traffic:

sudo ufw allow 'Nginx Full'

7. Verify the setup

Open your web browser and navigate to http://IP. You should see your Node.js application, indicating that Nginx is successfully proxying requests to your application running on port 3000.

Step 11: Obtain SSL/TLS Certificate (Optional Step)

You need to obtain a SSL/TLS Certificate inorder to use HTTPS instead of HTTP.

Also we need to configure nginx such that, if the user visits http then they should be redirect to https

There are two approaches:

  1. Using Let’s Encrypt with a Domain Name
  2. Creating a Self-Signed Certificate

11.1 Using Let’s Encrypt with a Domain Name

For this step, you require a domain name because many Certificate Authorities, including Let's Encrypt, prefer or require a domain name to issue SSL/TLS certificates

  1. Purchase a Domain Name:

You can buy a domain name from registrars like Namecheap, GoDaddy, or Google Domains

2. Point the Domain to Your Server:

Set up an A record in your domain’s DNS settings to point to your server’s IP address. You can do this using Amazon Route 53

3. Complete the Configuration:

  1. Install Certbot and the Nginx plugin

Certbot will help you obtain and manage SSL/TLS certificates from Let’s Encrypt:

sudo apt install certbot python3-certbot-nginx

2. Obtain an SSL/TLS Certificate

Obtain an SSL/TLS Certificate

sudo certbot --nginx -d your_domain_or_IP

3. Configure Nginx to Redirect HTTP to HTTPS

Open the Nginx configuration file for your site:

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

Modify the configuration to include both the HTTP to HTTPS redirect and the HTTPS server block. Your updated configuration should look like this:

server {
listen 80;
server_name your_domain_or_IP;

# Redirect all HTTP requests to HTTPS
return 301 https://$host$request_uri;
}

server {
listen 443 ssl;
server_name your_domain_or_IP;

ssl_certificate /etc/letsencrypt/live/your_domain_or_IP/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your_domain_or_IP/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

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;
}
}

Make sure to replace your_domain_or_IP with your actual domain name or IP address

4. Enable the Configuration and Restart Nginx

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

5. Test the Nginx configuration

sudo nginx -t

6. If there are no errors, restart Nginx to apply the changes:

sudo systemctl restart nginx

7. Adjust Firewall Rules (if necessary)

sudo ufw allow 'Nginx Full'

8. Verify the setup

Open your web browser and navigate to http://your_domain. You should be automatically redirected to https://your_domain and see your Node.js application served securely.

11.2 Creating a Self-Signed Certificate

1. Generate a Self-Signed Certificate:

sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/nginx-selfsigned.key -out /etc/ssl/certs/nginx-selfsigned.crt

2. Create a Strong Diffie-Hellman Group:

sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

3. Configure Nginx for the Self-Signed Certificate:

Create a new configuration file:

sudo nano /etc/nginx/snippets/self-signed.conf

Add the following configuration:

ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;

Create another configuration file for SSL settings:

sudo nano /etc/nginx/snippets/ssl-params.conf

Add the following configuration:

ssl_protocols TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256'; # Example cipher suite, adjust as necessary
ssl_ecdh_curve secp384r1;
ssl_session_timeout 10m;
ssl_session_cache shared:SSL:10m;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;

4. Modify the Nginx Configuration File:

Edit your site configuration:

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

Update it to use the self-signed certificate:

server {
listen 80;
server_name 52.87.198.144;

return 301 https://$host$request_uri;
}

server {
listen 443 ssl;
server_name 52.87.198.144;

include snippets/self-signed.conf;
include snippets/ssl-params.conf;

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;
}
}

5. Test and Restart Nginx:

sudo nginx -t
sudo systemctl restart nginx

6. Adjust Firewall Rules (if necessary)

Ensure that your firewall allows HTTPS traffic:

sudo ufw allow 'Nginx Full'

7. Verification

Open your web browser and navigate to http://your_ip_address. You should be automatically redirected to https://your_ip_address, and your Node.js application should be served securely.

Deployed Application

Note that with a self-signed certificate, you’ll receive a browser warning about the certificate not being trusted. This is normal and expected. If you prefer to avoid browser warnings, consider obtaining a domain name and using Let’s Encrypt to obtain a free, trusted SSL certificate.

There you go! You have successfully deployed your Node JS project on AWS EC2 Instance with NGINX AND SSL

--

--