TL;DR: This guide walks you through manually deploying a Node.js and Express application to an AWS EC2 Ubuntu instance using PM2 and Nginx. You will learn how to prepare your code with environment variables, provision a cloud server with the correct security group ports, and securely connect to your instance via SSH using a
.pemkey pair.
⚡ Key Takeaways
- Use
process.env.PORT || 3000in your Express app to allow the production server to dynamically assign ports. - Define a
startscript (e.g.,"start": "node server.js") in yourpackage.jsonso the server knows exactly how to launch your app. - Configure your AWS EC2 Security Groups to allow inbound traffic on HTTP (port 80), HTTPS (port 443), and SSH (port 22).
- Secure your downloaded AWS key pair by running
chmod 400 your-key.pemto prevent public read access before connecting. - Connect to your remote Ubuntu server using the command
ssh -i [path-to-key] ubuntu@[your-public-ip].
You've just finished coding your Node.js application. It runs perfectly when you type node server.js on your computer, and you can see your website happily sitting at http://localhost:3000.
The Problem: How do you get it on the internet? If you turn off your laptop, your website goes down. If a user tries to visit your site, they can't connect to your local computer. When you look up how to solve this, you are immediately hit with confusing jargon: AWS, EC2 instances, reverse proxies, SSH, and process managers.
The Answer: Deploying a web application simply means copying your code to a computer that never turns off (a server) and configuring it to safely accept visitors from the public internet. By breaking down the process into manageable steps—using a virtual server (AWS EC2), a process manager to keep your app alive (PM2), and a web server to route traffic (Nginx)—the magic of deployment becomes simple logic.
Why It Matters: Automated deployment platforms exist, but they are often expensive and hide how things actually work. By learning to deploy an app manually, you gain total control over your hosting infrastructure, save a massive amount of money, and develop the skills to troubleshoot real bugs when they happen.
In this guide, we will start from scratch. We'll explain every concept, define every term, and show you the exact commands needed to deploy a Node.js app to the internet in 2026.
Preparing Your Node.js Code for the Real World
What is Production? When you build an app on your computer, you are in a "development" environment. When you put that app on the internet for real users, it is in a "production" environment.
Why it matters:
In development, you know your app runs on port 3000. But in production, the server might need your app to run on a different port. We manage this using Environment Variables—dynamic values that change depending on where the app is running.
How to use it: You need to instruct your Node.js app to listen to an environment variable for its port, rather than hardcoding a single number.
Real example:
Look at this basic Express.js server. We use process.env.PORT to check if the server has assigned a specific port. If not, it falls back to 3000.
// server.js
const express = require('express');
const app = express();
// Use the environment variable PORT, or default to 3000
const PORT = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.send('Hello from production!');
});
// Start the server
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
Production Tip: Make sure your
package.jsonfile has astartscript defined (e.g.,"start": "node server.js"). This tells the server exactly how to launch your application.
Step 1: Renting Your Cloud Server (AWS EC2)
What is an EC2 Instance? AWS EC2 (Amazon Elastic Compute Cloud) is simply a computer you rent by the hour. It sits in a massive, highly secure Amazon data center. It has a hard drive, RAM, and a CPU, just like your laptop—except it never turns off, and it has a permanent public IP address.
Why it matters: Without a dedicated server, you have nowhere to put your code. We highly recommend using a cloud provider like AWS because they offer a "Free Tier" for beginners. At scale, our team manages hundreds of these instances through our DevOps and cloud deployment services, but learning the manual way on a single EC2 instance is a crucial foundational skill.
How to use it:
- Create a free AWS account.
- Search for "EC2" and click "Launch Instance".
- Choose "Ubuntu" as your Operating System (a popular, beginner-friendly version of Linux).
- Create a "Key Pair" (a special
.pemfile). This acts as the physical key to unlock your server. Download it and keep it safe! - In "Network Settings" or "Security Groups", check the boxes to allow HTTP (port 80), HTTPS (port 443), and SSH (port 22) traffic.
Real example:
Once the server is running, AWS will give you an IPv4 Public IP address (e.g., 198.51.100.23). You will connect to it from your local computer using SSH (Secure Shell)—a protocol that creates a secure command-line tunnel into your server.
Open your computer's terminal (or Command Prompt) and type these commands, replacing the file path and IP address with your own:
# Secure your downloaded key file (AWS requires this so it isn't publicly readable)
chmod 400 ~/Downloads/my-key-pair.pem
# Connect to your new rented computer
# -i specifies your downloaded security key
# ubuntu is the default username for Ubuntu servers
ssh -i ~/Downloads/my-key-pair.pem ubuntu@198.51.100.23
When you hit Enter, your terminal will transform. You are now securely controlling a computer sitting in an Amazon data center!
Step 2: Preparing the Server (Installing Node.js and Git)
What are we doing? Your new Ubuntu server is a blank slate. It does not have Node.js to run your code, nor does it have Git (a version control tool) to download your code.
Why it matters: You cannot simply drag and drop files onto this server like a normal computer because it doesn't have a graphical desktop interface. We must use a command-line package manager to install the software we need.
How to use it:
We will use apt (Ubuntu's built-in package manager) and curl (a tool to download files from the internet) to install our dependencies.
Real example: Run these commands one by one in your server terminal:
# 1. Update the server's list of available software
sudo apt update
# 2. Install Git so we can download our code
sudo apt install git -y
# 3. Download the Node.js setup script (we are using Node 20 LTS)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
# 4. Install Node.js
sudo apt install -y nodejs
# 5. Verify the installation worked
node -v
# The output should read v20.x.x
Production Tip: Always update your server (
sudo apt update) before installing new software to ensure you get the latest security patches.
Step 3: Downloading Your Code
What is Cloning? Instead of trying to upload files directly from your laptop to the server, we use Git to "clone" (download) the code directly from a repository like GitHub.
Why it matters: This makes updates incredibly easy later. When you change your code on your laptop, you push it to GitHub. Then, you log into your server and simply pull the new changes down.
How to use it:
You will use the git clone command followed by the web URL of your GitHub repository.
Real example:
# Download your code from GitHub to the server
git clone https://github.com/your-username/your-awesome-app.git
# Move into your project folder
cd your-awesome-app
# Install your project's specific dependencies (like Express)
npm install
At this point, you could type npm start and your app would run on the server. However, if you close your terminal window, the app will instantly die. We need a way to keep it running forever.
Step 4: Keeping the App Alive with PM2
What is PM2? PM2 is a Production Process Manager for Node.js. Analogy: Imagine your Node.js app is a toddler. If you leave it alone (close the terminal) or if it trips and falls (your code throws an error), it stops. PM2 is like a dedicated babysitter. If the app crashes, PM2 immediately restarts it. If the server reboots, PM2 starts the app back up.
Why it matters: In a production environment, you need 100% uptime. Your app cannot go down just because you disconnected your SSH session or encountered a minor bug.
How to use it: We install PM2 globally on the server so we can use it anywhere, then tell it to start our application.
Real example:
# Install PM2 globally using npm
sudo npm install pm2 -g
# Start your app using PM2 instead of the standard "node" command
pm2 start server.js --name "my-web-app"
# Tell PM2 to automatically start when the physical server restarts
pm2 startup
Note: Running pm2 startup will output a custom command at the bottom of your terminal (it will look something like sudo env PATH=$PATH...). You must copy that specific command, paste it into your terminal, and press Enter.
# Save the current list of apps so PM2 remembers them on reboot
pm2 save
Your app is now running securely in the background!
Step 5: Setting Up Nginx as a Reverse Proxy
What is Nginx and a Reverse Proxy? Nginx (pronounced "Engine-X") is powerful web server software. A Reverse Proxy is a setup where Nginx sits in front of your Node.js app.
Analogy: Think of Nginx as a hotel receptionist. When a guest (a web request) arrives at the hotel's front door (port 80, the default internet port), they don't go wandering the halls looking for the right room (port 3000). They ask the receptionist. The receptionist (Nginx) quietly takes their request, goes to room 3000 (your Node app), gets the answer, and hands it back to the guest.
Why it matters: Why not just run Node.js directly on port 80? Port 80 is a restricted, privileged port on Linux. Running Node.js as a "root" admin just to access port 80 is a massive security risk. Instead, Nginx safely handles the public internet on port 80 and forwards the traffic internally to your unprivileged Node.js app.
How to use it: We will install Nginx and write a simple configuration file that tells it where to route incoming traffic.
Real example:
# Install Nginx
sudo apt install nginx -y
# Open the default Nginx configuration file in a text editor called Nano
sudo nano /etc/nginx/sites-available/default
Once inside the Nano editor, delete everything and paste this block. Replace yourdomain.com with your actual domain name or the EC2 Public IP address:
server {
# Listen on the default HTTP port
listen 80;
# Your domain name or public IP
server_name yourdomain.com www.yourdomain.com;
location / {
# Forward traffic to your Node.js app running on port 3000
proxy_pass http://localhost:3000;
# Pass along important information about the user's request
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;
}
}
Save and exit Nano by pressing Ctrl+X, typing Y, and pressing Enter. Finally, test and restart Nginx to apply the changes:
# Check if your configuration file has any typos
sudo nginx -t
# Restart Nginx to load the new config
sudo systemctl restart nginx
If you visit your server's IP address or domain in a web browser now, you will see your Node.js app!
Step 6: Securing Your App with HTTPS (SSL Certificate)
What is an SSL Certificate? When you visit a website, you want to see the little padlock icon in the browser URL bar. This means the connection is encrypted via HTTPS. To achieve this, you need an SSL Certificate.
Why it matters: Without HTTPS, anything a user types into your website (like passwords or credit card numbers) is sent as plain text across the internet, where hackers can easily intercept it. Browsers like Google Chrome will also display a massive "Not Secure" warning to your users.
How to use it: We will use a free tool called Certbot provided by Let's Encrypt. It automatically verifies that you own the domain, issues a certificate, and updates your Nginx configuration for you.
(Note: You must have purchased a domain name and pointed its DNS 'A Record' to your EC2 IP address for this step to work. Ensure your DNS changes have propagated before running this command).
Real example:
# Install Certbot and its Nginx plugin
sudo apt install certbot python3-certbot-nginx -y
# Run Certbot to automatically secure your domain
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
Certbot will ask for your email address and whether you want to redirect all HTTP traffic to HTTPS. Always choose yes to ensure maximum security.
Congratulations! You have successfully deployed a production-ready Node.js application from scratch. You configured a cloud server, set up an environment, established continuous process management, routed internet traffic safely, and secured it all with encryption.
Need help building this in production?
SoftwareCrafting is a full-stack dev agency — we ship fast, scalable React, Next.js, Node.js, React Native & Flutter apps for global clients.
Get a Free ConsultationFrequently Asked Questions
Why do I need to use process.env.PORT instead of hardcoding port 3000?
In a production environment, your server might require your application to run on a specific port rather than your local development port. Using process.env.PORT allows your app to dynamically accept the port provided by the host environment. If a port isn't provided, it safely falls back to 3000.
What is an AWS EC2 instance and why do I need one for my Node.js app?
An AWS EC2 instance is a virtual server you rent in Amazon's cloud that stays on 24/7 and provides a permanent public IP address. You need it because your local computer cannot reliably host a public website. If you need help scaling your infrastructure beyond a single instance, SoftwareCrafting offers expert DevOps and cloud deployment services to manage this for you.
How do I securely connect to my new AWS EC2 Ubuntu server?
You connect to your EC2 instance using SSH (Secure Shell) and the .pem key pair file you downloaded during the AWS setup. First, secure your key file by running chmod 400 on it, then use the ssh -i command with your key path, the default ubuntu username, and the server's public IP address.
Which network ports do I need to open when setting up my EC2 instance?
When configuring your EC2 Security Groups, you must allow inbound traffic on port 80 (HTTP) for standard web traffic and port 443 (HTTPS) for secure web traffic. You also need to open port 22 (SSH) so you can remotely access and manage the server from your local terminal.
Why do I need PM2 and Nginx to deploy a Node.js app?
PM2 is a process manager that ensures your Node.js application stays alive and automatically restarts if it crashes. Nginx acts as a reverse proxy web server that securely routes incoming public internet traffic to your local Node.js process. If configuring these tools manually becomes overwhelming, SoftwareCrafting's deployment services can handle the entire setup, security, and optimization process for your business.
Why should I learn manual deployment instead of using automated hosting platforms?
Manual deployment gives you complete control over your hosting infrastructure and is significantly cheaper than automated platform-as-a-service options. It also teaches you how the underlying systems work, which is a crucial skill for troubleshooting real-world production bugs when they inevitably happen.
📎 Full Code on GitHub Gist: The complete
server.jsfrom this post is available as a standalone GitHub Gist — copy, fork, or embed it directly.
