๐ŸŽ‰ Limited-time Hostinger deal โ€” up to 75% off + extra with codeWEBHOST2026Get my link โ†’
H
Tutorials

Deploy a Next.js app on Hostinger VPS (step by step)

12 min read

A complete walkthrough: provision the server, install Node 24, build with standalone output, keep it alive with PM2, put Nginx and free HTTPS in front, and script your redeploys.

Why a VPS, and not shared hosting

A Next.js app that uses server components, route handlers, ISR or any kind of server rendering is a long-lived Node process. Shared hosting is built to run PHP scripts that start and die inside a single request โ€” there is no supervisor to keep `node` running, no way to bind a port, and usually no SSH. That is the whole reason this guide starts with a VPS.

The exception is a fully static site. If your app runs with `output: 'export'` and has no server features at all, you can drop the generated HTML onto any shared plan and skip everything below. Everything else needs a real server.

The entry-tier KVM 1 is enough for a small-to-medium Next.js site: 1 vCPU, 4 GB RAM and 50 GB NVMe. RAM is the spec that matters most here, because `next build` is memory-hungry and 4 GB at this price point is unusually generous.

What you need before you start

Five minutes of setup saves an hour of debugging later.

  • A VPS running Ubuntu 24.04 LTS or 26.04 LTS โ€” pick the OS template when you create the server.
  • A domain with an A record pointing at your VPS's IP address. Set this up first; DNS takes time to propagate and Let's Encrypt will fail without it.
  • Your app in a Git repository the server can reach.
  • Your SSH public key added to the server during provisioning โ€” far safer than a root password.

Step 1 โ€” Log in and create a deploy user

Running your app as root is the single most common mistake in VPS tutorials. Create an unprivileged user that owns the app, and reserve root for package installs.

From your laptop, connect as root, then create the deploy user:
bash
ssh root@YOUR_SERVER_IP

adduser --disabled-password --gecos "" deploy
usermod -aG sudo deploy

# Copy your SSH key across so you can log in as deploy
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy/
Lock the front door before you install anything:
bash
ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw --force enable
ufw status

Step 2 โ€” Install Node.js 24 LTS

As of September 2026, Node 24 (Krypton) is the Active LTS line and the right default for new deployments. Node 22 is in maintenance until April 2027, so existing apps are not in danger, but new servers should start on 24.

Ubuntu's own `apt` repository ships an old Node. Use NodeSource instead so you get the version you actually asked for.

Log out and back in as `deploy`, then:
bash
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt-get install -y nodejs

node -v   # v24.x.x
npm -v

Step 3 โ€” Add swap if you are on a 1 vCPU plan

`next build` can spike well past 1 GB of memory on a mid-sized app. With 4 GB of RAM you are usually fine, but a swap file costs nothing and turns a hard out-of-memory crash into a merely slow build. On smaller servers it is not optional.

Create a 2 GB swap file that survives reboots:
bash
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

free -h   # confirm swap is listed

Step 4 โ€” Clone, configure and build with standalone output

Setting `output: 'standalone'` in your Next config is the highest-value change you can make for a VPS deployment. Next.js traces exactly which files each page needs and copies them โ€” plus a minimal `server.js` โ€” into `.next/standalone`. You end up shipping a fraction of `node_modules`, which means faster deploys and a smaller disk footprint.

There is one catch that trips up almost everyone: the standalone folder does not include `public` or `.next/static`, because Next.js assumes a CDN will serve them. Copy them in yourself and `server.js` will serve them automatically.

next.config.ts
ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  output: "standalone",
};

export default nextConfig;
Clone into /var/www, install and build:
bash
sudo mkdir -p /var/www && sudo chown deploy:deploy /var/www
cd /var/www
git clone https://github.com/you/your-app.git myapp
cd myapp

npm ci
npm run build

# The bit everyone forgets:
cp -r public .next/standalone/
cp -r .next/static .next/standalone/.next/

Step 5 โ€” Keep it running with PM2

Your app needs to restart when it crashes and come back after a reboot. PM2 handles both. Point it at the standalone `server.js` rather than `next start` โ€” the minimal server boots faster and does not need the full dependency tree.

