Skip to main content

Deploying Next.js 14 (SSR) on Cloudways: PM2 and Nginx Reverse Proxy Setup

 Cloudways is an exceptional Platform-as-a-Service (PaaS) for PHP applications like WordPress or Laravel. However, deploying a modern Node.js application—specifically Next.js 14 with Server-Side Rendering (SSR)—on a platform architected for LAMP stacks introduces significant friction.

If you have tried simply uploading your .next folder and hoping for the best, you likely encountered 404 errors, 502 Bad Gateway responses, or a static site that fails to hydrate.

This guide details a production-grade deployment strategy. We will bypass the default Apache handling, implement a robust process manager (PM2), and configure a custom Nginx reverse proxy to serve Next.js 14 efficiently.

The Architecture Gap: Why This Fails by Default

To solve the deployment issue, you must understand the underlying architecture of a standard Cloudways server.

Cloudways servers typically use a hybrid stack: Nginx (as a static cache/proxy) sits in front of Apache (which processes PHP).

When you deploy a Next.js application, you are running a long-lived Node.js process (usually on port 3000). By default, the Cloudways Nginx/Apache duo is looking for index.php or index.html files on port 80/443. It has no knowledge of your Node process running on port 3000.

To bridge this gap, we must:

  1. Keep Node Alive: Ensure the next start process runs permanently, restarts on crash, and survives server reboots (using PM2).
  2. Reverse Proxy: Configure Nginx to intercept traffic destined for your domain and tunnel it to localhost:3000, bypassing Apache entirely.

Prerequisites

Before modifying configuration files, ensure you have the following:

  1. A Cloudways Server: Preferably a clean application slot. Select "Custom PHP" when creating the application to get a blank slate.
  2. SSH Access: You need "Master Credentials" from your Cloudways dashboard.
  3. Node.js 18.17+: Next.js 14 requires Node.js 18.17 or later.

Step 1: Verify and Update Node.js

Cloudways servers often come with older LTS versions of Node.js. SSH into your server and check the version.

ssh master@<your-server-ip>
node -v

If the version is below 18.17, you cannot run Next.js 14. You should install nvm (Node Version Manager) to manage this locally for your user.

# Install NVM (Check latest version on NVM github)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

# Activate NVM
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

# Install and Use Node 20 (LTS)
nvm install 20
nvm use 20
nvm alias default 20

Step 2: Prepare the Next.js Application

For SSR to work correctly, we need the build artifacts. While you can build locally and upload the .next folder via SFTP, building on the server ensures architecture compatibility (especially for sharp/image optimization libraries).

Navigate to your application folder. On Cloudways, this is usually strictly path-dependent.

cd applications/<your_app_name>/public_html

Clone your repository or upload your files. Ensure your package.json includes the standard start script:

"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start",
  "lint": "next lint"
}

Install dependencies and build the project:

npm ci
npm run build

Note: npm ci is preferred over npm install in CI/CD/Production environments as it strictly installs versions from the lockfile.

Step 3: Implement Process Management with PM2

Running npm start in your terminal is insufficient; if you close the SSH session, the site goes down. We use PM2 to daemonize the application.

Install PM2 globally:

npm install -g pm2

Create a configuration file named ecosystem.config.js in your project root. This configuration is superior to running long CLI commands because it documents your deployment settings as code.

// ecosystem.config.js
module.exports = {
  apps: [
    {
      name: "nextjs-app",
      script: "npm",
      args: "start",
      cwd: "/home/master/applications/<your_app_name>/public_html", // Update this path!
      instances: 1,
      autorestart: true,
      watch: false,
      max_memory_restart: "1G",
      env: {
        NODE_ENV: "production",
        PORT: 3000 // Internal port
      },
    },
  ],
};

Start the application:

pm2 start ecosystem.config.js
pm2 save

Verify it is running:

pm2 status

At this stage, your app is running on localhost:3000, but your domain still shows the default Cloudways/PHP page.

Step 4: Configuring the Nginx Reverse Proxy

This is the most critical step. We need to tell the server to forward external requests to port 3000.

Cloudways does not allow you to edit the root nginx.conf. However, specific application configurations can be modified.

  1. Log in to the Cloudways Platform.
  2. Navigate to Servers -> [Your Server] -> Applications -> [Your App].
  3. Go to Application Settings -> Application Settings (Menu) -> Web Server Settings (or "Varnish Settings" depending on UI version, but we are looking for Nginx).

Note: If you cannot find a direct Nginx editor in the UI, Cloudways enables include files.

