<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Minhaj Haider Shah</title>
    <description>The latest articles on DEV Community by Minhaj Haider Shah (@dopescripts).</description>
    <link>https://dev.to/dopescripts</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4100034%2Ff796a7d1-ead0-44c0-8515-2ff2083a4875.jpg</url>
      <title>DEV Community: Minhaj Haider Shah</title>
      <link>https://dev.to/dopescripts</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dopescripts"/>
    <language>en</language>
    <item>
      <title>Deploy Next.js on a VPS: Git, Nginx, PM2 &amp; Production Setup (2026 Guide)</title>
      <dc:creator>Minhaj Haider Shah</dc:creator>
      <pubDate>Mon, 07 Sep 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/dopescripts/deploy-nextjs-on-a-vps-git-nginx-pm2-production-setup-2026-guide-6aa</link>
      <guid>https://dev.to/dopescripts/deploy-nextjs-on-a-vps-git-nginx-pm2-production-setup-2026-guide-6aa</guid>
      <description>&lt;p&gt;Deploying a Next.js application to a VPS gives you significantly more control over your production environment than relying entirely on managed hosting platforms.&lt;/p&gt;

&lt;p&gt;You control the server, Node.js runtime, process manager, reverse proxy, domains, SSL, environment variables, and deployment workflow.&lt;/p&gt;

&lt;p&gt;But that control also means there are more pieces to configure.&lt;/p&gt;

&lt;p&gt;This guide walks through a practical production deployment of a Next.js application on a VPS, starting with cloning the project from Git and ending with a running application behind Nginx with PM2 managing the Node.js process.&lt;/p&gt;

&lt;p&gt;The exact commands may vary depending on your VPS provider and operating system, but the overall workflow applies to most Ubuntu-based VPS environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What We Are Building&lt;/li&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;1. Connect to Your VPS&lt;/li&gt;
&lt;li&gt;2. Update the Server&lt;/li&gt;
&lt;li&gt;3. Install Node.js&lt;/li&gt;
&lt;li&gt;4. Clone the Next.js Application&lt;/li&gt;
&lt;li&gt;5. Install Dependencies&lt;/li&gt;
&lt;li&gt;6. Configure Environment Variables&lt;/li&gt;
&lt;li&gt;7. Build the Next.js Application&lt;/li&gt;
&lt;li&gt;8. Install PM2&lt;/li&gt;
&lt;li&gt;9. Create a PM2 Ecosystem File&lt;/li&gt;
&lt;li&gt;10. Start Next.js with PM2&lt;/li&gt;
&lt;li&gt;11. Make PM2 Start After Reboots&lt;/li&gt;
&lt;li&gt;12. Configure Nginx as a Reverse Proxy&lt;/li&gt;
&lt;li&gt;13. Create the Nginx Virtual Host&lt;/li&gt;
&lt;li&gt;14. Enable the Virtual Host&lt;/li&gt;
&lt;li&gt;15. Configure DNS&lt;/li&gt;
&lt;li&gt;16. Add HTTPS / SSL&lt;/li&gt;
&lt;li&gt;17. Updating the Application&lt;/li&gt;
&lt;li&gt;18. A Better PM2 Deployment Workflow&lt;/li&gt;
&lt;li&gt;19. Common Problems You May Encounter&lt;/li&gt;
&lt;li&gt;20. Production Checklist&lt;/li&gt;
&lt;li&gt;21. Final Architecture&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What We Are Building
&lt;/h2&gt;

&lt;p&gt;By the end of this guide, the deployment will look roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         Internet
                            │
                            ▼
                    ┌───────────────┐
                    │    Domain     │
                    │ example.com   │
                    └───────┬───────┘
                            │
                            ▼
                    ┌───────────────┐
                    │     Nginx     │
                    │ Reverse Proxy │
                    └───────┬───────┘
                            │
                            ▼
                    ┌───────────────┐
                    │     PM2       │
                    │ Process Mgmt. │
                    └───────┬───────┘
                            │
                            ▼
                    ┌───────────────┐
                    │    Next.js    │
                    │   Node.js     │
                    └───────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The basic deployment flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Git repository
      ↓
Clone project
      ↓
Install Node.js
      ↓
Install dependencies
      ↓
Configure environment variables
      ↓
Build Next.js application
      ↓
Configure PM2
      ↓
Configure Nginx
      ↓
Point domain to VPS
      ↓
Configure SSL
      ↓
Application is live
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before starting, you should have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A VPS running Ubuntu or another Linux distribution&lt;/li&gt;
&lt;li&gt;SSH access to the server&lt;/li&gt;
&lt;li&gt;A Git repository containing your Next.js application&lt;/li&gt;
&lt;li&gt;A domain name&lt;/li&gt;
&lt;li&gt;DNS access for the domain&lt;/li&gt;
&lt;li&gt;Node.js-compatible Next.js application&lt;/li&gt;
&lt;li&gt;Root or sudo access on the VPS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This guide assumes an Ubuntu-based VPS and a standard Next.js application using the production Node.js server.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The exact commands may need to be adjusted depending on your VPS provider, Ubuntu version, Node.js version, and Next.js version.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. Connect to Your VPS
&lt;/h2&gt;

