Next.js Deployment with Nginx and PM2
DevOps18 min read

By Akshay Singh

Share this article:

How to Host a Next.js App on a VPS Using Nginx and PM2

When building a Next.js application, the development experience is seamless. Running npm run dev gets you a hot-reloading server in seconds. However, when it comes time to move your application to a production environment, simply running npm run start in a tmux session and exposing a port directly to the internet is a recipe for disaster.

In a production environment, you need your Next.js application to be:

  • Resilient: It should automatically restart if it crashes.
  • Persistent: It should automatically start when the server reboots.
  • Secure and Performant: It should sit behind a robust reverse proxy that handles SSL/TLS termination and efficiently serves static assets.

To achieve this, we rely on a proven stack: PM2 (a production process manager for Node.js) and Nginx (a high-performance web server and reverse proxy).

In this comprehensive guide, we will walk through the entire process of deploying a Server-Side Rendered (SSR) Next.js application on a Linux VPS.


Prerequisites

Before we begin, ensure you have the following ready:

  • A Linux VPS (Virtual Private Server) running Ubuntu 22.04 or 24.04.
  • SSH access to your server with a non-root user that has sudo privileges.
  • Node.js and npm installed on the server (preferably using NVM or NodeSource).
  • A domain name pointing to your server's public IP address.
  • A Next.js application ready for deployment.

Step 1: Prepare and Build Your Next.js Application

First, SSH into your server:

ssh your-username@your-server-ip

Navigate to the directory where you want to host your application. A standard location is /var/www/.

sudo mkdir -p /var/www/my-next-app
sudo chown -R $USER:$USER /var/www/my-next-app
cd /var/www/my-next-app

At this point, you can clone your repository from GitHub. For the sake of this tutorial, we will assume you have your code here.

Once your code is on the server, install the dependencies:

npm install

Next.js requires a build step before it can be run in production. This command compiles your React code, optimizes your assets, and prepares the Server-Side Rendering output:

npm run build

You should see an output detailing the pages built, their sizes, and the static/server breakdown.


Step 2: Manage the Next.js Process with PM2

If we run npm run start, the app will run, but it will block our terminal, and if it crashes, it stays dead. This is where PM2 comes in.

PM2 is a daemon process manager that will help you manage and keep your application online 24/7.

  1. Install PM2 globally:
sudo npm install -g pm2
  1. Start your Next.js application with PM2:

Since Next.js is started via npm scripts, we instruct PM2 to run the npm command and pass start as an argument.

pm2 start npm --name "my-next-app" -- start

Your application is now running in the background. You can check its status using:

pm2 status

You can also view the logs in real-time:

pm2 logs my-next-app
  1. Ensure PM2 Restarts on Server Boot:

To ensure your application starts automatically if the server is restarted, run PM2's startup script generator:

pm2 startup

PM2 will output a command that looks something like this:

sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u your-username --hp /home/your-username

Copy and paste that exact output command into your terminal and run it.

After running the generated command, save your current PM2 process list:

pm2 save

Your Next.js application is now robustly managed. By default, Next.js runs on port 3000. Next, we need to expose it securely to the world.


Step 3: Install and Configure Nginx

Nginx will act as our front door. It will listen for web traffic on port 80 (HTTP) and 443 (HTTPS) and securely route it to our Next.js application running on port 3000.

  1. Install Nginx:
sudo apt update
sudo apt install nginx -y
  1. Create a new Nginx Server Block:

Nginx configurations are managed in server block files. Create a new one for your domain:

sudo nano /etc/nginx/sites-available/my-next-app

Paste the following configuration, replacing example.com with your actual domain name:

server {
    listen 80;
    server_name example.com www.example.com;

    # Gzip Compression for better performance
    gzip on;
    gzip_proxied any;
    gzip_comp_level 4;
    gzip_types text/css application/javascript image/svg+xml;

    # Route traffic to Next.js
    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        
        # Forward real client IP to Next.js
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_addrs;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # Optional: Highly cache static assets handled by Next.js
    location /_next/static {
        proxy_pass http://localhost:3000;
        proxy_cache_valid 200 302 365d;
        proxy_cache_valid 404 1m;
        expires max;
        access_log off;
    }
}

Understanding the Configuration:

  • proxy_pass http://localhost:3000; forwards incoming requests to the internal Next.js process.
  • proxy_set_header Upgrade $http_upgrade; allows WebSockets to work, which is vital for Next.js hot-reloading (if used in dev) and certain real-time features.
  • X-Forwarded-For and X-Real-IP ensure Next.js knows the actual IP of the visitor, not just Nginx's internal 127.0.0.1.
  • The location /_next/static block is an optimization. Next.js generates static assets with unique hashes, meaning they can be cached aggressively by the browser.
  1. Enable the Configuration:

Create a symbolic link to the sites-enabled directory:

sudo ln -s /etc/nginx/sites-available/my-next-app /etc/nginx/sites-enabled/

Test your Nginx configuration for syntax errors:

sudo nginx -t

If it says syntax is ok and test is successful, restart Nginx:

sudo systemctl restart nginx

If you navigate to your domain in a web browser, you should now see your Next.js application!


Step 4: Secure with SSL (Let's Encrypt)

Serving your application over plain HTTP is not acceptable for production. We will use Certbot to provision a free SSL certificate from Let's Encrypt.

  1. Install Certbot and the Nginx plugin:
sudo apt install certbot python3-certbot-nginx -y
  1. Obtain the SSL Certificate:

Run Certbot, which will automatically verify your domain and update your Nginx configuration to support HTTPS.

sudo certbot --nginx -d example.com -d www.example.com

Certbot will ask for your email address (for urgent renewal notices) and ask you to agree to the Terms of Service. It will then provision the certificate and rewrite your Nginx server block to redirect all HTTP traffic to HTTPS automatically.


Conclusion

Deploying a Next.js application manually requires a few more steps than using a managed platform like Vercel, but it gives you complete control over your infrastructure, significantly lower costs at scale, and a deeper understanding of how web architecture works.

By leveraging PM2, you ensure your Node.js process is resilient and always online. By utilizing Nginx, you get a battle-tested reverse proxy that efficiently handles traffic routing and SSL termination.

Further Optimization (Standalone Output)

If you want to take your deployment a step further, look into Next.js's output: 'standalone' feature in next.config.js. This creates a highly optimized, minimal server output that drastically reduces your deployment size by strictly including only the necessary Node.js modules required for production. It is especially useful if you eventually decide to containerize your application with Docker.

nextjsnginxpm2vpsdevopsdeploymentreact
TheDailyDevsTheDailyDevs
TheDailyDevs is a developer-first blog and knowledge hub created by passionate engineers to share real-world development tips, deep-dive tutorials, industry insights, and hands-on solutions to everyday coding challenges. Whether you're building apps, exploring new frameworks, or leveling up your dev game, you'll find practical, no-fluff content here, updated daily.