A more reliable, engineer-centric method is creating a custom Nginx configuration file in your application directory if the platform supports the nginx-app.conf pattern, or editing the configuration directly if you have root access (which Cloudways limits).

The standard workaround that works on Cloudways "PHP" applications to bypass Apache is acting as a proxy.

Since we cannot easily edit the main Nginx block, we will utilize the .htaccess trick to proxy requests if you are on an Apache-enabled setup, OR (highly recommended) contact support to add a custom Nginx directive if the UI is restricted.

However, the modern Cloudways approach allows specific Nginx configurations.

Locate your Nginx configuration file typically found in /home/master/conf/nginx/. If locked, use the Cloudways specific setup for Node.js apps if available. Assuming you are on a "Custom PHP" app, we need to bridge port 80 to 3000.

The Working Nginx Configuration

If you have the ability to modify the Nginx config (via .conf files in your user directory that Cloudways includes), add the following block.

If you are strictly limited to the UI, verify if your specific Cloudways server type supports "Nginx Proxy".

Here is the Nginx block required to successfully proxy Next.js:

server {
    listen 80;
    server_name your-domain.com;

    location / {
        # Proxy to the Node.js process
        proxy_pass http://127.0.0.1:3000;
        
        # Standard Proxy Headers
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Bypass cache for Next.js specific routes if necessary
        proxy_cache_bypass $http_upgrade;
    }
}

The .htaccess Fallback (The "Cloudways" Way)

If you cannot edit Nginx directly because of permission locks, you can use the Apache .htaccess file in public_html to act as a proxy. This is slightly less performant but functionally robust on this specific host.

Edit public_html/.htaccess:

RewriteEngine On
RewriteRule ^$ http://127.0.0.1:3000/ [P,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ http://127.0.0.1:3000/$1 [P,L]

Why this works: The [P] flag instructs Apache to act as a proxy (mod_proxy), sending the traffic to your localhost Node process.

Step 5: Handling HTTPS and Secure Cookies

Next.js 14 relies heavily on headers to determine the protocol. When deploying behind a proxy (Nginx or Apache), the req.protocol might default to http even if the user is visiting https, causing redirect loops or "mixed content" errors.

In your next.config.js, you do not usually need changes, but you must ensure your proxy passes X-Forwarded-Proto.

If you used the .htaccess method above, Apache usually handles this automatically. If you encounter issues where cookies are not being set because they are Secure, ensure your environment knows it is proxied.

Deep Dive: Managing Rebuilds and Caching

One common pitfall with Next.js on server environments is the build cache.

When you deploy a new version:

  1. Pull code.
  2. Run npm run build.
  3. Critical: You must restart PM2.

If you do not restart PM2, the Node process will continue serving the old build artifacts from memory, while the filesystem contains new assets. This results in users downloading a new HTML file that references old JavaScript chunks that no longer exist, causing crashes.

Create a deploy.sh script in your root to automate this:

#!/bin/bash

# Navigate to directory
cd /home/master/applications/<your_app_name>/public_html

# Pull latest code
git pull origin main

# Install new deps
npm ci

# Build Next.js
npm run build

# Restart the specific PM2 process
pm2 restart nextjs-app

echo "Deployment complete."

Make it executable: chmod +x deploy.sh. Now you can deploy with a single command: ./deploy.sh.

Troubleshooting Common Errors

1. 502 Bad Gateway

This means the proxy (Nginx/Apache) cannot connect to port 3000.

  • Fix: Run pm2 status to ensure the app is online. Check pm2 logs nextjs-app for boot errors.

2. Styles or Images Not Loading (404s)

This often happens if the next.config.js assetPrefix is misconfigured or if Nginx is trying to serve the static files itself without finding them.

  • Fix: Ensure your proxy configuration forwards all requests, including /_next/*, to port 3000. Next.js handles static files efficiently; do not try to offload this to Nginx unless you are an advanced user manually configuring the _next/static alias.

3. "JavaScript heap out of memory"

Building Next.js 14 requires significant RAM. Standard Cloudways instances (1GB or 2GB) might fail during npm run build.

  • Fix: Increase the swap space on your server or temporarily upgrade the server size during the build. Alternatively, set NODE_OPTIONS in your build command: "build": "NODE_OPTIONS='--max-old-space-size=4096' next build"

Conclusion

Hosting Next.js 14 on Cloudways requires stepping outside the platform's PHP-first comfort zone. By leveraging PM2 for process stability and configuring a reverse proxy (via Nginx or Apache's proxy module), you gain the performance of a VPS with the management benefits of Cloudways.

This setup provides a robust foundation for SSR, ensuring your application is SEO-friendly, performant, and resilient.