Wansify
Guide

How to Deploy an Express.js Application on a Hostinger VPS with Ubuntu, PM2, Nginx, Firewall and a Public URL

Learn how to deploy an Express.js application on a Hostinger Ubuntu VPS using Node.js, PM2, Nginx, UFW firewall, DNS, public IP and SSL HTTPS.

August 5, 202612 min readBy Wansify
Express.jsHostinger VPSNode.jsNginxPM2SSL

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:

text
http://localhost:5000

but 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:

text
                    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 / APIs

The user will access:

text
https://example.com

while the Express.js application can internally continue running on:

text
http://127.0.0.1:5000

This 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:

text
VPS IP:
123.123.123.123

Domain:
api.example.com

Application:
Express.js

Application Port:
5000

Step 1: Connect to Your Hostinger VPS

From your local terminal, connect using SSH.

bash
ssh root@YOUR_SERVER_IP

For example:

bash
ssh root@123.123.123.123

If you are using another Linux user:

bash
ssh ubuntu@YOUR_SERVER_IP

After successful authentication, you are connected to your Ubuntu VPS.


Step 2: Update Ubuntu

Always update the package list before installing dependencies.

bash
sudo apt update
sudo apt upgrade -y

You can also install commonly required tools:

bash
sudo apt install -y git curl unzip nginx

Verify that Nginx is installed:

bash
nginx -v

Step 3: Install Node.js

Express.js requires Node.js.

Check whether Node.js is already installed:

bash
node -v

Check npm:

bash
npm -v

If Node.js is not installed, install a supported LTS version.

One common approach is NodeSource.

bash
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejs

Verify:

bash
node -v
npm -v

You 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:

bash
mkdir -p ~/apps
cd ~/apps

You can then create a project directory:

bash
mkdir my-express-app
cd my-express-app

Alternatively, you can clone the application directly into this directory.


Step 5: Clone Your Express.js Application

If your project is stored on GitHub:

bash
git clone YOUR_REPOSITORY_URL .

For example:

bash
git clone https://github.com/example/company-api.git .

Then inspect the files:

bash
ls

You might see:

text
package.json
package-lock.json
src
server.js
.env.example

Step 6: Install Application Dependencies

Run:

bash
npm install

If the application is strictly a production deployment and the project supports it:

bash
npm ci --omit=dev

Use the command appropriate for your project.


Step 7: Configure Environment Variables

Production applications should not hardcode credentials.

Your application may require:

text
PORT=5000
DATABASE_URL=...
JWT_SECRET=...
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...

Create your production environment file:

bash
nano .env

Add your required environment variables.

For example:

env
NODE_ENV=production
PORT=5000
DATABASE_URL=your_database_connection
JWT_SECRET=your_secure_secret

Save the file.

Do not commit production .env files to a public Git repository.

Make sure .gitignore contains:

text
.env

Step 8: Check the Express.js Server Configuration

Your Express.js application should listen correctly.

For example:

javascript
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:

text
127.0.0.1:5000

and 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:

bash
npm start

Or, depending on your project:

bash
node server.js

Then test from another SSH terminal or after starting the process:

bash
curl http://127.0.0.1:5000

If your application returns:

json
{
  "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:

bash
node server.js

If your SSH session closes, the process can stop depending on how it was launched.

Use PM2.

Install PM2:

bash
sudo npm install -g pm2

Check:

bash
pm2 -v

Step 11: Start Your Express.js Application with PM2

If your entry point is:

text
server.js

run:

bash
pm2 start server.js --name my-express-app

If your application uses npm:

bash
pm2 start npm --name my-express-app -- start

Check the process:

bash
pm2 status

You should see your application listed as online.


Step 12: Check PM2 Logs

If something isn't working, logs are extremely useful.

Run:

bash
pm2 logs my-express-app

You can also view recent logs:

bash
pm2 logs my-express-app --lines 100

Check the process details:

bash
pm2 show my-express-app

Step 13: Make PM2 Start Automatically After Server Reboot

PM2 should automatically restart your application after a VPS reboot.

Run:

bash
pm2 startup

PM2 will provide a command.

Copy and execute the command it provides.

Then save the current process list:

bash
pm2 save

Now your application can automatically restart after a server reboot.


Step 14: Understand the Firewall

Ubuntu commonly uses UFW (Uncomplicated Firewall).

Check its status:

bash
sudo ufw status

If 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:

bash
sudo ufw allow OpenSSH

For HTTP:

bash
sudo ufw allow 80/tcp

For HTTPS:

bash
sudo ufw allow 443/tcp

Then enable the firewall:

bash
sudo ufw enable

Check:

bash
sudo ufw status

You should see rules similar to:

text
22/tcp
80/tcp
443/tcp

Do You Need to Open the Express.js Port?

This is a common question.

Suppose Express runs on:

text
5000

You might think you need:

bash
sudo ufw allow 5000/tcp

Usually, you don't need to do this when using Nginx as a reverse proxy.

The architecture should be:

text
Internet
   |
   v
Port 80 / 443
   |
   v
Nginx
   |
   v
127.0.0.1:5000
   |
   v
Express.js

Port 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:

text
Internet
   ↓
Hostinger / VPS firewall
   ↓
Ubuntu UFW
   ↓
Nginx
   ↓
Express

If 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:

text
22    SSH
80    HTTP
443   HTTPS

If 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:

bash
sudo nano /etc/nginx/sites-available/my-express-app

Add:

nginx
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:

text
example.com

with your actual domain.

Replace:

text
5000

with the port used by your Express application.


Step 17: Enable the Nginx Configuration

Create a symbolic link:

bash
sudo ln -s /etc/nginx/sites-available/my-express-app /etc/nginx/sites-enabled/

Test Nginx configuration:

bash
sudo nginx -t

You should see something similar to:

text
syntax is ok
test is successful

Then restart Nginx:

bash
sudo systemctl restart nginx

Check its status:

bash
sudo systemctl status nginx

Step 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:

text
Type: A
Name: @
Value: YOUR_VPS_IP
TTL: Default

For example:

text
A    @    123.123.123.123

For www:

text
A    www    123.123.123.123

If you are hosting an API on a subdomain:

text
A    api    123.123.123.123

Then your API can be accessed through:

text
https://api.example.com

DNS changes may take some time to propagate.

You can check DNS from your local system:

bash
nslookup example.com

or:

bash
dig example.com

Step 19: Test the Public URL

After DNS propagation, open:

text
http://example.com

The request flow should be:

text
Browser
   ↓
example.com
   ↓
VPS IP
   ↓
Port 80
   ↓
Nginx
   ↓
127.0.0.1:5000
   ↓
Express.js

If 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:

bash
sudo apt install -y certbot python3-certbot-nginx

Run:

bash
sudo certbot --nginx -d example.com -d www.example.com

For an API subdomain:

bash
sudo certbot --nginx -d api.example.com

Certbot can configure the Nginx HTTPS settings and certificate.

After successful setup, your application becomes:

text
https://example.com

or:

text
https://api.example.com

Step 21: Test SSL Renewal

Let's Encrypt certificates are short-lived and need renewal.

Check:

bash
sudo certbot renew --dry-run

If the dry run succeeds, automatic renewal should be configured correctly.


Step 22: Verify All Services

Check PM2:

bash
pm2 status

Check Nginx:

bash
sudo systemctl status nginx

Check firewall:

bash
sudo ufw status

Check the application:

bash
curl http://127.0.0.1:5000

Check the public endpoint:

bash
curl https://example.com

This gives you a complete view of the deployment.


Common Deployment Architecture

A typical Express.js production server can look like this:

text
                     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 Data

This 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?

bash
pm2 status

Then:

bash
curl http://127.0.0.1:5000

If this fails, the problem is inside your Express application.


Check 2: Check PM2 logs

bash
pm2 logs

Look for:

  • Port errors
  • Database errors
  • Missing environment variables
  • Module errors
  • Authentication errors
  • Application crashes

Check 3: Check Nginx

bash
sudo nginx -t

Then:

bash
sudo systemctl status nginx

Check Nginx logs:

bash
sudo tail -f /var/log/nginx/error.log

Access logs:

bash
sudo tail -f /var/log/nginx/access.log

Check 4: Check whether the port is listening

Run:

bash
sudo ss -tulpn

You may see:

text
127.0.0.1:5000
0.0.0.0:80
0.0.0.0:443

This tells you which services are listening.


Check 5: Check UFW

bash
sudo ufw status

Make sure:

text
22
80
443

are 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:

bash
dig example.com

If 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:

text
3000
4000
5000
8000
8080
9000

Only 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:

bash
sudo ufw allow OpenSSH

Mistake 4: Forgetting Nginx Configuration

Running Express does not automatically make it available through your domain.

You need the reverse proxy:

text
Domain
 ↓
Nginx
 ↓
Express

Mistake 5: Incorrect `server_name`

Make sure Nginx contains the correct domain:

nginx
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:

text
5000

but Nginx points to:

text
3000

you will get a connection failure.

Both must match.


A clean VPS structure might look like:

text
/home/ubuntu/
│
├── apps/
│   ├── my-api/
│   │   ├── src/
│   │   ├── package.json
│   │   ├── package-lock.json
│   │   └── .env
│   │
│   └── another-api/
│
└── backups/

Nginx configurations can remain under:

text
/etc/nginx/

Application logs can be managed through PM2.


Useful Commands Cheat Sheet

Server

bash
sudo apt update
sudo apt upgrade -y

Node

bash
node -v
npm -v

Application

bash
npm install
npm start

PM2

bash
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 save

Nginx

bash
sudo nginx -t
sudo systemctl restart nginx
sudo systemctl status nginx

Firewall

bash
sudo ufw status
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

Local API test

bash
curl http://127.0.0.1:5000

Listening ports

bash
sudo ss -tulpn

SSL

bash
sudo certbot renew --dry-run

Production 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 -t successful
  • [ ] 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:

text
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 APPLICATION

Conclusion

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:

text
https://yourdomain.com

This 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.

Related reading

Continue with related deployment and production guides. Linked titles are available now; others are planned next.

Next step

Need help deploying your Node.js API?

We help teams deploy Express.js and Node.js applications on Ubuntu VPS with PM2, Nginx, firewall hardening, DNS, and SSL — production-ready.