&lt;p&gt;Connect to your server using SSH:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh user@your-server-ip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh root@203.0.113.10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once connected, verify the operating system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/os-release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's also useful to check the server's current resources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;free &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Next.js application doesn't necessarily require a powerful server, but you should make sure the VPS has enough RAM and disk space for your application and its build process.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Update the Server
&lt;/h2&gt;

&lt;p&gt;Before installing the application stack, update the system packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt upgrade &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're logged in as root, you can omit &lt;code&gt;sudo&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I also recommend installing a few basic utilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; git curl build-essential
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;build-essential&lt;/code&gt; can be useful when npm packages contain native dependencies that need to be compiled.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Install Node.js
&lt;/h2&gt;

&lt;p&gt;Next.js requires Node.js, so the first major application dependency is the Node.js runtime.&lt;/p&gt;

&lt;p&gt;There are several ways to install Node.js on a VPS. I generally prefer using &lt;strong&gt;NVM (Node Version Manager)&lt;/strong&gt; because it makes switching Node.js versions much easier.&lt;/p&gt;

&lt;p&gt;Install NVM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-o-&lt;/span&gt; https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reload your shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify that NVM is available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nvm &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then install the Node.js version required by your application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nvm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--lts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set it as the default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nvm &lt;span class="nb"&gt;alias &lt;/span&gt;default node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;-v&lt;/span&gt;
npm &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Use the Node.js version supported by your particular Next.js version. Don't blindly install the newest Node.js release if your application's dependencies require another version.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  4. Clone the Next.js Application
&lt;/h2&gt;

&lt;p&gt;Choose a directory where you want to keep the application.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /var/www
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clone your repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/USERNAME/REPOSITORY.git my-next-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then enter the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;my-next-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check that the project was cloned correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see files such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package.json
next.config.js
app/
public/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your exact structure will depend on how your Next.js project is organized.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Install Dependencies
&lt;/h2&gt;

&lt;p&gt;Install the project's dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the project uses a lockfile and you want reproducible production installs, use the package-manager command appropriate for your project.&lt;/p&gt;

&lt;p&gt;For npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm ci
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important distinction is that &lt;code&gt;npm install&lt;/code&gt; may update the lockfile, while &lt;code&gt;npm ci&lt;/code&gt; installs exactly what is specified in the lockfile.&lt;/p&gt;

&lt;p&gt;For a production deployment, I generally prefer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm ci
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;when a valid &lt;code&gt;package-lock.json&lt;/code&gt; is committed to the repository.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Configure Environment Variables
&lt;/h2&gt;

&lt;p&gt;Next.js applications commonly require environment variables for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database connections&lt;/li&gt;
&lt;li&gt;API URLs&lt;/li&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Third-party services&lt;/li&gt;
&lt;li&gt;Application secrets&lt;/li&gt;
&lt;li&gt;Public configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create your production environment file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano .env.production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NEXT_PUBLIC_API_URL=https://api.example.com
DATABASE_URL=your-database-connection
NEXTAUTH_URL=https://example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the variables required by your own application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't commit production secrets
&lt;/h3&gt;

&lt;p&gt;Your &lt;code&gt;.env.production&lt;/code&gt; file should generally not be committed to Git if it contains secrets.&lt;/p&gt;

&lt;p&gt;Make sure it is covered by &lt;code&gt;.gitignore&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.env
.env.local
.env.production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact environment-file strategy depends on your deployment architecture, but &lt;strong&gt;production secrets should never be pushed into a public Git repository&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Build the Next.js Application
&lt;/h2&gt;

&lt;p&gt;Once dependencies and environment variables are configured, create a production build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A successful build should generate the &lt;code&gt;.next&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;You can then test the production application directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on your application, Next.js will normally listen on port &lt;code&gt;3000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can verify that the process is listening:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ss &lt;span class="nt"&gt;-lntp&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or test locally from the VPS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://127.0.0.1:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you receive the application's HTML response, the Next.js server is running.&lt;/p&gt;

&lt;p&gt;Stop the temporary process with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ctrl + C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We don't want to keep it running manually.&lt;/p&gt;

&lt;p&gt;That's where PM2 comes in.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Install PM2
&lt;/h2&gt;

&lt;p&gt;PM2 is a process manager for Node.js applications.&lt;/p&gt;

&lt;p&gt;Instead of manually running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you can let PM2 manage the application.&lt;/p&gt;

&lt;p&gt;Install it globally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; pm2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pm2 &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PM2 can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep the application running&lt;/li&gt;
&lt;li&gt;Restart it after crashes&lt;/li&gt;
&lt;li&gt;Start it automatically after server reboots&lt;/li&gt;
&lt;li&gt;Manage application logs&lt;/li&gt;
&lt;li&gt;Run multiple processes when appropriate&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  9. Create a PM2 Ecosystem File
&lt;/h2&gt;

