
This is blog about how you can deploy a Node app to a server with Nginx, whether it is aVPS
,VDS
, ordedicated server
. This assumes you're familiar with basicLinux
&git
commands. This will work for any Node app that runs a server, be it anExpress
app,Next.js
app,Remix
app, etc. Another thing to note is that we will deploy application code; the database will be separated.
Assumptions
- A hosting service with full root access and a domain
- OS: Ubuntu 22.04
- IPv4:
172.172.172.172
- IPv6:
2001:0db8:85a3:0000:0000:8a2e:0370:7334
- Domain Name:
example.com
- Using a
bash
terminal on yourlocal computer - Code is hosted on
GitHub
Workflow Steps
1. Secure Your Server
A. Login to Your Server
- Open your terminal (bash, zsh, etc.)
ssh root@172.172.172.172
You will be prompted to provide the root password. After entering it, you will be logged into your server. You should see something like this:
root@vm172048:~$
If so, you have successfully logged in as the root user.
B. Server Hardening
Now that we are inside our machine, we can start installing the necessary packages and software, but before that, let's upgrade our system. Enter the command below in your terminal:
apt update&& apt upgrade-y
Now, all the current packages are updated to the latest patch version, which keeps the system safe from "unpatched vulnerability exploitation."
C. Create Non-Root User
Deploying as a root user is not recommended, as it has full access to all server resources. So let's create anon-root user calledadmin
and add it to thesudo
group to use commands that need root privileges.
To create asudo
user:
useradd-m-s /bin/bash admin
This will create a new user with the nameadmin
, and you can check the groups of the user using thegroups
command.
groups admin
Let's add theadmin
user to thesudo
group:
usermod-aGsudoadmin
This will add the user to thesudo
group without removing them from the originaladmin
group.
Now let'screate a password for the user:
sudopasswd admin
You will be prompted to enter a new password and retype it.
Tocheck if the password is set properly, open a new terminal and check it by typing:
ssh admin@172.172.172.172
You will be prompted for your new password. After entering it, you should see something like:
admin@vm172048:~$
D. Connect to the Server Using SSH
Usingpassword login is not recommended. You want to use SSH (Secure Shell) and make sure thatSSH is the only way to log in.
If you are a user ofgit
, chances are that you already have anssh key
set up. If you don't already have anssh key
, use the command below to generate a new SSH key on yourlocal machine:
ssh-keygen-t ed25519-C"your_email@example.com"-f ~/.ssh/example
Activate SSH agent:
eval"$(ssh-agent-s)"&& ssh-add ~/.ssh/example
Usecat ~/.ssh/example.pub
to copy the key. Login to server and Then paste it into the server's~/.ssh/authorized_keys
:
mkdir ~/.sshtouch ~/.ssh/authorized_keyssudonano ~/.ssh/authorized_keys
After all of this, you should be able to log in without using a password.
E. Disable Root and Password Login on the Server
To turn off username and password login, type in:
sudonano /etc/ssh/sshd_config
Find these values and set them as follows:
Port 1234# Change the default port (use a number between 1024 and 65535)PermitRootLogin no# Disable root loginPasswordAuthentication no# Disable password authenticationPubkeyAuthenticationyes# Enable public key authenticationAuthorizedKeysFile .ssh/authorized_keys# Specify authorized_keys file locationAllowUsers admin# Only allow specific users to log in
Thisdisallows every login method besides SSH under the user you copied your public key to. It stops login as root and only allows the user you specify to log in. HitCTRL+S
to save andCTRL+X
to exit the file editor. Restart SSH:
sudoservice ssh restart
Now try logging in as root to see if it disallows you. Since you changed the default SSH port from 22 to 1234, you need to mention the port when logging in.
ssh -p 1234 root@172.172.172.172
This will disallow you since root login is disabled. To log in, use:
ssh -p 1234 admin@172.172.172.172
Also, it should go without saying, butyou need to keep the private key safe, and if you lose it,you will not be able to get in remotely anymore.
F. Firewall Configuration
Ubuntu comes with theufw
firewall by default. If not, you can install it with the command:
sudo apt install ufw -y
To see the current status ofufw
, enter:
sudo ufw status
This will show the current status of the firewall. To enable the firewall, run the following command:
sudo ufw enable
First, run some default policies with:
sudo ufw default deny incoming && sudo ufw default allow outgoing
Now, since we changed the SSH port to1234
, allow it through the firewall. Aside from that, we will be using ports 80 and 443 for web serving via HTTP and HTTPS, so allow them too.
sudo ufw allow 1234,80,443
To further improve brute force login via SSH, use the command below:
sudo ufw limit 1234
This will limit port 1234 to 6 connections in 30 seconds from a single IP. If you opened any port incorrectly, you can deny the connection with:
sudo ufw deny <port_number>
Now, to see the current status, use:
sudo ufw status verbose
Restart theufw
to make sure all rules are applied:
sudo ufw reload
After enabling the firewall,neverexit from your remote server connection without enabling the rule for thessh
connection. Otherwise,you won't be able to log into your own server.
G. Fail2Ban Configuration
Fail2Ban provides a protective shield for Ubuntu 22.04 that is specifically designed to block unauthorized access and brute-force attacks on essential services likeSSH and FTP. To installfail2ban
, use the command below:
sudo apt install fail2ban
After the installation is complete, start the Fail2ban service with:
sudo systemctl start fail2ban
Toenable Fail2ban on Ubuntu 22.04 so that it starts automatically when your system boots up, use:
sudo systemctl enable fail2ban
Next, we need to verify if Fail2ban is up and running without any issues using the following command:
sudo systemctl status fail2ban
Now let's configure Fail2ban. The main configuration is located in/etc/fail2ban/jail.conf
, but it's recommended to create a local configuration file:
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Now open thejail.local
file and tweak the values in the Default section:
bantime = 10mfindtime = 10mmaxretry = 5
Fail2Ban works by banning an IP for a specifiedban time after detecting repeated failures within a definedfind time. Themax retry setting determines the number of failures allowed before an IP is banned.
Now restart the Fail2ban service:
sudo systemctl restart fail2ban
To see which service for which jail is activated, enter:
sudo fail2ban-client status
If you have come this far, congratulations! You have secured your server. Now it's ready to deploy yourwebApp
. Enterexit
to end the session.
exit
2. DNS Configuration
Our website will be known by a domain name, in this case,example.com
. To make the domain name point to our server, we need to do some DNS configuration on the side of our domain provider.
a. Login to your domain provider's website.
b. Navigate toexample.com
and then manage DNS Management.
c. Now update theA
andAAAA
records for IPv4 & IPv6 Address.
Record Type | Host Name | Address |
---|---|---|
A | @ | 172.172.172.172 |
AAAA | @ | 2001:0db8:85a3:0000:0000:8a2e:0370:7334 |
d. Next, update theCNAME
Record to forwardwww.example.com
toexample.com
.
Record Type | Host Name | Address |
---|---|---|
CNAME | www | example.com |
CNAME
maps a subdomain to another domain name.
Now it might take a few minutes to propagate to all DNS servers. To check ifexample.com
resolves to your hostIP address
, check DNS propagation using this online tool:DNS Checker.
3. Deploy the Web App
To deploy, we will need to install several packages. First, we will be usinggit
to clone the repository fromGitHub
. Since it's a Node app, we will needNode
installed on our system; I will be using Node version 20. We will be usingnpm
, which comes withNode
. Finally, to manage the app as a background process, we will be usingpm2
.
First, log in to your server using:
ssh -p 1234 admin@172.172.172.172
A. Install All Necessary Dependencies
a. First, check the installation:
nginx -vnode -vnpm -vgit --versionpm2 --version
b. Then update to the latest version & remove unnecessary packages:
sudo apt update && sudo apt upgrade -y && sudo apt autoremove -y
c. Install the latest version ofgit
available:
sudo apt install git -y
d. InstallNode
version lts & its accompanyingnpm
(Node Package Manager) using nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bashsource ~/.bashrcnvm install --ltsnode -v
e. Install thenginx
web server:
sudo apt install -y nginx
f. Install pm2:
sudonpm-ginstallpm2
B. Clone the Code Repo from GitHub
Our code repo will be hosted onGitHub
. We will be cloning the repo to our server using a deploy key.
a. First, generate a deploy key namedexample
:
ssh-keygen -t ed25519 -C "your_email@gmail.com" -f ~/.ssh/example
You will be prompted for a passphrase; just press enter. This will generate a public-private key pair calledexample.pub
&example
in your~/.ssh
folder.
b. Make sure the~/.ssh
folder is owned byadmin
:
sudo chown -R admin ~/.ssh
c. Add GitHub's SSH server public key to the server'sknown_hosts
file:
ssh-keyscan -t ed25519 github.com >> ~/.ssh/known_hosts
d. Next, copy the SSH Public key after outputting the key to the terminal:
cat ~/.ssh/example.pub
e. Use the copied key as a deploy key in GitHub:
- Go to your GitHub Repo
- Click on the Settings Tab
- Click on Deploy Keys option from the sidebar
- Click on the Add Deploy Key Button and paste the copied SSH Public Key with a name of your choice
- Click on Add Key
f. Clone the project from your GitHub Repo to your server's home using:
git clone git@github.com:admin7374/example_app.git
Here,admin7374
is the GitHub username andexample_app
is the Node app we are about to deploy. This will clone the code repo on the server.
C. Run the App with pm2
Now it's time to build and run the Node app in the background:
a. Navigate to the project folder:
cd ~/example_app
b. Create a.env
file:
touch .env
c. Open the.env
file and paste your environmental variables:
sudo nano .env
Example.env
file:
PORT = 8001DATABASE_URL = "database_url"
After pasting, clickCTRL + S
andCTRL + X
to save and exit.
d. Create anecosystem.config.cjs
file in your repo code (best created inside the GitHub repo):
touch ecosystem.config.cjssudo nano ecosystem.config.cjs
Then paste the code below:
module.exports = { apps : [ { name: "example_app", script: "npm start", port: 8001 // optional, if have port set in app } ]}
The above code will run the Node app at port8001
; make sure it matches the port defined in the application. Thescript
is usually how the Node app generally runs. It assumes there is annpm start
script inside yourpackage.json
to run the build code.
e. Next, install the necessary Node modules using:
npm ci
The above command creates anode_modules
folder with all necessary packages to run the code.
f. Now, let's build the code. Type:
npm run build
The above script will build the code for distribution using thebuild
script defined inpackage.json
.
g. Add PM2 Process on Startup:
sudo pm2 startup
h. Start the Node App usingpm2
:
pm2 start ecosystem.config.cjs
i. Save the PM2 Process:
pm2 save
This will save the process to keep running in the background.
j. List all PM2 processes running in the background:
pm2 list
k. If you need to reload for redeployment, use:
pm2 reload example_app
l. To check the PM2 process logs, use:
pm2 monit
This will open an interactive terminal that will show you logs and metadata for each process. Enterq
to quit.
m. Check if the app is working properly using:
curl localhost:8001
This should output properly if the app is working.
D. Serve the App with NGINX
Now it's time to finally serve the app using Nginx. Nginx is a powerful web server, reverse proxy, and load balancer. In this case, we will use thenginx
reverse proxy feature to serve the app running onlocalhost:8001
to the internet.
a. Start and enablenginx
:
sudo systemctl start nginxsudo systemctl enable nginx
b. Verifynginx
is up and running:
sudo systemctl status nginx
If everything went well, the output should indicate that the Nginx service isactive (running)
.
c. If you wish to confirm Nginx's operation via a web browser, navigate to:
http://example.com
This should show the defaultnginx
page.
d. If it's not showing, theufw
firewall may be blocking ports80
and443
. To allow them through theufw
firewall, use:
sudo ufw allow 'Nginx Full'
e. Nginx, like many server software, relies on configuration files to dictate its behavior. Begin by creating a configuration file for your website:
sudo nano /etc/nginx/sites-available/example.com
f. Inside this file, input the following proxy pass configuration:
server { listen 80; listen [::]:80; server_name example.com www.example.com; location / { proxy_pass http://localhost:8001; # Proxy Params - pass client request information to the proxied server proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # If you need to upload files larger than 1M, use the below directive # client_max_body_size 500M; # Web socket upgrade configuration # Uncomment the following lines if you're using websockets in your app # proxy_http_version 1.1; # proxy_set_header Upgrade $http_upgrade; # proxy_set_header Connection "upgrade"; } # Logging access_log /var/log/nginx/example.com.access.log; error_log /var/log/nginx/example.com.error.log warn;}
The proxy pass configuration serves files directly; it proxies requests to a local application (in this case, running on port 8001).
g. With the configuration file created, it isn't live yet. To activate it, you'll create a symbolic link to thesites-enabled
directory:
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/example.com
Think of this step as "publishing" your configuration, making it live and ready to handle traffic.
h. Test the configuration before going live:
sudo nginx -t
Nginx will then parse your configurations and return feedback. A successful message indicates that your configurations are error-free.
i. Time to go live. This requires a reload:
sudo systemctl reload nginx
j. Now check in the web browser, go to:
http://example.com
k. Our current website configuration serves content over HTTP on port 80, which is unencrypted. Let's encrypt it viaLet's Encrypt. First, install certbot:
sudo apt updatesudo apt install software-properties-commonsudo add-apt-repository ppa:certbot/certbotsudo apt updatesudo apt install certbot python3-certbot-nginx
l. Generate SSL Certificates using certbot:
sudo certbot --nginx -d example.com -d www.example.com
Just follow the instructions in the prompts. Your website will be encrypted with a proper SSL/TLS encryption certificate. Check your website in the browser to see if it's installed.
m. Nowadays, certbot, when getting a new cert, will set up auto-renew for you, so it's a sit-and-forget kind of task. But to make sure it worked, you can run:
sudo systemctl status certbot.timer
Now, big congratulations! You have successfully deployed your web app using Nginx. If you want to optimize Nginx, I recommend following this post:Basic Nginx Setup.
This concludes my documentation onhow to deploy a Node app securely with Nginx.
References - For More Information
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse