Deploy Full-Stack Next.js App on Hostinger VPS: DNS & CDN

by Fahim

Serverless hosting gets expensive fast once your Next.js app starts querying a database on every SSR request or running long background tasks. I moved my full-stack Next.js production workloads over to a Hostinger VPS with Namecheap DNS and a CDN layer. That dropped my hosting bill from $90/month down to under $8 while keeping response times well under 60ms.

Here is how to set up an Ubuntu-powered Hostinger VPS for Next.js (both App Router and Pages Router). We will configure Node.js 20 LTS, run the app using PM2 and Next’s standalone output, set up Nginx as a reverse proxy with Let’s Encrypt SSL, point Namecheap DNS records, and front static assets with an edge CDN.

Terminal screen showing Next.js deployment commands on a Hostinger VPS with Nginx and PM2
Terminal screen showing Next.js deployment commands on a Hostinger VPS with Nginx and PM2

Why VPS Over Serverless for Full-Stack Next.js

Serverless is great for quick MVPs, but running stateful or database-heavy Next.js apps there hits real friction points:

  • Cold Starts: Pooling database connections (Prisma, Drizzle) inside serverless lambdas adds brutal 800ms to 2.5s latency spikes when idle functions boot up.
  • Execution Time Limits: Webhooks, heavy PDF/CSV exports, AI streaming, and background jobs simply die when they hit platform timeouts.
  • Memory Costs: A standalone Next.js Node instance only takes about 120MB–220MB of RAM under normal load. On a cheap 4GB VPS, you can run multiple apps side-by-side with Redis and Postgres without paying extra.

If you have already seen my guide on how to deploy Node.js on Hostinger VPS, we are using a similar foundation here, but fine-tuned for Next.js standalone builds.

Step 1: Prepare the Hostinger VPS Environment

Start with a fresh Ubuntu 22.04 or 24.04 instance on Hostinger. SSH into the server as root:

ssh root@YOUR_SERVER_IP

Update your packages and create a dedicated deploy user with sudo access so you never run Node apps as root:

apt update && apt upgrade -y
adduser deploy
usermod -aG sudo deploy
ufw allow OpenSSH
ufw enable

Switch over to that user and grab Node.js 20 LTS from NodeSource, along with Git, essential build tools, and PM2:

su - deploy
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs git build-essential nginx
sudo npm install -g pm2

Check that everything installed cleanly:

node -v
npm -v

Step 2: Configure Next.js for Standalone Output

By default, running next build creates a bundle that expects the entire node_modules folder on your server—easily ballooning to hundreds of megabytes. Next’s standalone build mode solves this by bundling only the exact production dependencies required to boot the server.

Open your local project’s next.config.js (or .mjs) and add output: 'standalone' as noted in the official Next.js deployment docs:

/** @type {import('next').NextConfig} */
const nextConfig = { output: 'standalone', reactStrictMode: true, poweredByHeader: false,
}; module.exports = nextConfig;

Commit that and push it up to your repository.

Step 3: Clone, Build, and Run Next.js with PM2

Back on your VPS terminal as your deploy user, create a web directory and clone down your repo:

sudo mkdir -p /var/www/my-next-app
sudo chown -R deploy:deploy /var/www/my-next-app
cd /var/www/my-next-app
git clone https://github.com/your-username/your-repo.git .

Add your production environment variables:

PORT=3000
NODE_ENV=production
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
NEXTAUTH_SECRET=your_long_random_secret_here
NEXTAUTH_URL=https://yourdomain.com

Install dependencies and run your build:

npm ci
npm run build

Because we turned on standalone mode, Next generates a minimal server entry point at .next/standalone/server.js. We just need to copy the static files over so the standalone server can resolve them properly:

cp -r public .next/standalone/
cp -r .next/static .next/standalone/.next/

Create an ecosystem.config.js inside /var/www/my-next-app to let PM2 manage process clustering and restarts, based on the PM2 quick start guide:

module.exports = { apps: [ { name: 'nextjs-app', script: '.next/standalone/server.js', instances: 'max', exec_mode: 'cluster', env: { PORT: 3000, NODE_ENV: 'production' } } ]
};

Start the app and save the process list so PM2 resurrects your app if the VPS restarts:

pm2 start ecosystem.config.js
pm2 save
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u deploy --hp /home/deploy

Verify that the app is listening locally on port 3000:

curl -I http://127.0.0.1:3000

You should get an immediate HTTP/1.1 200 OK response.

Step 4: Configure Nginx Reverse Proxy with Static Asset Caching

Do not expose Node.js directly to the web. Let Nginx handle SSL termination, gzip compression, and static asset serving.

Create a new server block configuration:

sudo nano /etc/nginx/sites-available/yourdomain.com

Paste the following block (remember to change yourdomain.com to your actual domain):