&lt;p&gt;Rather than putting all your PM2 configuration into a command, create an ecosystem file.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano ecosystem.config.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A basic configuration could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-next-app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/var/www/my-next-app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;instances&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;autorestart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;max_memory_restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;500M&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;NODE_ENV&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important options are:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;name&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The name PM2 will use for the application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-next-app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;script&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The command that PM2 executes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;args&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Arguments passed to npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This effectively runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;cwd&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The application directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/var/www/my-next-app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;env&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Environment variables passed to the process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;NODE_ENV&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adjust this configuration to match your own project.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Start Next.js with PM2
&lt;/h2&gt;

&lt;p&gt;Start the application using the ecosystem file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pm2 start ecosystem.config.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the running processes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pm2 list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see your Next.js application in the list.&lt;/p&gt;

&lt;p&gt;Check its logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pm2 logs my-next-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also inspect the process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pm2 show my-next-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now test the application again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://127.0.0.1:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything works, your Next.js server is now being managed by PM2.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. Make PM2 Start After Reboots
&lt;/h2&gt;

&lt;p&gt;A VPS can reboot because of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Operating system updates&lt;/li&gt;
&lt;li&gt;Provider maintenance&lt;/li&gt;
&lt;li&gt;Hardware issues&lt;/li&gt;
&lt;li&gt;Manual reboots&lt;/li&gt;
&lt;li&gt;Unexpected crashes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't want to manually start your application every time.&lt;/p&gt;

&lt;p&gt;Generate the startup configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pm2 startup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PM2 will print a command that you need to execute.&lt;/p&gt;

&lt;p&gt;Run that command exactly as PM2 provides it.&lt;/p&gt;

&lt;p&gt;Then save the currently running processes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pm2 save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now PM2 can restore your application after a server restart.&lt;/p&gt;

&lt;p&gt;You can test this with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After reconnecting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pm2 list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your application should be running again.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Configure Nginx as a Reverse Proxy
&lt;/h2&gt;

&lt;p&gt;At this point the application is running on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://127.0.0.1:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But users shouldn't need to visit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;example.com:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, we'll put Nginx in front of Next.js.&lt;/p&gt;

&lt;p&gt;The architecture becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
   │
   ▼
example.com:443
   │
   ▼
Nginx
   │
   ▼
127.0.0.1:3000
   │
   ▼
Next.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install Nginx if it isn't already installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;nginx &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check its status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  13. Create the Nginx Virtual Host
&lt;/h2&gt;

&lt;p&gt;Create a configuration file for your domain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/nginx/sites-available/example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A basic configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="s"&gt;[::]:80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;example.com&lt;/span&gt; &lt;span class="s"&gt;www.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;proxy_http_version&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Upgrade&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Connection&lt;/span&gt; &lt;span class="s"&gt;"upgrade"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt; &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;server_name&lt;/code&gt; should contain your actual domain.&lt;/p&gt;

&lt;p&gt;The important part is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Nginx to forward incoming requests to the Next.js server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optional but important
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Handling next static files
&lt;/h3&gt;

&lt;p&gt;Next.js builds hashed, immutable static assets into &lt;code&gt;.next/static/&lt;/code&gt;. These are requested directly by the browser under the &lt;code&gt;/_next/static/&lt;/code&gt; path, and there's no reason to route them through the Node.js process at all, Nginx can serve them straight from disk, which is faster and takes load off PM2.&lt;/p&gt;

&lt;p&gt;Add this location block &lt;strong&gt;above&lt;/strong&gt; the general &lt;code&gt;location /&lt;/code&gt; block in the same server block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/_next/static/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;alias&lt;/span&gt; &lt;span class="n"&gt;/var/www/my-next-app/.next/static/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;expires&lt;/span&gt; &lt;span class="s"&gt;365d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;alias&lt;/code&gt; (not &lt;code&gt;root&lt;/code&gt;) is important here — it maps &lt;code&gt;/_next/static/&lt;/code&gt; directly onto the &lt;code&gt;.next/static/&lt;/code&gt; folder, stripping the prefix.&lt;/li&gt;
&lt;li&gt;The path must match wherever you cloned the app in Section 4 — adjust &lt;code&gt;/var/www/my-next-app&lt;/code&gt; to your actual &lt;code&gt;cwd&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;expires 365d;&lt;/code&gt; is safe because Next.js fingerprints these filenames on every build, so a changed file gets a new URL rather than overwriting a cached one.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;access_log off;&lt;/code&gt; just cuts noise from your Nginx logs — static asset hits aren't usually worth logging.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your full server block now looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="s"&gt;[::]:80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;example.com&lt;/span&gt; &lt;span class="s"&gt;www.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/_next/static/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;alias&lt;/span&gt; &lt;span class="n"&gt;/var/www/my-next-app/.next/static/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;expires&lt;/span&gt; &lt;span class="s"&gt;365d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;proxy_http_version&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Upgrade&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Connection&lt;/span&gt; &lt;span class="s"&gt;"upgrade"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt; &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Note:&lt;/strong&gt; Order matters in Nginx &lt;code&gt;location&lt;/code&gt; blocks — a static prefix match like &lt;code&gt;/_next/static/&lt;/code&gt; should sit before the catch-all &lt;code&gt;location /&lt;/code&gt; so it gets evaluated first.
&lt;/h2&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  14. Enable the Virtual Host
&lt;/h2&gt;

&lt;p&gt;Create a symbolic link:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test the Nginx configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not reload Nginx until this succeeds.&lt;/p&gt;

&lt;p&gt;You should see something similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;syntax is ok
test is successful
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then reload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  15. Configure DNS
&lt;/h2&gt;

&lt;p&gt;Your domain needs to point to the VPS.&lt;/p&gt;

&lt;p&gt;Create an &lt;code&gt;A&lt;/code&gt; record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Type: A
Name: @
Value: YOUR_SERVER_IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;code&gt;www&lt;/code&gt;, you can use either another A record or a CNAME depending on your DNS setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Type: CNAME
Name: www
Value: example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DNS propagation can take some time depending on the provider and TTL.&lt;/p&gt;

&lt;p&gt;You can verify DNS resolution from your local machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nslookup example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the domain resolves to your VPS, visiting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;should reach Nginx and then your Next.js application.&lt;/p&gt;




&lt;h2&gt;
  
  
  16. Add HTTPS / SSL
&lt;/h2&gt;

&lt;p&gt;Never leave a production application running only over plain HTTP.&lt;/p&gt;

&lt;p&gt;For a typical Nginx setup, Let's Encrypt can provide a free TLS certificate.&lt;/p&gt;

&lt;p&gt;Install Certbot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;certbot python3-certbot-nginx &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then request a certificate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;certbot &lt;span class="nt"&gt;--nginx&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; example.com &lt;span class="nt"&gt;-d&lt;/span&gt; www.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Certbot can configure the Nginx HTTPS settings for you.&lt;/p&gt;

&lt;p&gt;Afterward, your request flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTPS
  ↓
Nginx
  ↓
HTTP localhost:3000
  ↓
Next.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test your application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also verify certificate renewal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;certbot renew &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  17. Updating the Application
&lt;/h2&gt;

&lt;p&gt;One of the biggest advantages of using Git is that updating the production application becomes straightforward.&lt;/p&gt;

&lt;p&gt;After pushing changes to your repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /var/www/my-next-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pull the latest code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git pull
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install any dependency changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm ci
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new production build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then restart the PM2 process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pm2 restart my-next-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The basic deployment cycle becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Developer
   ↓
git push
   ↓
Production VPS
   ↓
git pull
   ↓
npm ci
   ↓
npm run build
   ↓
pm2 restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a small project, this manual workflow can be perfectly reasonable.&lt;/p&gt;

&lt;p&gt;As the project grows, you can automate it using GitHub Actions or another CI/CD system.&lt;/p&gt;




&lt;h2&gt;
  
  
  18. A Better PM2 Deployment Workflow
&lt;/h2&gt;

&lt;p&gt;You can also define deployment commands directly inside the PM2 ecosystem configuration.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-next-app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/var/www/my-next-app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;NODE_ENV&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;

  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;production&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;deploy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_SERVER_IP&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;origin/main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;git@github.com:USERNAME/REPOSITORY.git&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/var/www/my-next-app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post-deploy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm ci &amp;amp;&amp;amp; npm run build &amp;amp;&amp;amp; pm2 reload ecosystem.config.js --env production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whether you should use this approach depends on your deployment architecture.&lt;/p&gt;

&lt;p&gt;For many small applications, &lt;strong&gt;GitHub Actions + SSH + PM2&lt;/strong&gt; can provide a cleaner CI/CD workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  19. Common Problems You May Encounter
&lt;/h2&gt;

&lt;p&gt;A Next.js VPS deployment can fail in several different places.&lt;/p&gt;

&lt;p&gt;Understanding where to look makes troubleshooting much easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application isn't running
&lt;/h2&gt;

&lt;p&gt;Check PM2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pm2 list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pm2 logs my-next-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also test Next.js directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://127.0.0.1:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this fails, the problem is probably with Next.js or Node.js rather than Nginx.&lt;/p&gt;




&lt;h3&gt;
  
  
  Nginx returns 502 Bad Gateway
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;502&lt;/code&gt; usually means Nginx cannot reach the upstream application.&lt;/p&gt;

&lt;p&gt;Check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://127.0.0.1:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that fails, check PM2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pm2 logs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the Next.js process is running correctly, inspect your Nginx configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And check the Nginx error log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/nginx/error.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Application works on port 3000 but not through the domain
&lt;/h3&gt;

&lt;p&gt;This usually means the application itself is fine and the problem is somewhere around:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DNS
 ↓
Nginx
 ↓
Reverse Proxy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check DNS first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then verify the Nginx &lt;code&gt;server_name&lt;/code&gt; and &lt;code&gt;proxy_pass&lt;/code&gt; configuration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Build fails on the VPS
&lt;/h2&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;directly rather than through PM2.&lt;/p&gt;

&lt;p&gt;This makes build errors easier to read.&lt;/p&gt;

&lt;p&gt;Also check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;-v&lt;/span&gt;
npm &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A different Node.js version between development and production can cause unexpected build failures.&lt;/p&gt;




&lt;h3&gt;
  
  
  Environment variables aren't available
&lt;/h3&gt;

&lt;p&gt;Remember that changing environment variables may require a new build depending on how the variable is used.&lt;/p&gt;

&lt;p&gt;For example, variables prefixed with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NEXT_PUBLIC_
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;are intended to be exposed to client-side code and can be embedded during the build.&lt;/p&gt;

&lt;p&gt;Never put private secrets in &lt;code&gt;NEXT_PUBLIC_*&lt;/code&gt; variables.&lt;/p&gt;




&lt;h2&gt;
  
  
  20. Production Checklist
&lt;/h2&gt;

&lt;p&gt;Before considering the deployment complete, verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] VPS is updated&lt;/li&gt;
&lt;li&gt;[ ] Node.js version matches application requirements&lt;/li&gt;
&lt;li&gt;[ ] Git repository cloned&lt;/li&gt;
&lt;li&gt;[ ] Dependencies installed&lt;/li&gt;
&lt;li&gt;[ ] Production environment variables configured&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;npm run build&lt;/code&gt; succeeds&lt;/li&gt;
&lt;li&gt;[ ] Next.js runs correctly on localhost&lt;/li&gt;
&lt;li&gt;[ ] PM2 manages the application&lt;/li&gt;
&lt;li&gt;[ ] PM2 startup configured&lt;/li&gt;
&lt;li&gt;[ ] PM2 processes saved&lt;/li&gt;
&lt;li&gt;[ ] Nginx installed&lt;/li&gt;
&lt;li&gt;[ ] Virtual host configured&lt;/li&gt;
&lt;li&gt;[ ] Nginx configuration tested&lt;/li&gt;
&lt;li&gt;[ ] Domain points to VPS&lt;/li&gt;
&lt;li&gt;[ ] HTTPS configured&lt;/li&gt;
&lt;li&gt;[ ] SSL renewal tested&lt;/li&gt;
&lt;li&gt;[ ] Application works through the domain&lt;/li&gt;
&lt;li&gt;[ ] PM2 logs checked&lt;/li&gt;
&lt;li&gt;[ ] Nginx error logs checked&lt;/li&gt;
&lt;li&gt;[ ] Server firewall configured appropriately&lt;/li&gt;
&lt;li&gt;[ ] Production secrets are not committed to Git&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  21. Final Architecture
&lt;/h2&gt;

&lt;p&gt;After everything is configured, your production environment should look approximately like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         ┌──────────────────┐
                         │      GitHub      │
                         └────────┬─────────┘
                                  │
                              git pull
                                  │
                                  ▼
┌───────────────────────────────────────────────────────────┐
│                         VPS                               │
│                                                           │
│  ┌─────────────┐      ┌─────────────┐                     │
│  │    Nginx    │────▶│     PM2     │                     │
│  │             │      │             │                     │
│  │ :80 / :443  │      │ Next.js     │                     │
│  └─────────────┘      │ :3000       │                     │
│                       └──────┬──────┘                     │
│                              │                            │
│                              ▼                            │
│                       Next.js Application                 │
│                                                           │
└───────────────────────────────────────────────────────────┘
                                  ▲
                                  │
                              HTTPS
                                  │
                         ┌────────┴────────┐
                         │     Browser     │
                         └─────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important separation is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nginx handles incoming HTTP/HTTPS traffic.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PM2 keeps the Node.js process alive.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next.js serves the application.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Git provides the deployment source.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each component has a specific responsibility, which makes the system much easier to reason about and troubleshoot.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Deploying Next.js to a VPS involves more manual configuration than platforms such as Vercel, but you gain considerably more control over the environment and infrastructure.&lt;/p&gt;

&lt;p&gt;The core workflow is straightforward once you understand how the pieces fit together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Git
 ↓
Node.js
 ↓
Next.js build
 ↓
PM2
 ↓
Nginx
 ↓
Domain
 ↓
HTTPS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deployment is only half the story, once your app is live, performance and SEO determine whether it actually gets found and ranks. I cover that in detail in &lt;a href="https://www.dopescripts.com/blog/nextjs-performance-seo" rel="noopener noreferrer"&gt;Next.js Performance &amp;amp; SEO Optimization: 2026 Best Practices&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once this setup is working, the next step is usually automating the deployment process so that a push to your main branch can build and deploy the application without manually SSHing into the server.&lt;/p&gt;

