Skip to content
AWS

Deploying a Node.js App on AWS EC2 with Nginx and PM2

A complete, practical walkthrough for deploying a Node.js application to an AWS EC2 instance behind Nginx, kept running with PM2, and secured with a free TLS certificate.

February 18, 2026 9 min read

Deploying a Node.js application to a virtual machine is a skill that stays useful no matter how many managed platforms come and go. Knowing how the pieces fit together — the operating system, the process manager, the reverse proxy and TLS — gives you the confidence to debug production instead of guessing. This guide walks through a reliable, repeatable setup on an AWS EC2 instance running Ubuntu.

By the end you will have a Node.js app running as a managed process, sitting behind Nginx on port 80/443, surviving reboots, and served over HTTPS with an automatically renewing certificate.

What you need before starting

  • An AWS account and a running EC2 instance (a small t3.micro is fine to begin with).
  • A key pair so you can connect over SSH.
  • A domain name you can point at the instance (optional but recommended for TLS).
  • A Node.js application with a defined start command, for example node server.js.

Step 1 — Open the right ports

In the instance security group, allow inbound traffic on port 22 (SSH), port 80 (HTTP) and port 443 (HTTPS). Keep the rule set as tight as the application allows — there is rarely a reason to expose the Node port (for example 3000) directly to the internet, because Nginx will sit in front of it.

Step 2 — Prepare the server

Connect over SSH and update the base packages before installing anything else. Installing Node.js from the NodeSource repository gives you a current, predictable version rather than whatever ships in the default archive.

bash
ssh -i key.pem ubuntu@your-server-ip
sudo apt update && sudo apt upgrade -y
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs nginx
node -v && npm -v

Step 3 — Get your code onto the box

Clone the repository (or copy an artifact), install production dependencies and build if needed. Keeping application code under a predictable path such as /var/www makes later automation easier.

bash
sudo mkdir -p /var/www && sudo chown -R $USER:$USER /var/www
cd /var/www
git clone https://github.com/your/repo.git app
cd app
npm ci --omit=dev
npm run build   # if your app has a build step

Step 4 — Keep the app alive with PM2

PM2 is a process manager that restarts the app if it crashes, keeps logs, and can regenerate a startup script so the process returns after a reboot. This is what turns a one-off node command into something you can leave running.

bash
sudo npm install -g pm2
pm2 start server.js --name api
pm2 startup systemd   # prints a command — run it
pm2 save
pm2 status

Step 5 — Put Nginx in front

Nginx acts as a reverse proxy: it accepts requests on port 80 and forwards them to the Node process on localhost. This gives you a clean place to handle TLS, compression, caching headers and multiple apps on one host.

nginx
server {
  listen 80;
  server_name example.com;

  location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}

Save the file in /etc/nginx/sites-available, enable it, test the configuration and reload.

bash
sudo ln -s /etc/nginx/sites-available/app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Step 6 — Add HTTPS with a free certificate

Once DNS points at the instance, Certbot can obtain and install a Let’s Encrypt certificate and configure automatic renewal in a single step.

bash
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com
sudo systemctl status certbot.timer

Good habits that save you later

  • Store secrets in an environment file that is not committed to Git.
  • Use pm2 logs api to read application output when something breaks.
  • Keep the security group minimal and review it periodically.
  • Build and push artifacts from CI rather than building on the server by hand.

Wrapping up

This setup is intentionally boring: a managed process, a reverse proxy and automatic TLS. That combination is easy to reason about, quick to reproduce on a new instance, and a solid base to layer monitoring and automated deployments on top of later.

#AWS#EC2#Nginx#Node.js#PM2#Deployment