Chapter 11: Application Hosting — WordPress, Flask and Express
11.3 Express application with pm2 + Nginx
It's really simple – once you have done Flask, Express follows exactly the same idea: app on localhost, a process manager to keep it alive, Nginx in front. Here pm2 takes the place of Gunicorn.
एकदम सिंपल आहे – Flask केलं की Express अगदी त्याच आयडियाने चालतं: ॲप localhost वर, तो जिवंत ठेवायला प्रोसेस मॅनेजर, आणि समोर Nginx. इथे Gunicorn च्या जागी pm2 येतो.
एकदम सिंपल है – Flask कर लिया तो Express बिल्कुल उसी आइडिया पर चलता है: ऐप localhost पर, उसे ज़िंदा रखने के लिए प्रोसेस मैनेजर, और आगे Nginx. यहाँ Gunicorn की जगह pm2 आता है.
Why pm2?
Friends, when you close SSH, node app.js stops – every student has this experience. pm2 keeps the app running and starts it again after a crash.
मित्रांनो, SSH बंद केला की node app.js बंद होतो – हा अनुभव प्रत्येक स्टुडंटला येतो. pm2 ॲपला चालू ठेवतो आणि क्रॅशनंतर परत स्टार्ट करतो.
दोस्तों, SSH बंद किया तो node app.js बंद हो जाता है – ये अनुभव हर स्टूडेंट को होता है. pm2 ऐप को चालू रखता है और क्रैश के बाद फिर से स्टार्ट करता है.
If you start Node with node app.js and close the SSH session, the app dies. If it crashes, nobody restarts it. pm2 is a production process manager for Node.js that keeps apps alive, restarts on crash, starts them at boot, manages logs, and can run one process per CPU core (cluster mode) with zero-downtime reloads.
Step 1 — Install Node.js, pm2 and Nginx
# AL2023 / CentOS Stream 9
curl -fsSL https://rpm.nodesource.com/setup_22.x | sudo bash -
sudo yum install -y nodejs nginx git
# Ubuntu
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs nginx git
sudo npm install -g pm2
Step 2 — Create the Express app
sudo mkdir -p /opt/expressdemo/public && sudo chown -R $USER:$USER /opt/expressdemo
cd /opt/expressdemo
npm init -y
npm install express
cat > server.js <<'EOF'
const express = require("express");
const os = require("os");
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.json());
app.use(express.static("public"));
const todos = [{ id: 1, task: "Learn EC2" }, { id: 2, task: "Deploy with pm2" }];
app.get("/api/todos", (req, res) => res.json(todos));
app.post("/api/todos", (req, res) => {
const todo = { id: todos.length + 1, task: req.body.task };
todos.push(todo);
res.status(201).json(todo);
});
app.get("/api/health", (req, res) => res.json({ status: "ok", host: os.hostname(), pid: process.pid }));
app.listen(PORT, "127.0.0.1", () => console.log(`Express running on 127.0.0.1:${PORT}`));
EOF
cat > public/index.html <<'EOF'
<!DOCTYPE html><html><head><meta charset="utf-8"><title>Express on EC2</title></head>
<body style="font-family:Arial;max-width:600px;margin:40px auto">
<h1>Express + pm2 + Nginx</h1><ul id="list"></ul>
<script>
fetch("/api/todos").then(r => r.json()).then(items => {
document.getElementById("list").innerHTML = items.map(t => "<li>" + t.task + "</li>").join("");
});
</script></body></html>
EOF
Step 3 — pm2 ecosystem file (cluster mode)
cat > ecosystem.config.js <<'EOF'
module.exports = {
apps: [{
name: "expressdemo",
script: "server.js",
cwd: "/opt/expressdemo",
instances: "max", // one process per vCPU
exec_mode: "cluster",
env: { NODE_ENV: "production", PORT: 3000 },
max_memory_restart: "300M"
}]
};
EOF
pm2 start ecosystem.config.js
pm2 status
curl -s http://127.0.0.1:3000/api/health
pm2 startup systemd # copy and run the 'sudo env PATH=...' line it prints
pm2 save
Useful pm2 commands
| Command | Purpose |
|---|---|
pm2 list / pm2 status |
Show processes |
pm2 logs expressdemo |
Stream logs (--lines 100) |
pm2 monit |
Live CPU/memory dashboard |
pm2 restart expressdemo |
Restart (brief downtime) |
pm2 reload expressdemo |
Zero-downtime reload (cluster mode) |
pm2 stop / pm2 delete expressdemo |
Stop / remove from list |
pm2 save |
Save process list for resurrection at boot |
pm2 install pm2-logrotate |
Rotate logs so the disk doesn't fill up |
Step 4 — Nginx reverse proxy
# AL2023 / CentOS path; Ubuntu: /etc/nginx/sites-available/expressdemo + symlink + remove default
sudo tee /etc/nginx/conf.d/expressdemo.conf > /dev/null <<'EOF'
upstream express_backend {
server 127.0.0.1:3000;
keepalive 16;
}
server {
listen 80;
server_name YOUR_SERVER_NAME;
location / {
proxy_pass http://express_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
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;
}
}
EOF
sudo sed -i "s/YOUR_SERVER_NAME/$(curl -s https://checkip.amazonaws.com)/" /etc/nginx/conf.d/expressdemo.conf
sudo setsebool -P httpd_can_network_connect 1 2>/dev/null # CentOS only
sudo service nginx start
sudo systemctl enable nginx
sudo nginx -t && sudo service nginx reload
Browse to http://<PUBLIC_IP>/ and http://<PUBLIC_IP>/api/health (refresh a few times — the pid changes because requests are shared across cluster workers).
Step 5 — Deploying updates
cd /opt/expressdemo
git pull
npm ci --omit=dev # clean install exactly from package-lock.json
pm2 reload expressdemo # zero downtime
Ravindra Bagale's Tip
Many students run pm2 startup but not the sudo env PATH=... command it prints, and after a reboot the app is gone. After that command, be sure to run pm2 save as well. For updates use pm2 reload expressdemo instead of pm2 restart – no downtime.
Ravindra Bagale's Tip – मराठी
खूप students pm2 startup चालवतात पण त्याने print केलेली sudo env PATH=... command चालवत नाहीत, आणि reboot नंतर app गायब. त्या command नंतर pm2 save पण नक्की करा. Updates साठी pm2 restart ऐवजी pm2 reload expressdemo वापरा – downtime नाही.
Ravindra Bagale's Tip – हिंदी
बहुत students pm2 startup चलाते हैं पर उसकी print की हुई sudo env PATH=... command नहीं चलाते, और reboot के बाद app गायब. उस command के बाद pm2 save भी ज़रूर करो. Updates के लिए pm2 restart की जगह pm2 reload expressdemo इस्तेमाल करो – downtime नहीं.
Lab
Chala, Express demo pm2 cluster mode madhe chalu kara aani http://<PUBLIC_IP>/api/health kahi vela refresh karun pid badalto ka te liha. Mag sudo reboot kara aani parat login karun pm2 status ne app aapoaap parat aala ka te confirm kara.