&lt;p&gt;That is where a CI/CD pipeline using GitHub Actions can take this workflow to the next level.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>devops</category>
      <category>node</category>
    </item>
    <item>
      <title>Laravel Multi-Tenancy for SaaS: Architecture and Code (2026 Guide)</title>
      <dc:creator>Minhaj Haider Shah</dc:creator>
      <pubDate>Sat, 29 Aug 2026 16:54:50 +0000</pubDate>
      <link>https://dev.to/dopescripts/laravel-multi-tenancy-for-saas-architecture-and-code-2026-guide-4hfg</link>
      <guid>https://dev.to/dopescripts/laravel-multi-tenancy-for-saas-architecture-and-code-2026-guide-4hfg</guid>
      <description>&lt;p&gt;Every Laravel SaaS hits the same fork in the road eventually: how do you keep Tenant A from ever seeing Tenant B's data? Get it wrong and you're not looking at a bug report , you're looking at a data breach. Get it right early, and tenancy becomes invisible infrastructure instead of a source of 2am incidents.&lt;/p&gt;

&lt;p&gt;This guide walks through the real architectural decision , single database vs. multi-database vs. hybrid , and shows how to implement it with Spatie's &lt;code&gt;laravel-multitenancy&lt;/code&gt; package, with copy-pasteable code throughout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Your First Decision Is Rarely Your Final One&lt;/li&gt;
&lt;li&gt;1. Architectural Patterns&lt;/li&gt;
&lt;li&gt;2. Spatie's laravel-multitenancy Package&lt;/li&gt;
&lt;li&gt;3. Database Setup and Migrations&lt;/li&gt;
&lt;li&gt;4. Writing Tenant-Aware Code&lt;/li&gt;
&lt;li&gt;5. Pitfalls &amp;amp; Tips&lt;/li&gt;
&lt;li&gt;Laravel Multi-Tenancy Checklist&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;li&gt;Resources&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Your First Decision Is Rarely Your Final One
&lt;/h2&gt;

&lt;p&gt;Most SaaS architects start with a single, shared database because it's the fastest way to ship. That's usually the right call , but as customer count, compliance requirements, or per-tenant data volume grow, many teams end up migrating toward isolated or hybrid setups. Knowing that migration path exists &lt;em&gt;before&lt;/em&gt; you need it changes how you design your schema from day one.&lt;/p&gt;

&lt;p&gt;The practical trend in 2026 is a &lt;strong&gt;hybrid model&lt;/strong&gt;: a shared global database for cross-tenant concerns (billing, auth, platform admin) paired with isolated per-tenant databases for the actual application data. It gives you the operational simplicity of a single control plane with the hard data isolation of separate databases where it matters most.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Architectural Patterns
&lt;/h2&gt;

&lt;p&gt;For more details checkout our detailed article on this topic &lt;a href="https://dev.to/blog/laravel-single-db-vs-database-per-tenant"&gt;Laravel single vs Multi-Database&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Single Database (Shared Schema)
&lt;/h3&gt;

&lt;p&gt;Every tenant's rows live in the same tables, scoped by a &lt;code&gt;tenant_id&lt;/code&gt; column.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simplest to build, migrate, and deploy&lt;/li&gt;
&lt;li&gt;One connection pool, one set of migrations&lt;/li&gt;
&lt;li&gt;Cheapest to run at small-to-medium scale&lt;/li&gt;
&lt;li&gt;Easy cross-tenant analytics and admin tooling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A missing &lt;code&gt;tenant_id&lt;/code&gt; scope is a data-leak waiting to happen&lt;/li&gt;
&lt;li&gt;Noisy-neighbor risk , one tenant's heavy query can slow down everyone&lt;/li&gt;
&lt;li&gt;Harder to satisfy enterprise customers who require physical data isolation&lt;/li&gt;
&lt;li&gt;Backup/restore for a single tenant means filtering, not just restoring a file
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌───────────────────────────────┐
│         app_database          │
│  ┌──────────────────────────┐ │
│  │ users (tenant_id: 1,2)   │ │
│  │ orders (tenant_id: 1,2)  │ │
│  │ invoices (tenant_id: 1,2)│ │
│  └──────────────────────────┘ │
└───────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Multi-Database (Isolated Tenants)
&lt;/h3&gt;

