Your Node.js app crashes right after deploying to Hostinger because it binds to the wrong internal port or fails to pick up your environment variables. What’s worse, the top search results usually tell you to drop a .env file straight into your web root. Do that, and anyone hitting yourdomain.com/.env can download your database credentials in plain text.
Here’s how to properly deploy a Node.js app on Hostinger using hPanel and SSH, keep your environment variables locked down outside the public web tree, and supervise the process so it doesn’t stay dead after an unexpected crash.

The Problem: Why Node.js Apps Break on Shared and Cloud Hosting
Running Node on an infrastructure originally tailored for PHP comes with two classic traps: dynamic port binding and directory leakage. Hostinger routes incoming web traffic through a reverse proxy (usually LiteSpeed or Nginx) down to an internal socket or dynamic port assigned at launch. If your entry script hardcodes port 3000 or 8080 without listening to Hostinger’s runtime variable, the proxy gives up and throws a 503 Service Unavailable error.
The second trap is directory layout. By default, files inside public_html are served directly as static assets. If you copy-paste or FTP your local workspace straight into that folder, your .env file, node_modules, and server logic are completely exposed to the open internet. Anyone can fetch your database strings and private API tokens directly in their browser.
We solve both issues by storing our application code outside public_html entirely and configuring our entrypoint to read the assigned runtime port properly.
Setting Up the Project Directory via SSH
Head to your Hostinger hPanel, look under the Advanced section, and open SSH Access. Enable it if it’s off, grab your connection command, and jump into your local terminal.
Connect to your hosting account via SSH:
ssh -p 65002 u123456789@your-server-ipSwap in your actual username and server IP from hPanel. Once you’re in, run pwd. You’ll see your home path, usually something like /home/u123456789.
Instead of touching public_html, create a private directory named apps/api inside your home root. This ensures that nothing hits the web unless the reverse proxy explicitly routes it.
Create your project folder and move into it:
mkdir -p ~/apps/api
cd ~/apps/apiIf you haven’t pointed your domain yet, get your DNS records straight before testing live routes. If you keep your domains at another registrar, check our walkthrough on how to point Namecheap DNS to Hostinger without breaking email so your mail records stay functional throughout the switch.
Structuring the Express Server for Production
Start with a lean Express setup to confirm the environment works before migrating heavy database models or business logic. Generate your initial package.json file inside ~/apps/api.
Generate the baseline package manifest:
{ "name": "hostinger-node-api", "version": "1.0.0", "main": "app.js", "scripts": { "start": "node app.js" }, "dependencies": { "dotenv": "^16.4.5", "express": "^4.19.2" }
}Next, build your entrypoint script named app.js. The make-or-break detail here is how you bind the listener. Hostinger passes a dynamic port into your runtime environment, so you have to read process.env.PORT first, using a fallback only for local testing on your laptop.
Write your entrypoint file using this structure:
const express = require('express');
const dotenv = require('dotenv');
const path = require('path'); // Load environment variables from our secure folder
dotenv.config({ path: path.resolve(__dirname, '.env')
}); const app = express();
const PORT = process.env.PORT || 3000;
const APP_ENV = process.env.NODE_ENV || 'production'; app.use(express.json()); app.get('/', (req, res) => { res.json({ status: 'online', environment: APP_ENV, timestamp: new Date().toISOString() });
}); app.get('/health', (req, res) => { res.status(200).send('OK');
}); app.listen(PORT, () => { console.log(`Server listening on port ${PORT} in ${APP_ENV} mode`);
});Notice we resolve the exact path to .env and read process.env.PORT directly. The official Node.js process documentation covers how hosting environments populate these runtime variables when the process spins up.
Locking Down Environment Variables
Put your production secrets into .env inside ~/apps/api/. Because this file sits at /home/u123456789/apps/api/ instead of /home/u123456789/domains/yourdomain.com/public_html/, visitors cannot request it over HTTP—the web server doesn’t even know it exists.
Create your production environment file:
NODE_ENV=production
PORT=3000
APP_SECRET=9f82c3b841e2a05f17d332619
DATABASE_URL=mysql://u123456789_user:SuperSecretPass123@127.0.0.1:3306/u123456789_db
CORS_ORIGIN=https://yourdomain.comLock down the file permissions. Your user account is the only entity that needs read access. Prevent any other accounts or shared server processes from reading your config.
Restrict permissions on the configuration file:
chmod 600 ~/apps/api/.envWith 600 permissions, any process not running under your exact Linux user account will get an immediate permission denied error if it tries to open the file.
Installing Dependencies and Running the First Test
Run npm install with production flags to skip dev tooling. This keeps your inode counts low and saves memory on shared tiers.
Install your production dependencies via SSH:
cd ~/apps/api
npm install --omit=devBefore wiring up hPanel, test the script right in your terminal to catch syntax typos or missing packages immediately:
node app.jsYou should see Server listening on port 3000 in production mode in your terminal. Hit Ctrl + C to stop it. If you see a missing module error, double-check that you ran npm install inside the directory containing package.json.
Configuring the Application in Hostinger hPanel
Hostinger handles Node.js two ways: through the native Node.js module in hPanel (on Cloud and select Shared plans) or via an unmanaged VPS. If you’re setting up a full virtual private server, follow our guide on how to deploy Next.js on Hostinger VPS with PM2 and Nginx. On standard hPanel hosting, use the built-in app launcher:
- Log into hPanel and pick your domain.
- Search for Node.js in the sidebar under Advanced.
- Click Create Application.
- Pick an LTS release for the Node.js version (like
20.xor18.x). - Set Application root to:
apps/api - Set Application startup file to:
app.js - Set Application URL to your domain or subdomain (e.g.,
api.yourdomain.com). - Click Create.
Hostinger configures an internal reverse proxy using LiteSpeed/Passenger to point incoming requests to your script’s internal socket. It also writes a small .htaccess inside your domain’s public_html to rewrite inbound traffic over to that socket automatically.
Managing Processes and Restarts with PM2
Hostinger’s built-in manager brings your app back after a server reboot. But if you want manual control, instant rollouts, and live memory tracking, install PM2 in your user environment.
The official PM2 Quick Start guide recommends installing it via npm to supervise Node processes. Run this in your terminal:
npm install pm2 -gKeep your startup arguments consistent across deployments by using an ecosystem file instead of passing long CLI flags by hand.
Create an ecosystem.config.js file:
module.exports = { apps: [ { name: 'node-api', script: './app.js', instances: 1, autorestart: true, watch: false, max_memory_restart: '250M', env: { NODE_ENV: 'production' } } ]
};Launch your application under PM2:
pm2 start ecosystem.config.js
pm2 saveRunning pm2 save stores the running process list so PM2 can resurrect your app if Hostinger reboots the host machine. You can monitor CPU and memory with pm2 status and watch live logs using pm2 logs node-api.
Fixing 503 Errors, Port Collisions, and CORS
These are the three bugs you’ll run into almost immediately when deploying Node on Hostinger, along with the fixes.
1. The 503 Service Unavailable Error
A 503 error means your app crashed before answering the reverse proxy’s initial health check. Don’t guess—check the error log in your home directory:
tail -n 50 ~/apps/api/stderr.logIf you see Error: Cannot find module 'dotenv', you didn’t run your install inside the production folder. Change into ~/apps/api and run npm install --production.
2. EADDRINUSE Port Collisions
If you tested the script in your terminal using node app.js and left it running while hPanel tried to start its own instance on that port, you’ll see Error: listen EADDRINUSE :::3000. Clear out your user’s running Node processes:
killall -9 nodeThen hit Restart inside the hPanel Node.js dashboard.
3. CORS Header Mismatches Behind Proxies
If your frontend is hosted elsewhere (like Vercel or Netlify) and calls your Hostinger API, browsers will block the response unless your CORS headers match. Use the standard cors package to restrict access according to the MDN CORS specifications.
Install the CORS package:
npm install corsUpdate your app.js with your actual client origins:
const cors = require('cors'); const allowedOrigin = process.env.CORS_ORIGIN || 'https://yourdomain.com'; app.use(cors({ origin: allowedOrigin, methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization']
}));If you have an edge proxy or CDN sitting ahead of Hostinger, your logs might show the CDN’s internal IP instead of real client addresses. If you’re running a custom Nginx stack on a VPS, read how to restore real visitor IPs behind a CDN.
Automating Zero-Downtime Restarts via Git Webhooks
You shouldn’t have to SSH in, run Git commands manually, and restart PM2 by hand every time you ship a fix. Put a simple deploy script right inside your project directory to automate the cycle.
Create a script named deploy.sh inside ~/apps/api:
#!/bin/bash
set -e echo "Deployment started..."
git pull origin main echo "Installing production dependencies..."
npm install --omit=dev echo "Reloading PM2 process..."
pm2 reload ecosystem.config.js --update-env echo "Deployment finished successfully!"Make the deployment script executable:
chmod +x ~/apps/api/deploy.shTrigger the deployment script remotely over SSH whenever you want to release:
ssh -p 65002 u123456789@your-server-ip "bash ~/apps/api/deploy.sh"Because PM2 uses reload instead of restart, it boots your new worker before terminating the old one—zero dropped requests while pushing updates.
Frequently Asked Questions
Can I run Next.js or Nuxt using the hPanel Node.js manager?
Technically yes, but shared hosting limits memory and CPU spikes heavily. For SSR frameworks like Next.js, an unmanaged VPS gives you the memory headroom you actually need. You can check our guide on how to deploy Docker Compose on Hostinger VPS for heavier apps.
Where should I store uploaded files?
Never store user uploads inside apps/api. If you redeploy or wipe the directory with Git, those files are gone. Store public uploads in a dedicated directory like ~/public_html/uploads or offload them directly to an S3-compatible storage bucket.
How do I switch Node.js versions later?
Go to hPanel, open your Node.js app, and select the new runtime version from the dropdown. Once updated, run npm rebuild inside your app directory via SSH to recompile native modules against the new Node binary.
Why are my environment variables returning undefined?
Make sure your script calls dotenv.config() before accessing any keys in process.env. Also, always resolve the path explicitly using path.resolve(__dirname, '.env') so your code finds the file no matter what directory the supervisor launched it from.
Next Steps
With your Node.js application running securely behind Hostinger’s reverse proxy, check our guide on how to point Namecheap DNS to Hostinger to ensure your SSL certificates stay valid and your production API endpoint stays reachable under your primary domain.

