← The blog
DevOps·May 2026·7 min read

Deploying a Next.js monorepo on a VPS with PM2

Three Next.js apps on one box, each a named PM2 process, with graceful reloads that drop zero requests.

Three Next.js apps, one 4 GB VPS, and a deploy that takes about forty seconds. No Kubernetes, no managed platform bill, just PM2 and a reverse proxy. For a monorepo this size that tradeoff is the right one, and here is how it fits together.

One ecosystem file

PM2 reads a single ecosystem file checked into the repo. Each app is a named process bound to its own port, and the file is the source of truth for how the box runs.

module.exports = {
  apps: [
    { name: "web", cwd: "apps/web", script: "npm", args: "start", env: { PORT: 3000 } },
    { name: "admin", cwd: "apps/admin", script: "npm", args: "start", env: { PORT: 3001 } },
    { name: "api", cwd: "apps/api", script: "npm", args: "start", env: { PORT: 3002 } },
  ],
};

Because the file is versioned, a fresh box reaches the same state with pm2 start ecosystem.config.js. Nothing lives only in someone's shell history.

The reverse proxy

Nginx terminates TLS and routes by hostname to the right port. The apps never speak HTTPS themselves, which keeps their config simple and means certificate renewal is one concern in one place.

server {
  server_name app.fuyad.dev;
  location / { proxy_pass http://127.0.0.1:3000; }
}

Zero-downtime reloads

The part people get wrong is restarting. pm2 restart kills the process and drops in-flight requests. pm2 reload starts a new instance, waits for it to come up, then retires the old one. With Next.js running in cluster mode the swap is invisible to anyone mid-request.

The deploy script pulls, installs, builds each app, then reloads:

git pull --ff-only
npm ci
npm run build --workspaces
pm2 reload ecosystem.config.js

Treating the box like cattle

The VPS is a single server, but I still treat it as disposable. The Nginx config, the ecosystem file, and the deploy script all live in the repo. pm2 save plus the startup hook means the apps come back after a reboot. If the provider lost the machine tomorrow, a new one would be serving traffic inside fifteen minutes from version control alone.

This setup will not autoscale, and that is fine. It serves real traffic, it deploys without dropped requests, and it costs less than a coffee subscription. When one of these apps genuinely outgrows the box, it gets its own. Until then, the simple thing keeps winning.

Next.jsPM2NginxDeployment
Let's talk

Let's build
SOMETHING REAL.

Have a system that needs to think, scale, or just finally get shipped? Let's make it happen.

Based in Dhaka, Bangladesh · Available worldwide