{"id":4467,"date":"2026-09-12T17:32:58","date_gmt":"2026-09-12T12:02:58","guid":{"rendered":"https:\/\/cpanelfree.com\/blog\/how-to-deploy-nextjs-ssr-linux-vps-pm2\/"},"modified":"2026-09-17T11:23:52","modified_gmt":"2026-09-17T05:53:52","slug":"how-to-deploy-nextjs-ssr-linux-vps-pm2","status":"publish","type":"post","link":"https:\/\/cpanelfree.com\/blog\/how-to-deploy-nextjs-ssr-linux-vps-pm2\/","title":{"rendered":"How to Deploy Next.js SSR Web Applications on Linux VPS with PM2 &amp; Standalone Output"},"content":{"rendered":"<p>Next.js is the dominant React framework for building modern, search-engine-optimized web applications. However, hosting Next.js applications on commercial serverless platforms like Vercel often results in exorbitant monthly invoices as soon as traffic scales. High bandwidth egress markups, arbitrary serverless execution function timeouts, and costly concurrency seat limits penalize growing applications.<\/p>\n<p>By leveraging Next.js\u2019s built-in <strong>standalone build output<\/strong>, you can package an entire Next.js SSR application into an ultra-lean Node.js bundle that runs on an affordable <a href=\"https:\/\/cpanelfree.com\/\">Linux VPS<\/a>. Paired with <strong>PM2 process manager<\/strong> and an Nginx reverse proxy, self-hosting Next.js delivers continuous sub-20ms SSR response times, eliminates cold-start latency, and reduces infrastructure costs by up to <strong>90%<\/strong>.<\/p>\n<h2>1. Enabling Standalone Build Output in next.config.js<\/h2>\n<p>By default, a Next.js build requires your entire <code>node_modules<\/code> folder\u2014often weighing several gigabytes. The standalone feature traces your project&#8217;s import tree and compiles only the strict dependencies required for production into a lightweight <code>.next\/standalone<\/code> folder.<\/p>\n<p>Open <code>next.config.js<\/code> (or <code>next.config.mjs<\/code>) and enable standalone mode:<\/p>\n<pre><code>\/** @type {import('next').NextConfig} *\/\nconst nextConfig = {\n  output: 'standalone',\n  poweredByHeader: false,\n  reactStrictMode: true,\n};\n\nmodule.exports = nextConfig;<\/code><\/pre>\n<p>Execute the production build on your local machine or CI\/CD runner:<\/p>\n<pre><code>npm run build<\/code><\/pre>\n<p>Next.js creates an optimized self-contained web server at <code>.next\/standalone\/server.js<\/code> weighing less than 80MB.<\/p>\n<h2>2. Preparing the Production Linux VPS &amp; Node.js Runtime<\/h2>\n<p>Log in to your VPS as a non-root administrative user and install Node.js 20 LTS alongside PM2:<\/p>\n<pre><code># Install NodeSource repository and Node.js\ncurl -fsSL https:\/\/deb.nodesource.com\/setup_20.x | sudo -E bash -\nsudo apt install -y nodejs nginx\n\n# Install PM2 process manager globally\nsudo npm install -g pm2<\/code><\/pre>\n<p>Create the target application deployment directory:<\/p>\n<pre><code>sudo mkdir -p \/var\/www\/my-nextjs-app\nsudo chown -R $USER:$USER \/var\/www\/my-nextjs-app<\/code><\/pre>\n<h2>3. Transferring Artifacts &amp; Managing Static Assets<\/h2>\n<p>To run a standalone Next.js build, you must deploy three specific folders to <code>\/var\/www\/my-nextjs-app<\/code>:<\/p>\n<ol>\n<li>The entire contents of <code>.next\/standalone\/<\/code><\/li>\n<li>The <code>public\/<\/code> folder (copied into <code>\/var\/www\/my-nextjs-app\/public\/<\/code>)<\/li>\n<li>The compiled static assets from <code>.next\/static\/<\/code> (copied into <code>\/var\/www\/my-nextjs-app\/.next\/static\/<\/code>)<\/li>\n<\/ol>\n<p>Sync these folders efficiently via <code>rsync<\/code>:<\/p>\n<pre><code># Run from your local repository\nrsync -avz --delete .next\/standalone\/ deployer@your-vps-ip:\/var\/www\/my-nextjs-app\/\nrsync -avz --delete public\/ deployer@your-vps-ip:\/var\/www\/my-nextjs-app\/public\/\nrsync -avz --delete .next\/static\/ deployer@your-vps-ip:\/var\/www\/my-nextjs-app\/.next\/static\/<\/code><\/pre>\n<h2>4. Configuring PM2 Process Manager Ecosystem<\/h2>\n<p>Create an <code>ecosystem.config.js<\/code> file in <code>\/var\/www\/my-nextjs-app\/<\/code> to manage multi-core clustering and automatic restarts:<\/p>\n<pre><code>module.exports = {\n  apps: [\n    {\n      name: 'nextjs-production',\n      script: 'server.js',\n      cwd: '\/var\/www\/my-nextjs-app',\n      instances: 'max', \/\/ Scales across all available CPU cores in cluster mode\n      exec_mode: 'cluster',\n      env: {\n        NODE_ENV: 'production',\n        PORT: 3000,\n        HOSTNAME: '127.0.0.1',\n      },\n      max_memory_restart: '512M',\n      listen_timeout: 10000,\n      kill_timeout: 5000,\n    },\n  ],\n};<\/code><\/pre>\n<p>Start the application cluster and save the systemd boot startup hook:<\/p>\n<pre><code>cd \/var\/www\/my-nextjs-app\npm2 start ecosystem.config.js\npm2 save\nsudo env PATH=$PATH:\/usr\/bin pm2 startup systemd -u $USER --hp \/home\/$USER<\/code><\/pre>\n<h2>5. Nginx Reverse Proxy &amp; Static Asset Caching<\/h2>\n<p>Configure Nginx to terminate SSL and serve immutable static files directly from disk without invoking Node.js worker cycles:<\/p>\n<pre><code>server {\n    listen 80;\n    listen 443 ssl http2;\n    server_name yourdomain.com;\n\n    ssl_certificate \/etc\/letsencrypt\/live\/yourdomain.com\/fullchain.pem;\n    ssl_certificate_key \/etc\/letsencrypt\/live\/yourdomain.com\/privkey.pem;\n\n    # Serve Next.js static chunks directly from disk with 1-year immutable caching\n    location \/_next\/static\/ {\n        alias \/var\/www\/my-nextjs-app\/.next\/static\/;\n        expires 365d;\n        access_log off;\n        add_header Cache-Control \"public, max-age=31536000, immutable\";\n    }\n\n    # Serve public static assets directly\n    location \/public\/ {\n        alias \/var\/www\/my-nextjs-app\/public\/;\n        expires 30d;\n        access_log off;\n    }\n\n    # Proxy dynamic SSR requests to PM2 Node.js cluster\n    location \/ {\n        proxy_pass http:\/\/127.0.0.1:3000;\n        proxy_http_version 1.1;\n        proxy_set_header Upgrade $http_upgrade;\n        proxy_set_header Connection 'upgrade';\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_cache_bypass $http_upgrade;\n    }\n}<\/code><\/pre>\n<h2>5. Zero-Downtime Rolling Deployments with PM2 Reload &amp; Git Hooks<\/h2>\n<p>Deploying application updates to production without interrupting active user sessions is critical for modern Next.js deployments. Instead of executing <code>pm2 restart<\/code>, which terminates the existing process before starting the new one, configure graceful zero-downtime reloads:<\/p>\n<ul>\n<li><strong>Graceful Cluster Reloading:<\/strong> PM2 supports <code>pm2 reload nextjs-app<\/code>. When invoked, PM2 starts the new worker process, waits for it to listen on the local port, and only then issues a SIGINT signal to the old process. This guarantees that pending HTTP requests finish executing cleanly.<\/li>\n<li><strong>Automated Post-Receive Git Hook:<\/strong> Configure an automated deployment pipeline directly on your Linux VPS using Git hooks:\n<pre><code>#!\/bin\/bash\n# \/var\/repo\/nextjs.git\/hooks\/post-receive\nTARGET=\"\/var\/www\/nextjs-app\"\nGIT_DIR=\"\/var\/repo\/nextjs.git\"\nBRANCH=\"main\"\n\nwhile read oldrev newrev ref\ndo\n    if [[ $ref =~ .*\/$BRANCH$ ]]; then\n        echo \"Master ref received. Deploying Next.js to production...\"\n        git --work-tree=$TARGET --git-dir=$GIT_DIR checkout -f\n        cd $TARGET\n        npm ci\n        npm run build\n        cp -r public .next\/standalone\/\n        cp -r .next\/static .next\/standalone\/.next\/\n        pm2 reload nextjs-app\n        echo \"Deployment complete with zero downtime!\"\n    fi\ndone<\/code><\/pre>\n<\/li>\n<li><strong>Handling Node.js Memory Leaks:<\/strong> By setting <code>max_memory_restart: '1G'<\/code> in your PM2 ecosystem configuration, PM2 automatically recycles worker processes that exceed the threshold without downtime.<\/li>\n<\/ul>\n<h2>6. Edge ISR (Incremental Static Regeneration) Caching with Nginx Microcaching<\/h2>\n<p>Combine Next.js built-in ISR with Nginx proxy microcaching for extreme scale. By caching static HTML fragments in shared Nginx memory zones (<code>proxy_cache_path \/tmp\/nginx_cache levels=1:2 keys_zone=NEXT_CACHE:100m inactive=60m<\/code>), your VPS can effortlessly absorb viral traffic spikes of tens of thousands of requests per second without touching Node.js runtime threads.<\/p>\n<div style=\"background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);border: 1px solid #334155;border-radius: 12px;padding: 28px;margin: 36px 0;text-align: center\">\n<h3 style=\"color: #38bdf8;margin-top: 0;font-size: 22px\">Deploy Next.js on CpanelFree High-Speed VPS<\/h3>\n<p style=\"color: #cbd5e1;font-size: 16px;line-height: 1.6;max-width: 680px;margin: 12px auto 24px auto\">Say goodbye to unpredictable serverless billing. Run your Next.js applications on dedicated CPU cores, unmetered bandwidth, and lightning-fast NVMe storage on CpanelFree.<\/p>\n<p>    <a href=\"https:\/\/cpanelfree.com\/\" style=\"background: #38bdf8;color: #0f172a;font-weight: 700;padding: 12px 28px;border-radius: 6px;text-decoration: none;display: inline-block;font-size: 15px\">Discover CpanelFree Developer VPS &rarr;<\/a>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Next.js is the dominant React framework for building modern, search-engine-optimized web applications. However, hosting Next.js applications on commercial serverless platforms like Vercel often results in exorbitant monthly invoices as soon as traffic scales. High bandwidth egress markups, arbitrary serverless execution function timeouts, and costly concurrency seat limits penalize growing applications. By leveraging Next.js\u2019s built-in standalone &#8230; <a title=\"How to Deploy Next.js SSR Web Applications on Linux VPS with PM2 &amp; Standalone Output\" class=\"read-more\" href=\"https:\/\/cpanelfree.com\/blog\/how-to-deploy-nextjs-ssr-linux-vps-pm2\/\" aria-label=\"Read more about How to Deploy Next.js SSR Web Applications on Linux VPS with PM2 &amp; Standalone Output\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":4535,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-4467","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-web-hosting-news"],"_links":{"self":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts\/4467","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/comments?post=4467"}],"version-history":[{"count":1,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts\/4467\/revisions"}],"predecessor-version":[{"id":4485,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts\/4467\/revisions\/4485"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/media\/4535"}],"wp:attachment":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/media?parent=4467"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/categories?post=4467"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/tags?post=4467"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}