server { listen 80; listen [::]:80; server_name yourdomain.com www.yourdomain.com; # Security and performance headers server_tokens off; gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml; # Cache Next.js immutable static assets directly location /_next/static/ { alias /var/www/my-next-app/.next/static/; expires 365d; access_log off; add_header Cache-Control "public, max-age=31536000, immutable"; } # Pass all other traffic to PM2 Next.js server 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, check the config syntax, and reload Nginx:

sudo ln -s /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

Step 5: Configure Namecheap DNS Records

In your Namecheap dashboard, go to Domain List -> Manage -> Advanced DNS. Add two A records pointing to your Hostinger VPS IP address.

If you want a breakdown on handling root domains versus subdomains, check out our guide to point root domain in Namecheap DNS with A records vs CNAME.

  • Type: A Record | Host: @ | Value: YOUR_VPS_IP_ADDRESS | TTL: Automatic (or 1 min for testing)
  • Type: A Record | Host: www | Value: YOUR_VPS_IP_ADDRESS | TTL: Automatic

Wait a couple of minutes for DNS to propagate, then check it from your terminal:

dig +short yourdomain.com

Once you see your VPS IP returned, grab a free Let’s Encrypt SSL certificate via Certbot:

sudo apt install -y certbot python3-certbot-nginx
sudo ufw allow 'Nginx Full'
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

Certbot will patch your Nginx config to enforce HTTPS and install auto-renewal timers.

Step 6: Add CDN Edge Caching for Static Assets

Nginx handles static assets quickly, but routing them through an edge CDN brings TTFB down worldwide. You can put Cloudflare, Fastly, or StackPath in front of your domain.

When routing through a CDN, make sure original host headers pass through cleanly so Server Actions and dynamic routes work properly. For step-by-step CDN configuration, read our guide on how to connect a Namecheap domain to Hostinger through StackPath CDN.

Verify your caching headers with curl:

curl -I https://yourdomain.com/_next/static/chunks/main-app.js

You should see standard cache hit indicators and immutable headers on static files:

HTTP/2 200
content-type: application/javascript
cache-control: public, max-age=31536000, immutable
server: nginx

For more details on headers and font loading rules, see our reference on CORS and Cache-Control headers for CDN static assets.

The Gotcha I Hit: Missing Static Chunks on App Reload

The first time I deployed a Next.js standalone build on a VPS, the homepage loaded fine, but client-side navigation threw 404 Not Found errors on assets like /_next/static/chunks/app/dashboard/page.js.

Here is why: Next’s standalone mode compiles server.js into .next/standalone/, but it does not automatically copy the .next/static folder or the public/ folder into that directory during next build.

To fix this permanently, I hooked a postbuild script into package.json:

{ "scripts": { "dev": "next dev", "build": "next build && cp -r public .next/standalone/ && cp -r .next/static .next/standalone/.next/", "start": "node .next/standalone/server.js" }
}

Now every time npm run build finishes, the static chunks and public files are copied automatically to the exact paths that standalone server.js and Nginx expect.

Automating Deployments with a Simple Bash Script

Instead of manually typing out git pulls and build steps on every release, save a deploy.sh script in your app root:

#!/bin/bash
set -e echo "Pulling latest code..."
git pull origin main echo "Installing dependencies..."
npm ci echo "Building Next.js application..."
npm run build echo "Reloading PM2 cluster with zero downtime..."
pm2 reload ecosystem.config.js --update-env echo "Deployment completed successfully!"

Make it executable:

chmod +x deploy.sh
./deploy.sh

Now running ./deploy.sh triggers a zero-downtime rolling reload across all worker processes.

Frequently Asked Questions

How much RAM do I need for Next.js on a Hostinger VPS?

A Hostinger KVM 1 plan with 4GB RAM easily handles 3–4 Next.js apps running in standalone mode behind Nginx. Just note that running npm run build on the server briefly spikes memory to around 1.5GB during TypeScript compilation.

Do Next.js Server Actions and SSR work on a VPS?

Yes, completely. Because the app runs in a persistent Node.js environment via PM2, Server Actions, Dynamic SSR, API Routes, and WebSockets work natively with no serverless timeout limits.

How do I handle environment variables when redeploying?

Keep secrets in /var/www/my-next-app/.env.production. When you run pm2 reload ecosystem.config.js --update-env, PM2 pushes those updated variables to the running workers without dropping traffic.

Can I run a PostgreSQL or MySQL database on the same VPS?

Yes. Run sudo apt install postgresql and point your Next.js app to localhost:5432. This keeps database queries sub-millisecond with zero network egress costs.

Next Steps

Your full-stack Next.js app is now running in cluster mode on Hostinger, secured with SSL, routed via Namecheap, and cached at the edge.

If you prefer containerizing your app, Redis, and database together, take a look at our guide on how to deploy a Docker Compose app on Hostinger VPS with Namecheap DNS and SSL.

all_in_one_marketing_tool