Next.js needs a Node.js server whenever your app uses server rendering, API routes, middleware or image optimisation. On a VPS you build the app once, start it with Node.js and put a web server in front. This guide covers both the classic next start method and the leaner standalone output.
Before you start
- A Linux VPS with Node.js 20 or newer. See Install Node.js and npm on a Linux VPS.
- PM2 installed: PM2 process manager.
- Your project in a Git repository, or a way to upload it (SFTP, rsync).
- At least 1 GB of RAM free for the build; large apps need 2 GB or more.
Get the code onto the server
- Log in as your app user and clone the project:
cd ~ git clone https://github.com/you/myapp.git cd myapp - Create the production environment file with your real values (see Node.js environment variables):
nano .env.production
Method 1: build and run with next start
- Install dependencies exactly as locked and build:
npm ci npm run build - Test the production server:Run
npx next start -p 3000 -H 127.0.0.1curl -I http://127.0.0.1:3000in a second session, then stop it with Ctrl+C. - Run it under PM2:
pm2 start npm --name myapp -- start -- -p 3000 -H 127.0.0.1 pm2 save
Method 2: standalone output
Standalone mode copies only the files your app needs into .next/standalone, so you do not need the full node_modules at runtime. It is ideal for small VPS plans and Docker.
- Enable it in
next.config.js:module.exports = { output: 'standalone', }; - Build, then copy the static assets next to the server (Next.js does not copy them for you):
npm ci npm run build cp -r public .next/standalone/ cp -r .next/static .next/standalone/.next/ - Start the generated server with PM2:
cd .next/standalone PORT=3000 HOSTNAME=127.0.0.1 pm2 start server.js --name myapp pm2 save
Warning: If pages load without CSS or images in standalone mode, you forgot to copy public and .next/static. Repeat that step after every build.
Put Nginx in front
Proxy your domain to 127.0.0.1:3000 as shown in Node.js reverse proxy with Nginx, Apache or LiteSpeed, then add HTTPS with SSL for Node.js apps.
Deploy updates
cd ~/myapp
git pull
npm ci
npm run build
pm2 reload myappFor standalone builds, copy the static folders again before reloading. To automate this, see Deploy to a VPS with GitHub Actions.
Troubleshooting
- Build killed or
JavaScript heap out of memory— add swap or more RAM, or build on your computer/CI and upload the result. See Fix common Node.js errors. EADDRINUSE— something already uses port 3000. Runpm2 listand stop the old copy.- Old content after deploy — you rebuilt but did not reload PM2.