&lt;p&gt;Each tenant gets their own database (or schema). The application switches connections at runtime based on who's logged in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;True data isolation , no shared tables means no leak risk from a forgotten &lt;code&gt;WHERE&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Per-tenant backup, restore, and even geographic placement&lt;/li&gt;
&lt;li&gt;Easier to satisfy enterprise/compliance requirements (SOC 2, HIPAA, data residency)&lt;/li&gt;
&lt;li&gt;One tenant's load doesn't degrade another's queries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Migrations must run against every tenant database&lt;/li&gt;
&lt;li&gt;Connection management adds real complexity&lt;/li&gt;
&lt;li&gt;Cross-tenant reporting requires aggregating across databases&lt;/li&gt;
&lt;li&gt;More expensive at scale (more connections, more overhead per tenant)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌───────────┐  ┌───────────┐  ┌───────────┐
│  tenant_a │  │  tenant_b │  │  tenant_c │
│  (own DB) │  │  (own DB) │  │  (own DB) │
└───────────┘  └───────────┘  └───────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Hybrid (Shared Global + Per-Tenant Databases)
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;landlord&lt;/code&gt; database holds tenant records, billing, and platform-wide auth. Each tenant then gets an isolated database for their actual application data.&lt;/p&gt;

&lt;p&gt;This pattern is increasingly the default for growing Laravel SaaS products in 2026 , it keeps platform operations (like tenant provisioning and billing) simple while still giving each tenant hard data isolation for the data that actually matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Spatie's laravel-multitenancy Package
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/spatie/laravel-multitenancy" rel="noopener noreferrer"&gt;&lt;code&gt;spatie/laravel-multitenancy&lt;/code&gt;&lt;/a&gt; is the go-to solution for implementing this pattern in Laravel without hand-rolling connection-switching logic yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require spatie/laravel-multitenancy

php artisan vendor:publish &lt;span class="nt"&gt;--tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"multitenancy-config"&lt;/span&gt;
php artisan vendor:publish &lt;span class="nt"&gt;--tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"multitenancy-migrations"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuring tenant detection
&lt;/h3&gt;

&lt;p&gt;The package supports detecting the current tenant by domain, subdomain, or a custom strategy. Domain-based detection is the most common approach for B2B SaaS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/multitenancy.php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Spatie\Multitenancy\TenantFinder\DomainTenantFinder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'tenant_finder'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;DomainTenantFinder&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="s1"&gt;'tenant_model'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;\App\Models\Tenant&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="s1"&gt;'switch_tenant_tasks'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;\Spatie\Multitenancy\Tasks\SwitchTenantDatabaseTask&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Spatie\Multitenancy\Tasks\PrefixCacheTask&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The tenant model
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/Models/Tenant.php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Spatie\Multitenancy\Models\Tenant&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;BaseTenant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Tenant&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseTenant&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$fillable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'domain'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'database'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Middleware: switching the connection per request
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// bootstrap/app.php or routes/web.php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Spatie\Multitenancy\Http\Middleware\NeedsTenant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Spatie\Multitenancy\Http\Middleware\EnsureValidTenantSession&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nc"&gt;NeedsTenant&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;EnsureValidTenantSession&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/dashboard'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;DashboardController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once &lt;code&gt;NeedsTenant&lt;/code&gt; resolves the tenant from the request domain, every subsequent Eloquent query in that request automatically hits the correct tenant database , no manual scoping required.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Database Setup and Migrations
&lt;/h2&gt;

&lt;p&gt;Split your migrations into two directories: &lt;code&gt;database/migrations&lt;/code&gt; for the landlord (global) database, and &lt;code&gt;database/migrations/tenant&lt;/code&gt; for anything that belongs inside each tenant database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/multitenancy.php&lt;/span&gt;

&lt;span class="s1"&gt;'migrate_tenant_migration_path'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;database_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'migrations/tenant'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running migrations across all tenants
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan tenants:artisan &lt;span class="s2"&gt;"migrate --path=database/migrations/tenant"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating a tenant programmatically
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\Tenant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$tenant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Tenant&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Acme Corp'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'domain'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'acme.yourapp.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'database'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'tenant_acme'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nv"&gt;$tenant&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;makeCurrent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$tenant&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createDatabase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;Artisan&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'migrate'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'--database'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'tenant'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'--path'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'database/migrations/tenant'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'--force'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Global vs. tenant-specific tables
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Table&lt;/th&gt;
&lt;th&gt;Location&lt;/th&gt;
&lt;th&gt;Reasoning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tenants&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Landlord DB&lt;/td&gt;
&lt;td&gt;Registry of all tenants , must be queryable without a tenant context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;subscriptions&lt;/code&gt; / &lt;code&gt;plans&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Landlord DB&lt;/td&gt;
&lt;td&gt;Billing spans the platform, not a single tenant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;users&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tenant DB&lt;/td&gt;
&lt;td&gt;Each tenant's users are isolated from every other tenant's&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;orders&lt;/code&gt;, &lt;code&gt;invoices&lt;/code&gt;, &lt;code&gt;products&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Tenant DB&lt;/td&gt;
&lt;td&gt;Core application data , the whole reason isolation exists&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  4. Writing Tenant-Aware Code
&lt;/h2&gt;

