Deploy Multi-Site Next.js on VPS with Nginx, PM2 & SSL
Run multiple Next.js 16 apps on one VPS with Nginx reverse proxy, PM2 process management, Let's Encrypt SSL, and zero-downtime symlink deploys. Production directory layout, server blocks, ecosystem config, and checklist.
By Mussawar Hayat
One VPS, Many Production Next.js Apps
A single VPS can host several Next.js 16 sites with isolated Node processes, Nginx as the TLS terminator and reverse proxy, and PM2 for process management and zero-downtime reloads. This is the exact production layout I use for multi-site hosting: directory structure, unique localhost ports, Nginx server blocks with HTTP/2, PM2 ecosystem files, Certbot SSL with auto-renewal, and a symlink-based deploy pattern that supports instant rollback.
What You Will Learn
- Production directory and port allocation per app
- Nginx reverse proxy, HTTP/2, and required proxy headers
- PM2 ecosystem config for multiple Next.js apps
- Certbot SSL and renew hooks that reload Nginx
- Zero-downtime deploy with release directories and symlink flips
- Firewall, logging, and monitoring checklist
1. Directory Layout and Ports
Keep each site in its own tree under /var/www. Use a releases folder plus a current symlink so deploys are atomic and rollbacks are one command.
/var/www/
site-a/
releases/
2026-08-01-1200/
2026-08-02-0900/
current -> releases/2026-08-02-0900
site-b/
releases/
current -> ...
/etc/nginx/sites-available/
site-a.conf
site-b.conf
/etc/nginx/sites-enabled/ # symlinks to sites-available
Each Next.js app listens on a unique localhost port (3001, 3002, …). Never bind Node to a public interface. Nginx alone terminates TLS and proxies to 127.0.0.1:<port>.
2. Nginx Server Block for Next.js
Use a dedicated server block per hostname. Prefer HTTP/2, modern TLS, and the proxy headers Next.js and logging need.
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Optional: security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
location / {
proxy_pass http://127.0.0.1:3001;
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-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
Enable the site with a symlink into sites-enabled, test with nginx -t, then reload. For multi-domain email deliverability on the same VPS, see also SPF, DKIM & DMARC for Multi-Domain VPS.
3. PM2 Ecosystem for Multiple Apps
Define every app in one ecosystem file so process management is consistent.
// ecosystem.config.cjs
module.exports = {
apps: [
{
name: 'site-a',
cwd: '/var/www/site-a/current',
script: 'node_modules/next/dist/bin/next',
args: 'start -p 3001',
instances: 1,
exec_mode: 'fork',
env: { NODE_ENV: 'production', PORT: 3001 },
},
{
name: 'site-b',
cwd: '/var/www/site-b/current',
script: 'node_modules/next/dist/bin/next',
args: 'start -p 3002',
instances: 1,
exec_mode: 'fork',
env: { NODE_ENV: 'production', PORT: 3002 },
},
],
}
Start or reload with pm2 start ecosystem.config.cjs and pm2 reload site-a. Use output: 'standalone' in Next.js for smaller runtime footprints when you later move to Docker (see Docker Multi-Stage Builds for Next.js 16).
4. Zero-Downtime Deploy Pattern
- Build on CI or on the server into a new timestamped release directory.
- Install production dependencies and run
next build(or copy a pre-built standalone output). - Flip the
currentsymlink to the new release. pm2 reload <app-name>so the process picks up the new code with minimal downtime.- Keep the previous release for instant rollback: point
currentback and reload again.
This pattern avoids in-place overwrites and makes failed deploys recoverable in seconds.
5. SSL with Certbot and Auto-Renewal
Issue certificates once per primary domain (and www if needed), then rely on Certbot’s timer.
sudo certbot --nginx -d example.com -d www.example.com
Add a deploy hook so Nginx reloads after renewal:
# /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
#!/bin/sh
systemctl reload nginx
Test renewal with certbot renew --dry-run. Keep only ports 80/443 (and SSH) open on the firewall.
6. Monitoring and Hardening
- Firewall: allow 80, 443, and SSH only; deny public access to Node ports
- Log rotation for Nginx access/error logs and PM2 logs
- Disk and memory alerts (simple cron + df/free or a lightweight agent)
- Health checks against each public hostname
- Fail2ban or equivalent on SSH
- Keep system packages and Node LTS updated on a schedule
7. FAQ: Multi-Site Next.js on VPS
How many Next.js apps can one VPS run?
It depends on traffic and memory. Each Node process needs RAM for the app and peak concurrency. Start with one or two apps on a 2–4 GB VPS, monitor RSS and CPU, then scale vertically or split hosts.
Should I use PM2 cluster mode with Next.js?
For most sites, a single fork instance per app behind Nginx is simpler and sufficient. Next.js already handles concurrent requests inside one process. Use multiple instances only when you have measured CPU-bound load and understand sticky sessions / shared cache implications.
Is Nginx required or can I expose Next.js directly?
Do not expose Node publicly. Nginx (or another reverse proxy) should terminate TLS, handle HTTP→HTTPS redirects, and forward only to localhost ports.
How does this relate to Prisma and connection pooling?
On a long-lived VPS process you can use a higher Prisma connection_limit than pure serverless. Still prefer a pooler (PgBouncer or Prisma Accelerate) under concurrent load. See the Prisma Connection Exhaustion guide.
When should I move from PM2 to Docker?
When you need reproducible images, stricter isolation, or the same deploy path across multiple servers. Multi-stage standalone builds are the next step after a stable PM2 + Nginx setup.
8. Production Checklist
- Unique localhost ports per app; never expose Node ports publicly
- TLS via Certbot with a renew hook that reloads Nginx
- Firewall: only 80/443 and SSH open
- PM2 ecosystem file for all apps; reload (not restart) for deploys
- Symlink releases for atomic deploys and fast rollback
- Log rotation for Nginx and PM2
- Health checks and disk/memory alerts
- Proxy headers: Host, X-Forwarded-For, X-Forwarded-Proto, Upgrade/Connection for websockets
Summary
Nginx + PM2 + symlink releases is a durable, low-cost way to run multiple Next.js production sites on one VPS. Isolate processes by port, terminate TLS at Nginx, deploy with atomic symlink flips, and monitor resources. This foundation scales until you need Docker or a larger fleet.
Key Takeaway
Isolate processes by port, terminate TLS at Nginx, deploy with symlink flips plus PM2 reload, and keep Node off the public internet.
Need a production VPS multi-site setup?
I configure multi-site Next.js hosting, SSL, PM2, and deploy pipelines on VPS. Get in touch, or explore DevOps and full-stack services.
Related guides
Ship a minimal, secure Next.js 16 App Router image with output: "standalone", multi-stage builds, non-root user, and only the files the runtime needs. Production Dockerfile, .dockerignore, and checklist.
SPF, DKIM & DMARC for Multi-Domain VPSEmail deliverability is silent until it breaks. Here is the exact DNS setup I use to keep transactional email out of spam across multiple domains.
Grok Bot Explained: Persistent Cloud Agents, Shared Computers, and Production Guardrails (2026)Grok Bot gives AI teammates a persistent cloud computer with a browser, filesystem, and terminal. Here is what it is, how it differs from Cursor Cloud Agents and coding agents, and the production rules that keep always-on bots from becoming a liability.
