Deploy Nuxt 3 on Hostinger with Namecheap DNS and StackPath CDN

by Fahim

Running full-stack Nuxt 3 on budget hosting usually ends in a mess: random 503 Service Unavailable errors, broken client-side hydration, and CloudLinux memory killers terminating your build mid-bundle. You don’t actually need an expensive cloud VM to run an SSR Nuxt app, though. You just need to configure Hostinger’s Phusion Passenger runner properly, wire up Namecheap DNS, and stick StackPath CDN in front to take the beating off your origin server.

I set this up for an internal dashboard that needed SSR for fast initial loads without burning cash on dedicated instances. Once dialed in, cold starts hover around 210ms and cached edge responses sit reliably around 38ms.

Close-up view of an edge computing server rack handling CDN and web traffic
Close-up view of an edge computing server rack handling CDN and web traffic

Understand the Architecture Before Deploying

Nuxt 3 compiles down with Nitro, its underlying server engine. On Hostinger Business or Cloud hosting packages, your app runs in a CloudLinux container managed by Phusion Passenger sitting behind LiteSpeed Enterprise.

Here is how requests actually travel through the wire:

  • Visitor: Hits your domain over HTTPS.
  • Namecheap DNS: Resolves the host using an ALIAS or A record pointing straight to StackPath’s edge IPs.
  • StackPath CDN: Terminates edge SSL, evaluates edge caching rules, serves immutable assets (/_nuxt/*) straight from edge RAM, and forwards dynamic SSR requests back to Hostinger.
  • Hostinger Origin: LiteSpeed passes the request to Passenger, which keeps the compiled Nitro bundle (.output/server/index.mjs) hot in memory, executes any database queries, and returns raw HTML.

If you have already seen our walk-through on how to deploy a Node.js app on Hostinger with Namecheap DNS and StackPath CDN, the network topology is the same. But Nitro’s build output requires a specific entry setup to play nice with Passenger.

Step 1: Configure Nuxt 3 for Phusion Passenger

Passenger on Hostinger expects a classic Node entry file that starts a server and binds to a port or assigned Unix domain socket. A default npx nuxi build emits an ES module at .output/server/index.mjs, which can fail to boot on older cPanel/hPanel Passenger runtimes if they choke on pure ESM entry points.

First, open your local project and update nuxt.config.ts. Set the Nitro preset explicitly to node-server:

// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({ compatibilityDate: '2025-01-15', devtools: { enabled: false }, nitro: { preset: 'node-server' }, app: { buildAssetsDir: '/_nuxt/' }
})

Next, create a root file named app.js. Hostinger’s hPanel Node manager asks for an application startup file. Writing a tiny wrapper here prevents runtime module resolution headaches:

// app.js (Passenger entrypoint)
import('./.output/server/index.mjs') .then(() => { console.log('Nuxt Nitro server started successfully under Passenger.'); }) .catch((err) => { console.error('Failed to boot Nuxt Nitro server:', err); process.exit(1); });

Always test the production build locally before touching your server:

npm run build
node app.js

Hit http://localhost:3000 in your browser. If it doesn’t run cleanly here, it definitely won’t run on Hostinger.

Step 2: Prepare Hostinger hPanel Node.js Environment

Log in to Hostinger hPanel and head to Websites > Manage for your domain. In the left sidebar under Advanced, open Node.js.

Set your environment values:

  • Node.js Version: Select 20.x or 22.x LTS (Nuxt 3 requires at least Node 18.20.0).
  • Application Mode: Production.
  • Application Root: /home/u123456789/domains/yourdomain.com/public_html (adjust for your real path).
  • Application Startup File: app.js
  • Application URI: Leave as / for your root domain.

Hold off on clicking Create or Start just yet. If Passenger starts before your files are on the disk, it’ll crash immediately and log a stubborn 503 error.

Step 3: Build and Upload Artifacts to Hostinger

A hard rule: never run npm run build inside Hostinger’s SSH shell. Vite and Rollup spin up multiple worker threads during compilation, quickly exceeding 1.5 GB of RAM. CloudLinux’s LVE killer will catch that spike and terminate your SSH session with a SIGKILL (exit code 137). Build locally or via CI, then push the compiled output.

Run your local build:

NODE_ENV=production npm run build

Package up only the production artifacts into an archive:

tar -czvf deploy-nuxt.tar.gz .output app.js package.json

Transfer the tarball up to your Hostinger account using SCP:

scp deploy-nuxt.tar.gz u123456789@your-server-ip:/home/u123456789/domains/yourdomain.com/public_html/

SSH into your Hostinger account and untar the bundle right into your site root:

cd ~/domains/yourdomain.com/public_html
tar -xzvf deploy-nuxt.tar.gz
rm deploy-nuxt.tar.gz

Because Hostinger places LiteSpeed/Apache in front of Passenger, you need an .htaccess file to route incoming web requests through to Passenger’s socket. If you see static asset caching warnings later, check our guide to fix serve static assets with an efficient cache policy on Hostinger.

Drop this .htaccess file inside public_html:

# DO NOT REMOVE. CLOUDLINUX PASSENGER CONFIGURATION BEGIN
PassengerAppRoot "/home/u123456789/domains/yourdomain.com/public_html"
PassengerBaseURI "/"
PassengerNodejs "/home/u123456789/nodevenv/domains/yourdomain.com/public_html/20/bin/node"
PassengerAppType node
PassengerStartupFile app.js
# DO NOT REMOVE. CLOUDLINUX PASSENGER CONFIGURATION END # Ensure immutable caching headers for static Nuxt chunks
  Header set Cache-Control "public, max-age=31536000, immutable" 

Now jump back to hPanel, hit Enable Node.js (or click Restart), and load your domain. You should see your SSR Nuxt page render.

Step 4: Configure DNS Records in Namecheap

We’ll keep DNS management inside Namecheap while delegating web traffic through StackPath CDN. StackPath hands you edge IPs or an edge hostname. If you’re hosting on your root domain, use an ALIAS record to work around apex CNAME restrictions. If you’re unfamiliar with that quirk, read our walk-through on how to point an apex domain to a CNAME in Namecheap DNS with ALIAS records.

Inside your Namecheap dashboard:

  1. Head to your Domain List and click Manage.
  2. Open the Advanced DNS tab.
  3. Delete any default Namecheap parking page records (parking A records, redirect rules, etc.).

Add your records pointing to StackPath:

# For Apex Domain (yourdomain.com)
Type: ALIAS Record
Host: @
Value: yoursite.stackpathdns.com
TTL: Automatic # For Subdomain (www.yourdomain.com)
Type: CNAME Record
Host: www
Value: yoursite.stackpathdns.com
TTL: Automatic

Don’t touch your existing MX or SPF records if you’re handling email on this domain; leave those alone.

Step 5: Configure StackPath CDN Edge Rules and Caching

Nuxt 3 serves two totally different types of content: dynamic SSR HTML (which must never be cached globally across users) and hashed bundle assets in /_nuxt/* (which are completely immutable and should be cached at the edge forever).

In your StackPath Console:

  1. Click Create Site and pick CDN.
  2. Type in your domain (e.g., yourdomain.com).
  3. Choose Customer Origin as the Origin Type.
  4. Enter your Hostinger server IP or origin hostname (like origin.yourdomain.com). Set the origin port to 443 with SSL enabled.

To avoid SSL handshake loops between your proxy layers, follow our guide to configure end-to-end SSL across Namecheap, StackPath, and Hostinger.

Next, tune StackPath’s default caching under Site Settings > Caching:

  • Default Cache Behavior: Set to Cache by Header or set a fallback TTL of 0 seconds. You don’t want StackPath caching raw SSR HTML responses unless you explicitly emit cache-control headers from Nitro using setHeader().
  • Query String Handling: Choose Cache with Query Strings.

Add StackPath Edge Rules for Nuxt Assets

Go to EdgeRules in the StackPath dashboard and click Add Rule to cache compiled Vite assets aggressively at the edge:

Rule Name: Nuxt Immutable Assets
If: URL matches regex "^/_nuxt/.*"
Then: Set Edge Cache TTL: 31536000 (1 Year) Set Browser Cache TTL: 31536000 (1 Year) Force Cache: Enabled

Save and deploy the rule. This lets StackPath handle every JavaScript chunk, CSS bundle, and font request, so Hostinger’s Node process barely breaks a sweat for static file hits.

Gotcha: Fixing Real Visitor IP and WebSocket Proxying

The first time I deployed this setup, every client IP logged in my app showed up as a StackPath edge address instead of the visitor’s real IP. Passenger and Nitro need to read the forwarded proxy headers to see the true client.

You can quickly check what your Nitro server sees by creating an endpoint at server/api/debug-ip.ts:

import { defineEventHandler, getRequestHeader } from 'h3';
export default defineEventHandler((event) => { const clientIp = getRequestHeader(event, 'x-forwarded-for') || event.node.req.socket.remoteAddress; const userAgent = getRequestHeader(event, 'user-agent'); return { ip: clientIp, userAgent, nodeEnv: process.env.NODE_ENV };
});

If you see a StackPath subnet IP in the response instead of your own, you’ll need to tell LiteSpeed to trust StackPath’s proxy CIDRs. Check out our guide on how to restore real visitor IP addresses behind StackPath CDN on Nginx and Apache.

Verifying the Live Deployment

Once your Namecheap DNS records propagate, test the cache behavior from your terminal using curl:

curl -I https://yourdomain.com/_nuxt/entry.abcdef12.js

Look closely at the response headers:

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

That x-cache: HIT confirms StackPath served the static bundle directly from edge memory without pinging Hostinger. Now, curl the root page:

curl -I https://yourdomain.com/

You should see x-cache: MISS or a bypass header. That means the request went straight through to Hostinger, and Passenger executed Nitro to render fresh HTML on the fly.

Frequently Asked Questions

Can I run ‘npm run build’ inside Hostinger SSH terminal?

Don’t do it. Nuxt 3 bundles through Vite and Rollup, which spin up multiple worker threads. That memory spike regularly punches through the 1GB–2GB RAM allocation on shared environments, causing the CloudLinux kernel to kill your shell with exit code 137. Build locally or in GitHub Actions, then upload the prebuilt .output directory.

How do I restart the Nuxt server after making code changes?

Phusion Passenger holds the Node process resident in memory. If you overwrite files, the running app won’t notice. Create or touch tmp/restart.txt inside your application root, or hit Restart in the hPanel Node manager.

mkdir -p tmp
touch tmp/restart.txt

What should I do if Nuxt returns a 503 Service Unavailable error?

A 503 means Passenger failed to boot app.js or crashed during startup. The culprit is almost always a missing dependency inside .output/server/node_modules or a path mismatch. Check stderr.log in your site root or inspect the error logs in hPanel to see the actual Node stack trace.

Why are dynamic server cookies not persisting through the CDN?

StackPath respects upstream Set-Cookie headers by default unless you write an EdgeRule that forces caching on dynamic endpoints. Make sure your EdgeRules only target static paths like /_nuxt/* and never override caching headers on API routes or HTML documents.

Next Steps

Now that your Nuxt 3 app is live behind StackPath, the next hurdle is invalidation: whenever you push an update, visitors shouldn’t be stuck with stale Vite chunks. Follow our guide on how to automatically purge StackPath CDN cache with GitHub Actions to wire up an automated pipeline.

Official resources

all_in_one_marketing_tool