KVM VPS from Rs 149/mo. Mumbai, Noida and Jaipur nodes.

24×7 infrastructure operations Sales +91 98297 14343
vpswala.in
VPSWala 4 min read

Deploying Next.js SSR on a Linux VPS: Standalone Build, PM2 & Nginx Proxy

Learn how to deploy a server-rendered Next.js application on a Linux KVM VPS using standalone build artifacts, PM2 cluster management, and Nginx.

Server-side rendering (SSR) in modern React frameworks like Next.js delivers significant advantages for search engine optimization, dynamic content delivery, and initial page load speed. While many developers prototype on managed serverless platforms, deploying Next.js on a dedicated Linux KVM VPS provides full architectural control, predictable billing, zero execution timeouts, and direct access to local memory and disk caches.

Why Deploy Next.js SSR on a Virtual Private Server?

Serverless deployment platforms impose rigid operational limitations: strict execution duration caps, cold-start latency spikes, expensive egress bandwidth, and memory throttling during compute-heavy page rendering. In contrast, running Next.js on a high-speed Linux KVM VPS allows your Node.js runtime to maintain persistent database connection pools, local in-memory caches, and background cron routines without recurring per-request fees.

To operate a production-ready Next.js deployment, engineering teams utilize three complementary architectural layers:

  1. Standalone Output Build: Generates a minimal, self-contained production bundle that excludes unneeded development dependencies.
  2. PM2 Process Manager: Keeps the Node.js application process running continuously, orchestrates automatic restarts on unexpected errors, and enables zero-downtime reloads.
  3. Nginx Reverse Proxy: Terminates SSL certificates, serves static assets directly from disk, and forwards dynamic requests to the local Node.js application port.

Step 1: Configuring Next.js Standalone Output

By default, deploying a Next.js application requires shipping the entire node_modules directory, which frequently exceeds several gigabytes. Next.js includes an optimized build mode called standalone that automatically traces required dependencies and copies only the essential files into a compact bundle.

Enable standalone mode in your next.config.js (or next.config.mjs):

/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone',
  reactStrictMode: true,
  poweredByHeader: false,
};

module.exports = nextConfig;

Build the production application on your local machine or CI/CD runner:

npm run build

The build process creates a standalone server file at .next/standalone/server.js. Note that Next.js does not automatically copy the public/ folder or static client assets into the standalone directory. Your deployment script must copy these directories into the target folder:

# Deploy assets to the standalone directory
cp -r public .next/standalone/
cp -r .next/static .next/standalone/.next/

Step 2: Process Management with PM2

On your Linux KVM VPS, install Node.js (v20 LTS recommended) and the PM2 process manager globally:

# Install PM2 globally
sudo npm install -g pm2

# Navigate to your application's standalone directory
cd /var/www/my-next-app/.next/standalone

# Launch the Next.js standalone server with PM2
PORT=3000 pm2 start server.js --name "next-ssr-app" -i max

Using -i max enables PM2 cluster mode, spawning a separate worker process for each available CPU core. Because incoming HTTP requests are distributed across workers, your application can render multiple concurrent SSR pages without thread blocking.

To ensure PM2 automatically restarts your application if the VPS reboots, configure the systemd startup hook:

# Generate and configure systemd startup script
pm2 startup systemd
pm2 save

Step 3: Configuring Nginx Reverse Proxy and Static Caching

Place Nginx in front of your PM2 process to handle TLS encryption and offload static asset requests directly:

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name yourdomain.com www.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    # Serve static assets directly from disk with long cache headers
    location /_next/static/ {
        alias /var/www/my-next-app/.next/standalone/.next/static/;
        expires 365d;
        access_log off;
    }

    location /public/ {
        alias /var/www/my-next-app/.next/standalone/public/;
        expires 30d;
        access_log off;
    }

    # Proxy dynamic SSR requests to PM2
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        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;

        proxy_connect_timeout 60s;
        proxy_read_timeout 60s;
    }
}

Managing Environment Variables and Secrets

In a standalone Next.js deployment, environment variables are evaluated at two distinct phases: build time and runtime. Variables prefixed with NEXT_PUBLIC_ are inlined directly into the client-side JavaScript bundle during the build step. In contrast, sensitive server-side credentials—such as database connection strings, payment gateway API keys, and session secrets—must be loaded by the Node.js runtime process.

Create an ecosystem.config.js file to manage runtime environment variables across PM2 cluster instances cleanly:

module.exports = {
  apps: [
    {
      name: 'next-ssr-app',
      script: 'server.js',
      instances: 'max',
      exec_mode: 'cluster',
      env_production: {
        NODE_ENV: 'production',
        PORT: 3000,
        DATABASE_URL: 'postgresql://dbuser:secret@localhost:5432/appdb',
      },
    },
  ],
};

Launch the cluster in production mode using pm2 start ecosystem.config.js --env production. This approach isolates secret keys from version control while ensuring all worker threads share identical runtime environment states.

Automating Zero-Downtime Deployments

To deploy application updates without dropping active user requests, create an automated deployment shell script on your VPS:

#!/bin/bash
set -e

cd /var/www/my-next-app
git pull origin main
npm ci
npm run build

# Copy standalone static dependencies
cp -r public .next/standalone/
cp -r .next/static .next/standalone/.next/

# Zero-downtime rolling reload across PM2 cluster workers
pm2 reload ecosystem.config.js --env production

The pm2 reload command performs a rolling restart across cluster workers sequentially: it launches updated worker processes and verifies they are accepting connections before terminating older workers, ensuring continuous service uptime for active visitors.

Hardware Sizing for Next.js SSR Workloads

Server-side rendering involves running React component lifecycles in Node.js on every incoming request, which consumes substantial CPU cycles and memory. For small to mid-sized web platforms, a VPS with 2 to 4 vCPUs and 4 GB to 8 GB of RAM provides ample headroom for PM2 workers and caching. For high-traffic enterprise applications, VPSWala's AMD Ryzen 9 9950X VPS instances (featuring Zen 5 architecture and DDR5 RAM) deliver the fast single-thread performance needed to minimize server render latency.

Explore VPSWala Cloud VPS plans for flexible Linux hosting, or configure high-frequency compute on VPSWala 9950X VPS.


Sources

Not sure which size?

Send the stack, get a size.

Tell us the operating system, application stack, current traffic, database size and where it hurts today. You get a sizing recommendation, the matching plan and a price.

Related

More on this.

Get sizing help See VPS plans