Bind to `127.0.0.1`, not `0.0.0.0`. The Node process should only be reachable from Nginx on the same machine; anything else means people can bypass your reverse proxy by hitting port 3000 directly.

Start the app and register it with systemd:
bash
sudo npm install -g pm2

cd /var/www/myapp
PORT=3000 HOSTNAME=127.0.0.1 pm2 start .next/standalone/server.js --name myapp

pm2 save
pm2 startup systemd
# PM2 prints a sudo command โ€” copy it, run it, then run 'pm2 save' again.
Useful day-to-day commands:
bash
pm2 status
pm2 logs myapp --lines 100
pm2 restart myapp
pm2 monit

Step 6 โ€” Put Nginx in front

The Next.js self-hosting documentation recommends a reverse proxy rather than exposing the Node server directly, and the reasoning is worth understanding: Nginx absorbs malformed requests, slow-connection attacks, oversized payloads and rate limiting, so your Node process spends its CPU rendering pages instead of validating junk traffic.

The `location /_next/static/` block below is an easy win. Those files are content-hashed and immutable, so letting Nginx serve them straight off disk with a long cache header takes that load off Node entirely.

bash
sudo apt-get install -y nginx
sudo nano /etc/nginx/sites-available/myapp
/etc/nginx/sites-available/myapp
nginx
server {
    listen 80;
    server_name example.com www.example.com;

    # Immutable, content-hashed assets โ€” let Nginx serve them directly.
    location /_next/static/ {
        alias /var/www/myapp/.next/static/;
        expires 365d;
        add_header Cache-Control "public, max-age=31536000, immutable";
        access_log off;
    }

    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_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;
        proxy_cache_bypass $http_upgrade;
    }
}
Enable the site and reload:
bash
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

Step 7 โ€” Free HTTPS with Let's Encrypt

Certbot edits your Nginx config, installs the certificate and sets up automatic renewal in one command. Renewal runs on a systemd timer, so this is genuinely fire-and-forget.

bash
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

# Confirm auto-renewal works
sudo certbot renew --dry-run

Step 8 โ€” A one-command redeploy

Doing all of the above by hand every time you ship is how mistakes happen. Save this script and your deploys become a single command.

/var/www/myapp/deploy.sh โ€” then `chmod +x deploy.sh`
bash
#!/usr/bin/env bash
set -euo pipefail

cd /var/www/myapp

git pull origin main
npm ci
npm run build

# Standalone output does not include these โ€” copy them every build.
cp -r public .next/standalone/
cp -r .next/static .next/standalone/.next/

pm2 restart myapp --update-env
pm2 save

echo "Deployed at $(date -Iseconds)"

Gotchas worth knowing before they bite

Most Next.js VPS problems come from a short list of surprises. These are the ones that cost real time.

  • ISR and the data cache are written to local disk on the instance. That works perfectly for one server, but if you ever scale to two, they will serve each other stale pages โ€” at that point you need a shared cache handler.
  • `next/image` optimizes at runtime and needs `sharp`, which on glibc-based Linux can use a lot of memory. If RSS creeps up over days, this is the usual culprit.
  • Dynamically rendered pages send `private, no-cache, no-store` automatically. Do not add a caching layer in Nginx that overrides this, or you risk serving one user's page to another.
  • Copy `public` and `.next/static` on every single build, not just the first. A stale copy means your CSS silently belongs to last week's deploy.
  • Check your VPS renewal price before the term ends โ€” the introductory rate is not the long-run rate.

Where the cost lands

One KVM 1 server comfortably runs several Next.js apps side by side โ€” add another PM2 process on port 3001, another Nginx server block, another certificate. That is the real economy of a VPS versus per-service platform pricing: the second app is free.

If you are provisioning one, coupon WEBHOST2026 applies the discount automatically through the links on this site, and longer terms carry the lower monthly rate.

Ready to save with coupon WEBHOST2026?

Get my discount link โ†’

Ready to claim your Hostinger discount?

Generate your personalised cart link with coupon WEBHOST2026 attached โ€” it takes 20 seconds.