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
For our purpose, we can use the Ubuntu Server
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.
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.
A security group acts as a virtual firewall to control inbound and outbound traffic. Create a new security group with following rules.
Click on launch instance
This should connect you to the virtual machine.
You can also connect to the instance using the ssh key we generated earlier (Optional)
sudo apt update
sudo apt upgrade
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 npm
Check Node.js version
node -v
Check npm version
npm -v
If npm is not installed for some reason, you can install it separately.
sudo apt install npm -y
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
In this step, you need to transfer all the project files using either git or scp or any other mechanism
For this example, I'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.
cd your-repo-name
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:
- We need to set up the environment variables
- We need the application to run in the background. If we close the terminal, the server will shut down
- 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
sudo vim /etc/app.env
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
sudo chmod 600 /etc/app.env
sudo chown ubuntu:ubuntu /etc/app.env
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.
Navigate to the systemd directory and create a new service file, myapp.service
.
sudo vim /etc/systemd/system/myapp.service
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
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
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:
- On many operating systems, binding to ports below 1024 (such as 80 and 443) requires elevated privileges (root access)
- 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
- 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, I'll use Nginx
for reverse proxy
sudo apt install nginx
sudo systemctl start nginx
sudo systemctl enable nginx
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
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;
}
}
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
Ensure that your firewall allows HTTPS traffic:
sudo ufw allow 'Nginx Full'
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.
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:
- Using Let's Encrypt with a Domain Name
- Creating a Self-Signed Certificate
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
You can buy a domain name from registrars like Namecheap, GoDaddy, or Google Domains
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
Certbot will help you obtain and manage SSL/TLS certificates from Let's Encrypt:
sudo apt install certbot python3-certbot-nginx
Obtain an SSL/TLS Certificate
sudo certbot --nginx -d your_domain_or_IP
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
sudo ln -s /etc/nginx/sites-available/nodeapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
sudo ufw allow 'Nginx Full'
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.
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/nginx-selfsigned.key -out /etc/ssl/certs/nginx-selfsigned.crt
sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048
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;
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;
}
}
sudo nginx -t
sudo systemctl restart nginx
Ensure that your firewall allows HTTPS traffic:
sudo ufw allow 'Nginx Full'
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.
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