Deploying an Express.js application locally is relatively simple, but making it publicly accessible on the internet requires several additional steps.
Your application may work perfectly on:
http://localhost:5000but users on the internet cannot access localhost.
To make an Express.js application publicly accessible from a Hostinger VPS, you need to configure the server, install Node.js, upload your application, configure environment variables, run the application with PM2, configure Nginx as a reverse proxy, open the required firewall ports, connect your domain, and optionally configure SSL.
This guide explains the complete process for deploying an Express.js application on an Ubuntu VPS hosted on Hostinger.
What We Will Build
By the end of this tutorial, the architecture will look like this:
Internet
|
|
Domain / Public IP
|
v
Hostinger VPS
Ubuntu Server
|
Firewall (UFW)
|
Port 80
|
v
Nginx
|
Reverse Proxy
|
v
Express.js Application
Port 5000
|
v
Database / APIsThe user will access:
https://example.comwhile the Express.js application can internally continue running on:
http://127.0.0.1:5000This is an important concept.
You usually do not need to expose the Express.js port directly to the public internet when Nginx is being used as the reverse proxy.
Requirements
Before starting, you should have:
- Hostinger VPS
- Ubuntu server
- SSH access
- Express.js application
- Git repository or application files
- Domain name
- Database if required by your application
- Basic Linux terminal knowledge
You should also know:
- VPS IP address
- SSH username
- Express.js application port
- Git repository URL
- Domain name
For example:
VPS IP:
123.123.123.123
Domain:
api.example.com
Application:
Express.js
Application Port:
5000Step 1: Connect to Your Hostinger VPS
From your local terminal, connect using SSH.
ssh root@YOUR_SERVER_IPFor example:
ssh root@123.123.123.123If you are using another Linux user:
ssh ubuntu@YOUR_SERVER_IPAfter successful authentication, you are connected to your Ubuntu VPS.
Step 2: Update Ubuntu
Always update the package list before installing dependencies.
sudo apt update
sudo apt upgrade -yYou can also install commonly required tools:
sudo apt install -y git curl unzip nginxVerify that Nginx is installed:
nginx -vStep 3: Install Node.js
Express.js requires Node.js.
Check whether Node.js is already installed:
node -vCheck npm:
npm -vIf Node.js is not installed, install a supported LTS version.
One common approach is NodeSource.
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejsVerify:
node -v
npm -vYou should see Node.js and npm version information.
Step 4: Create a Directory for Your Application
A clean server structure makes future maintenance easier.
For example:
mkdir -p ~/apps
cd ~/appsYou can then create a project directory:
mkdir my-express-app
cd my-express-appAlternatively, you can clone the application directly into this directory.
Step 5: Clone Your Express.js Application
If your project is stored on GitHub:
git clone YOUR_REPOSITORY_URL .For example:
git clone https://github.com/example/company-api.git .Then inspect the files:
lsYou might see:
package.json
package-lock.json
src
server.js
.env.exampleStep 6: Install Application Dependencies
Run:
npm installIf the application is strictly a production deployment and the project supports it:
npm ci --omit=devUse the command appropriate for your project.
Step 7: Configure Environment Variables
Production applications should not hardcode credentials.
Your application may require:
PORT=5000
DATABASE_URL=...
JWT_SECRET=...
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...Create your production environment file:
nano .envAdd your required environment variables.
For example:
NODE_ENV=production
PORT=5000
DATABASE_URL=your_database_connection
JWT_SECRET=your_secure_secretSave the file.
Do not commit production .env files to a public Git repository.
Make sure .gitignore contains:
.envStep 8: Check the Express.js Server Configuration
Your Express.js application should listen correctly.
For example:
const express = require("express");
const app = express();
const PORT = process.env.PORT || 5000;
app.get("/", (req, res) => {
res.json({
message: "API is running"
});
});
app.listen(PORT, "127.0.0.1", () => {
console.log(`Server running on port ${PORT}`);
});If Nginx will be the public entry point, binding the application to localhost is often preferable.
The application becomes:
127.0.0.1:5000and Nginx handles public traffic.
Step 9: Test the Express Application Locally on the VPS
Before configuring Nginx, make sure the application itself works.
Start the application:
npm startOr, depending on your project:
node server.jsThen test from another SSH terminal or after starting the process:
curl http://127.0.0.1:5000If your application returns:
{
"message": "API is running"
}your Express.js application is working.
Stop the temporary process after testing.
Step 10: Use PM2 to Keep Express.js Running
You should not run your production server manually with:
node server.jsIf your SSH session closes, the process can stop depending on how it was launched.
Use PM2.
Install PM2:
sudo npm install -g pm2Check:
pm2 -vStep 11: Start Your Express.js Application with PM2
If your entry point is:
server.jsrun:
pm2 start server.js --name my-express-appIf your application uses npm:
pm2 start npm --name my-express-app -- startCheck the process:
pm2 statusYou should see your application listed as online.
Step 12: Check PM2 Logs
If something isn't working, logs are extremely useful.
Run:
pm2 logs my-express-appYou can also view recent logs:
pm2 logs my-express-app --lines 100Check the process details:
pm2 show my-express-appStep 13: Make PM2 Start Automatically After Server Reboot
PM2 should automatically restart your application after a VPS reboot.
Run:
pm2 startupPM2 will provide a command.
Copy and execute the command it provides.
Then save the current process list:
pm2 saveNow your application can automatically restart after a server reboot.
Step 14: Understand the Firewall
Ubuntu commonly uses UFW (Uncomplicated Firewall).
Check its status:
sudo ufw statusIf it is enabled, you need to allow the ports required for server access.
At minimum, SSH must be allowed before enabling the firewall.
For SSH:
sudo ufw allow OpenSSHFor HTTP:
sudo ufw allow 80/tcpFor HTTPS:
sudo ufw allow 443/tcpThen enable the firewall:
sudo ufw enableCheck:
sudo ufw statusYou should see rules similar to:
22/tcp
80/tcp
443/tcpDo You Need to Open the Express.js Port?
This is a common question.
Suppose Express runs on:
5000You might think you need:
sudo ufw allow 5000/tcpUsually, you don't need to do this when using Nginx as a reverse proxy.
The architecture should be:
Internet
|
v
Port 80 / 443
|
v
Nginx
|
v
127.0.0.1:5000
|
v
Express.jsPort 5000 can remain private.
This is generally preferable because users only interact with Nginx.
Step 15: Hostinger Firewall Configuration
There can be more than one firewall layer.
Your server may have:
Internet
↓
Hostinger / VPS firewall
↓
Ubuntu UFW
↓
Nginx
↓
ExpressIf port 80 or 443 is blocked at the Hostinger infrastructure level, opening it only in UFW won't make the application accessible.
Therefore, check the VPS firewall/network settings in your Hostinger control panel.
Make sure the required ports are allowed:
22 SSH
80 HTTP
443 HTTPSIf you intentionally want to expose another service directly, that service's port must also be allowed at the relevant firewall layers.
For a standard Nginx deployment, however, public access normally only requires HTTP/HTTPS.
Step 16: Configure Nginx
Now we need to tell Nginx:
When a user visits my domain, forward the request to my Express.js application.
Create an Nginx configuration:
sudo nano /etc/nginx/sites-available/my-express-appAdd:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
location / {
proxy_pass http://127.0.0.1:5000;
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;
}
}Replace:
example.comwith your actual domain.
Replace:
5000with the port used by your Express application.
Step 17: Enable the Nginx Configuration
Create a symbolic link:
sudo ln -s /etc/nginx/sites-available/my-express-app /etc/nginx/sites-enabled/Test Nginx configuration:
sudo nginx -tYou should see something similar to:
syntax is ok
test is successfulThen restart Nginx:
sudo systemctl restart nginxCheck its status:
sudo systemctl status nginxStep 18: Connect Your Domain to the VPS
Now your domain needs to point to the VPS.
Open your domain's DNS management.
Create an A record:
Type: A
Name: @
Value: YOUR_VPS_IP
TTL: DefaultFor example:
A @ 123.123.123.123For www:
A www 123.123.123.123If you are hosting an API on a subdomain:
A api 123.123.123.123Then your API can be accessed through:
https://api.example.comDNS changes may take some time to propagate.
You can check DNS from your local system:
nslookup example.comor:
dig example.comStep 19: Test the Public URL
After DNS propagation, open:
http://example.comThe request flow should be:
Browser
↓
example.com
↓
VPS IP
↓
Port 80
↓
Nginx
↓
127.0.0.1:5000
↓
Express.jsIf everything is configured correctly, your Express.js application should now be publicly accessible.
Step 20: Add HTTPS with SSL
Running the application over plain HTTP is not recommended for production.
Use HTTPS.
A common approach on Ubuntu is Let's Encrypt with Certbot.
Install Certbot:
sudo apt install -y certbot python3-certbot-nginxRun:
sudo certbot --nginx -d example.com -d www.example.comFor an API subdomain:
sudo certbot --nginx -d api.example.comCertbot can configure the Nginx HTTPS settings and certificate.
After successful setup, your application becomes:
https://example.comor:
https://api.example.comStep 21: Test SSL Renewal
Let's Encrypt certificates are short-lived and need renewal.
Check:
sudo certbot renew --dry-runIf the dry run succeeds, automatic renewal should be configured correctly.
Step 22: Verify All Services
Check PM2:
pm2 statusCheck Nginx:
sudo systemctl status nginxCheck firewall:
sudo ufw statusCheck the application:
curl http://127.0.0.1:5000Check the public endpoint:
curl https://example.comThis gives you a complete view of the deployment.
Common Deployment Architecture
A typical Express.js production server can look like this:
INTERNET
|
|
Domain Name
|
v
Public VPS IP
|
v
Hostinger Firewall
|
v
Ubuntu UFW
/ \
22 80/443
| |
SSH Nginx
|
|
Reverse Proxy
|
v
Express.js / Node
127.0.0.1
Port 5000
|
+-----------+-----------+
| |
PostgreSQL Redis
|
Application DataThis architecture is simple, secure, and suitable for many small-to-medium applications.
What If the Public URL Doesn't Work?
If your application works locally but not publicly, troubleshoot layer by layer.
Check 1: Is Express running?
pm2 statusThen:
curl http://127.0.0.1:5000If this fails, the problem is inside your Express application.
Check 2: Check PM2 logs
pm2 logsLook for:
- Port errors
- Database errors
- Missing environment variables
- Module errors
- Authentication errors
- Application crashes
Check 3: Check Nginx
sudo nginx -tThen:
sudo systemctl status nginxCheck Nginx logs:
sudo tail -f /var/log/nginx/error.logAccess logs:
sudo tail -f /var/log/nginx/access.logCheck 4: Check whether the port is listening
Run:
sudo ss -tulpnYou may see:
127.0.0.1:5000
0.0.0.0:80
0.0.0.0:443This tells you which services are listening.
Check 5: Check UFW
sudo ufw statusMake sure:
22
80
443are allowed as required.
Check 6: Check Hostinger Firewall
If Ubuntu allows the connection but the public URL still does not work, check your VPS firewall/network rules in the Hostinger control panel.
Check 7: Check DNS
Verify that the domain resolves to the correct VPS IP:
dig example.comIf the IP is wrong, fix your DNS records.
Common Mistakes
Mistake 1: Running Express with `node server.js`
This is not ideal for production process management.
Use PM2 instead.
Mistake 2: Opening Every Port
Do not blindly open:
3000
4000
5000
8000
8080
9000Only expose ports that are genuinely required.
With Nginx, the Express port can usually remain private.
Mistake 3: Forgetting SSH Before Enabling UFW
If SSH is blocked, you may lose access to the server.
Always ensure SSH access is allowed before enabling UFW:
sudo ufw allow OpenSSHMistake 4: Forgetting Nginx Configuration
Running Express does not automatically make it available through your domain.
You need the reverse proxy:
Domain
↓
Nginx
↓
ExpressMistake 5: Incorrect `server_name`
Make sure Nginx contains the correct domain:
server_name api.example.com;Mistake 6: Domain DNS Is Pointing Somewhere Else
Your A record must point to the correct VPS IP.
Mistake 7: Missing Environment Variables
An application may work locally because your local .env exists but fail on the VPS because production variables were never configured.
Mistake 8: Application Is Listening on the Wrong Port
If Express runs on:
5000but Nginx points to:
3000you will get a connection failure.
Both must match.
Recommended Production Folder Structure
A clean VPS structure might look like:
/home/ubuntu/
│
├── apps/
│ ├── my-api/
│ │ ├── src/
│ │ ├── package.json
│ │ ├── package-lock.json
│ │ └── .env
│ │
│ └── another-api/
│
└── backups/Nginx configurations can remain under:
/etc/nginx/Application logs can be managed through PM2.
Useful Commands Cheat Sheet
Server
sudo apt update
sudo apt upgrade -yNode
node -v
npm -vApplication
npm install
npm startPM2
pm2 start server.js --name my-api
pm2 status
pm2 logs my-api
pm2 restart my-api
pm2 stop my-api
pm2 delete my-api
pm2 saveNginx
sudo nginx -t
sudo systemctl restart nginx
sudo systemctl status nginxFirewall
sudo ufw status
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcpLocal API test
curl http://127.0.0.1:5000Listening ports
sudo ss -tulpnSSL
sudo certbot renew --dry-runProduction Deployment Checklist
Before considering your Express.js deployment complete:
VPS
- [ ] Ubuntu installed
- [ ] SSH access working
- [ ] System packages updated
- [ ] Node.js installed
- [ ] Git installed
Application
- [ ] Repository cloned
- [ ] Dependencies installed
- [ ] Production environment variables configured
- [ ] Database connection tested
- [ ] Application tested locally on VPS
Process Management
- [ ] PM2 installed
- [ ] Application running with PM2
- [ ] PM2 startup configured
- [ ] PM2 process saved
Firewall
- [ ] SSH allowed
- [ ] HTTP allowed
- [ ] HTTPS allowed
- [ ] Unnecessary ports closed
- [ ] Hostinger firewall checked
Nginx
- [ ] Nginx installed
- [ ] Reverse proxy configured
- [ ]
nginx -tsuccessful - [ ] Nginx restarted
- [ ] Application reachable through Nginx
Domain
- [ ] A record configured
- [ ] DNS points to VPS
- [ ] Domain resolves correctly
SSL
- [ ] HTTPS configured
- [ ] SSL certificate installed
- [ ] HTTP redirects to HTTPS
- [ ] Certificate renewal tested
Security
- [ ] Production secrets not committed
- [ ] Database not unnecessarily exposed
- [ ] Express port not publicly exposed when Nginx is used
- [ ] Firewall configured
- [ ] Strong SSH authentication configured
- [ ] Application dependencies reviewed
- [ ] Backups configured
Final Deployment Flow
The entire process can be summarized as:
Express.js Application
↓
Git Repository
↓
Hostinger Ubuntu VPS
↓
Install Node.js
↓
Clone Application
↓
npm install
↓
Configure .env
↓
Test Application
↓
PM2
↓
Express runs internally
↓
Configure UFW
↓
Open 22 / 80 / 443
↓
Configure Hostinger Firewall
↓
Nginx
↓
Reverse Proxy
↓
Domain DNS
↓
VPS IP
↓
SSL / HTTPS
↓
PUBLIC PRODUCTION APPLICATIONConclusion
Deploying an Express.js application on a Hostinger Ubuntu VPS involves more than simply running npm start.
A reliable deployment requires several layers working together:
Express.js handles the application logic.
Node.js provides the runtime.
PM2 keeps the application running and restarts it when necessary.
Nginx acts as the reverse proxy and public web server.
UFW controls the Ubuntu server firewall.
Hostinger firewall/network rules control another layer of network access where applicable.
DNS connects your domain to the VPS.
SSL/HTTPS secures communication between users and the server.
When these components are configured correctly, your Express.js application can run privately on an internal port such as 5000 while users access it securely through:
https://yourdomain.comThis approach provides a strong foundation for hosting Node.js APIs, REST APIs, SaaS backends, CRM systems, ERP applications, admin panels, and other web applications on an Ubuntu VPS.
If you are deploying an AI-assisted or Vibe-Coded backend, also read our Vibe Coding complete guide and explore our cloud services for production infrastructure support.