A VPS is a practical place to host a production Next.js app when you want control over Node.js, Nginx, SSL, environment variables, logs, background work, and release timing. The goal is not only to start the app on port 3000. The goal is to run it behind a reverse proxy, keep the process supervised, protect secrets, and make future deployments repeatable.
This guide uses a common setup: Ubuntu, a deployment user, Git, Node.js, PM2, Nginx, Certbot, and a Next.js production build. It fits dashboards, SaaS frontends, documentation sites with server rendering, ecommerce interfaces, and private app UIs that need direct server access.
Prepare the server before cloning the application. Use a non-root deployment user, confirm DNS points to the VPS, decide which domain will serve the app, and keep environment variables out of the repository. If the app uses a database, payment gateway, private API, object storage, or email provider, collect those credentials before the build step.
npm run build.Use a dedicated user instead of running the app as root. The exact permission model depends on your team, but a separate user keeps app files, process ownership, and SSH access easier to audit.
sudo adduser deploy
sudo usermod -aG sudo deploy
su - deploy
After the user is ready, add SSH keys, review firewall rules, and close access that is not needed. For a basic web app, public access usually belongs on ports 80 and 443, while the Node.js process should listen locally behind Nginx.
Install the runtime and services the app needs. Use the Node.js version expected by the project. If the repository includes .nvmrc or an engines field, match that version rather than guessing.
sudo apt update
sudo apt install -y git nginx
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
sudo npm install -g pm2
Confirm the versions before deployment:
node -v
npm -v
nginx -v
pm2 -v
Clone the repository into a predictable location such as /home/deploy/apps/example-app. Keep secrets in an environment file on the server, not inside the repository. Check file permissions if the variables include API keys or database credentials.
mkdir -p ~/apps
cd ~/apps
git clone [email protected]:team/example-app.git
cd example-app
npm ci
nano .env.production
npm run build
If the build fails, read the first real error before changing server resources. Common causes include a missing environment variable, wrong Node.js version, unavailable private package, TypeScript error, or memory limit during build.
PM2 keeps the Node.js process supervised and gives you logs, restart controls, and startup integration. For a standard Next.js app, build first and then run the production start command.
pm2 start npm --name nextjs-app -- start
pm2 status
pm2 logs nextjs-app
pm2 save
Enable PM2 startup for the deployment user only after the app runs correctly. PM2 will print the command that should be executed with sudo.
pm2 startup
Nginx should handle public HTTP and HTTPS traffic, then proxy requests to the local Node.js process. Keep the app bound to localhost where possible.
sudo nano /etc/nginx/sites-available/example.com
server {
listen 80;
server_name example.com www.example.com;
location / {
proxy_pass http://127.0.0.1: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;
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;
}
}
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/example.com
sudo nginx -t
sudo systemctl reload nginx
After DNS resolves to the VPS and Nginx responds on port 80, issue a certificate with Certbot. Then verify the site through the public domain, not only through localhost.
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
pm2 logs and Nginx error logs.A simple update flow is enough for many teams: pull the selected branch, install dependencies, build, restart the PM2 process, and check logs. For higher-risk apps, keep a release directory pattern or use a deployment tool so the previous version can be restored quickly.
cd ~/apps/example-app
git pull
npm ci
npm run build
pm2 restart nextjs-app
pm2 logs nextjs-app --lines 80
If the new build fails after restart, roll back to the previous known commit, rebuild, restart, and review logs before trying again. For Git-based deployment ideas, see the Voxfor guide to auto deploying GitHub apps to a VPS.
PM2 is a direct fit when the VPS only needs a Node.js app and Nginx. Docker Compose is useful when the app ships with supporting services such as Redis, PostgreSQL, workers, queues, or a full multi-container stack. The choice is operational: use the tool your team can debug under pressure. The related Docker versus Docker Compose VPS guide explains the tradeoffs.
After launch, watch process restarts, memory use, disk growth, build artifacts, error rates, Nginx logs, SSL renewal, API latency, and database connections. Monitoring matters because a Next.js app can fail at several layers: the Node.js process, reverse proxy, DNS, certificate, environment variables, or external API.
This guide was prepared by the Voxfor editorial team for developers and site owners deploying Next.js apps on VPS infrastructure. Voxfor works with VPS hosting, Linux server setup, WordPress infrastructure, deployment workflows, and application hosting for technical teams.
Yes. A VPS can run a production Next.js app with Node.js, PM2 or another process manager, Nginx as a reverse proxy, SSL, and a documented deployment process.
Use PM2 for a simple Node.js deployment. Use Docker Compose when the app needs multiple services, isolated containers, or a container-based release process.
No. Nginx handles public web traffic and proxies requests to the local Next.js process. The Next.js app still runs through Node.js or a standalone output server.
Store production variables on the server or in a deployment secret store, not in Git. Review which variables are safe for browser exposure and which must remain server-side.
A 502 error usually means Nginx cannot reach the local app process. Check the app port, PM2 status, Nginx proxy settings, firewall rules, and application logs.
Check HTTPS, redirects, login, API routes, dynamic pages, image loading, logs, process status, and any workflow that depends on production environment variables.