&lt;p&gt;Once the middleware has switched the connection, ordinary Eloquent queries are automatically scoped to the current tenant , no extra &lt;code&gt;where()&lt;/code&gt; clause needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Inside a request that has already resolved a tenant via middleware&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Only returns users belonging to the current tenant's database&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating a tenant at registration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RegisterTenantController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$tenant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Tenant&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;company_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'domain'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Str&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;company_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'.yourapp.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'database'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'tenant_'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nc"&gt;Str&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;company_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'_'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="nv"&gt;$tenant&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;makeCurrent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$tenant&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createDatabase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nc"&gt;Artisan&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'migrate'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'--database'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'tenant'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'--path'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'database/migrations/tenant'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'--force'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;admin_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;admin_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'password'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Hash&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$tenant&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/dashboard"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Pitfalls &amp;amp; Tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Missing tenant scope is the #1 data-leak risk.&lt;/strong&gt; In a single-DB setup, one forgotten &lt;code&gt;tenant_id&lt;/code&gt; filter on a query , especially a raw query or a job running outside the request lifecycle , can leak one tenant's data to another. Always scope explicitly, or rely on global scopes/traits that enforce it automatically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Unindexed tenant queries get slow fast.&lt;/strong&gt; Every query in a shared-schema setup effectively filters by &lt;code&gt;tenant_id&lt;/code&gt;, so it needs to be the leading column in your indexes. A composite index like &lt;code&gt;(tenant_id, created_at)&lt;/code&gt; will outperform a plain index on &lt;code&gt;created_at&lt;/code&gt; alone once you have more than a handful of tenants.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Queued jobs lose tenant context by default.&lt;/strong&gt; Since jobs run outside the HTTP request lifecycle, the middleware that sets the current tenant never fires. Spatie's package provides tenant-aware job dispatching , use it, or you'll get jobs silently running against the wrong (or no) tenant database.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cache keys need tenant prefixing.&lt;/strong&gt; Without it, a cached value for Tenant A can be served to Tenant B. The &lt;code&gt;PrefixCacheTask&lt;/code&gt; in the config above handles this automatically , don't skip it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test tenant isolation explicitly.&lt;/strong&gt; Write a test that creates two tenants, seeds different data into each, and asserts that switching context returns only the expected tenant's rows. This is the single highest-leverage test in a multi-tenant codebase.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Laravel Multi-Tenancy Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Chosen an architecture (single-DB, multi-DB, or hybrid) based on actual compliance/scale needs , not by default&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;spatie/laravel-multitenancy&lt;/code&gt; installed and tenant finder configured&lt;/li&gt;
&lt;li&gt;[ ] Landlord vs. tenant migrations split into separate directories&lt;/li&gt;
&lt;li&gt;[ ] Every tenant-scoped table has &lt;code&gt;tenant_id&lt;/code&gt; as the leading index column (single-DB) or lives in an isolated database (multi-DB)&lt;/li&gt;
&lt;li&gt;[ ] Middleware (&lt;code&gt;NeedsTenant&lt;/code&gt;) applied to every tenant-facing route&lt;/li&gt;
&lt;li&gt;[ ] Cache keys are tenant-prefixed&lt;/li&gt;
&lt;li&gt;[ ] Queued jobs explicitly carry and restore tenant context&lt;/li&gt;
&lt;li&gt;[ ] A test exists that verifies tenant data isolation end-to-end&lt;/li&gt;
&lt;li&gt;[ ] Tenant provisioning (creation, database setup, migration run) is scripted, not manual&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Should I start with single-DB or multi-DB for a new SaaS?&lt;/strong&gt;&lt;br&gt;
Start with single-DB unless you already know you need hard isolation (e.g., an enterprise customer requiring it contractually). It's faster to build and sufficient for most early-stage products. Design your schema so &lt;code&gt;tenant_id&lt;/code&gt; is present everywhere from day one , that's what makes a later migration to multi-DB tractable instead of a rewrite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Spatie's package support subdomain-based tenant detection?&lt;/strong&gt;&lt;br&gt;
Yes , &lt;code&gt;DomainTenantFinder&lt;/code&gt; works with both full custom domains and subdomains. You can also write a custom tenant finder if you need to resolve tenants by something other than the request domain, like a header or path segment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I handle a tenant that outgrows shared infrastructure?&lt;/strong&gt;&lt;br&gt;
This is exactly what the hybrid model is for. Keep the tenant's record and billing in the landlord database, but provision them their own isolated tenant database. Since your application code is already tenant-aware, this becomes an infrastructure change rather than an application rewrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://spatie.be/docs/laravel-multitenancy/v3/introduction" rel="noopener noreferrer"&gt;Spatie , laravel-multitenancy Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://laravel.com/docs/database" rel="noopener noreferrer"&gt;Laravel Docs , Database Configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://laravel.com/docs/queues" rel="noopener noreferrer"&gt;Laravel Docs , Queues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/spatie/laravel-multitenancy" rel="noopener noreferrer"&gt;Spatie , laravel-multitenancy on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>discuss</category>
    </item>
  </channel>
</rss>
