• Home
  • Cloud VPS
    • Hong Kong VPS
    • US VPS
  • Dedicated Servers
    • Hong Kong Servers
    • US Servers
    • Singapore Servers
    • Japan Servers
  • Company
    • Contact Us
    • Blog
logo logo
  • Home
  • Cloud VPS
    • Hong Kong VPS
    • US VPS
  • Dedicated Servers
    • Hong Kong Servers
    • US Servers
    • Singapore Servers
    • Japan Servers
  • Company
    • Contact Us
    • Blog
ENEN
  • 简体简体
  • 繁體繁體
Client Area

How to Deploy a Node.js Application on Hong Kong VPS: Complete Guide

March 22, 2026

Deploying a Node.js application on a Hong Kong VPS gives you full control over your runtime environment, direct access to CN2 GIA network routing for China-facing APIs, and the flexibility to run any framework — Express, Fastify, NestJS, Next.js, or a custom HTTP server — without the constraints of managed platforms.

This guide covers the complete deployment pipeline from a fresh VPS to a production-ready Node.js application: installing Node.js, configuring PM2 for process management, setting up Nginx as a reverse proxy, enabling HTTPS with Let’s Encrypt, and tuning performance for Asia-Pacific traffic.


Prerequisites

  • A Hong Kong VPS running Ubuntu 22.04 LTS (recommended) with at least 1 vCPU and 1 GB RAM
  • Root or sudo SSH access
  • A domain name with DNS A record pointing to your VPS IP
  • Your Node.js application code (locally or in a Git repository)
  • Basic familiarity with the Linux command line

Step 1: Initial Server Setup

Connect to your VPS and perform initial hardening before installing any application software:

ssh root@YOUR_VPS_IP

Update the system:

apt update && apt upgrade -y

Create a non-root user for running your application (running Node.js as root is a security risk):

adduser deploy
usermod -aG sudo deploy

Copy your SSH key to the new user:

rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy

Switch to the deploy user for all subsequent steps:

su - deploy

Step 2: Install Node.js via NVM

The recommended way to install Node.js on a VPS is via NVM (Node Version Manager) — it allows you to install multiple Node.js versions, switch between them instantly, and upgrade without affecting running applications. Avoid installing Node.js from Ubuntu’s default apt repository, which typically ships outdated versions.

Install NVM:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

Reload your shell to activate NVM:

source ~/.bashrc

Verify NVM is installed:

nvm --version

Install the latest LTS version of Node.js (recommended for production):

nvm install --lts
nvm use --lts
nvm alias default node

Verify the installation:

node --version
npm --version

You should see output similar to v20.x.x for Node.js and 10.x.x for npm.


Step 3: Deploy Your Application Code

Create a directory for your application:

mkdir -p /home/deploy/apps/myapp
cd /home/deploy/apps/myapp

Option A: Clone from Git

git clone https://github.com/yourusername/your-repo.git .
npm install --production

Option B: Transfer files via SCP

From your local machine:

scp -r ./your-app-folder deploy@YOUR_VPS_IP:/home/deploy/apps/myapp

Then on the server:

cd /home/deploy/apps/myapp
npm install --production

Configure environment variables

Never hardcode sensitive values (database credentials, API keys, JWT secrets) in your source code. Create a .env file:

nano /home/deploy/apps/myapp/.env

Add your environment variables:

NODE_ENV=production
PORT=3000
DB_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=myapp_db
DB_USER=myapp_user
DB_PASS=your_secure_password
JWT_SECRET=your_jwt_secret_here

Restrict permissions on the .env file:

chmod 600 /home/deploy/apps/myapp/.env

Step 4: Test Your Application Runs

Before setting up PM2, verify your application starts correctly:

cd /home/deploy/apps/myapp
node index.js

Replace index.js with your actual entry point file. You should see your application’s startup log output. Test it responds correctly by opening a second SSH session and running:

curl http://127.0.0.1:3000

If you get a valid response, your application is working. Press Ctrl+C to stop it — you will hand off process management to PM2 in the next step.


Step 5: Install and Configure PM2

PM2 is the industry-standard process manager for Node.js in production. It handles automatic restarts on crash, log management, cluster mode for multi-core utilisation, and startup configuration so your app survives server reboots.

Install PM2 globally:

npm install -g pm2

Start your application with PM2:

cd /home/deploy/apps/myapp
pm2 start index.js --name "myapp"

For better production configuration, create a PM2 ecosystem file instead of inline flags:

nano /home/deploy/apps/myapp/ecosystem.config.js

Add the following configuration:

module.exports = {
  apps: [
    {
      name: 'myapp',
      script: './index.js',
      instances: 'max',        // Use all available CPU cores
      exec_mode: 'cluster',    // Enable cluster mode for multi-core
      watch: false,            // Disable in production
      max_memory_restart: '500M',
      env: {
        NODE_ENV: 'production',
        PORT: 3000
      },
      error_file: '/home/deploy/logs/myapp-error.log',
      out_file: '/home/deploy/logs/myapp-out.log',
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      merge_logs: true
    }
  ]
};

Create the logs directory and start with the ecosystem config:

mkdir -p /home/deploy/logs
pm2 start ecosystem.config.js

Configure PM2 to start automatically on system reboot:

pm2 startup systemd -u deploy --hp /home/deploy

PM2 will output a command to run as root. Copy and execute it (it will look like sudo env PATH=... pm2 startup ...). Then save the current process list:

pm2 save

Useful PM2 commands

# View running processes
pm2 list

# View real-time logs
pm2 logs myapp

# Restart application
pm2 restart myapp

# Reload with zero downtime (for cluster mode)
pm2 reload myapp

# Monitor CPU and memory usage
pm2 monit

Step 6: Install and Configure Nginx as a Reverse Proxy

Node.js should not be exposed directly to the internet on port 80/443. Instead, Nginx acts as a reverse proxy — handling SSL termination, serving static files, managing connections efficiently, and forwarding dynamic requests to your Node.js process on port 3000.

Install Nginx:

sudo apt install nginx -y
sudo systemctl enable nginx
sudo systemctl start nginx

Create a new Nginx server block for your application:

sudo nano /etc/nginx/sites-available/myapp

Add the following configuration (replace yourdomain.com with your actual domain):

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;

    # Redirect HTTP to HTTPS
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name yourdomain.com www.yourdomain.com;

    # SSL certificates (Let's Encrypt - configured in Step 7)
    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    # SSL hardening
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # Serve static files directly from Nginx (bypass Node.js)
    location /static/ {
        alias /home/deploy/apps/myapp/public/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # Proxy all other requests to Node.js
    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;

        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
}

Enable the site and test the configuration:

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t

You should see syntax is ok and test is successful. If not, check the error message and fix the configuration before proceeding.


Step 7: Install SSL Certificate with Certbot

Install Certbot and the Nginx plugin:

sudo apt install certbot python3-certbot-nginx -y

Obtain and install your SSL certificate:

sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

Certbot will:

  1. Verify domain ownership via HTTP challenge
  2. Obtain the certificate from Let’s Encrypt
  3. Automatically update your Nginx configuration to reference the certificate files
  4. Set up automatic renewal via a systemd timer

Verify auto-renewal is configured correctly:

sudo certbot renew --dry-run

Now reload Nginx to apply all changes:

sudo systemctl reload nginx

Your application should now be accessible at https://yourdomain.com with a valid SSL certificate.


Step 8: Configure UFW Firewall

Lock down your server to only the ports your application needs:

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status

The output should show rules allowing SSH (port 22) and Nginx Full (ports 80 and 443). Port 3000 (Node.js) should not appear in the allowed list — it is only accessible internally via Nginx proxy.


Step 9: Set Up a Deployment Workflow

For ongoing deployments, manual file transfers become impractical. A simple but effective deployment script handles the process consistently:

nano /home/deploy/deploy.sh
#!/bin/bash
set -e

APP_DIR="/home/deploy/apps/myapp"
echo "==> Pulling latest code..."
cd $APP_DIR
git pull origin main

echo "==> Installing dependencies..."
npm install --production

echo "==> Reloading application (zero downtime)..."
pm2 reload myapp

echo "==> Deployment complete."
pm2 list
chmod +x /home/deploy/deploy.sh

Run future deployments with a single command:

./deploy.sh

PM2’s reload command (used instead of restart) performs a zero-downtime reload in cluster mode — new requests are routed to fresh workers while old workers finish processing in-flight requests before shutting down.


Step 10: Performance Tuning for Asia-Pacific Traffic

Increase Node.js heap size for large applications

Update your PM2 ecosystem config to allocate more V8 heap memory if your application handles large data sets:

node_args: '--max-old-space-size=512'

Enable Nginx worker process auto-scaling

Edit /etc/nginx/nginx.conf and set:

worker_processes auto;
worker_connections 2048;

Tune TCP settings for high-concurrency

Add to /etc/sysctl.conf:

net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_tw_reuse = 1

Apply changes:

sudo sysctl -p

WebSocket support

The Nginx configuration in Step 6 already includes WebSocket upgrade headers (proxy_set_header Upgrade and Connection 'upgrade'). Socket.io and native WebSocket connections will work without additional configuration.


Verifying Your Deployment

Run through this checklist after every fresh deployment:

  • HTTPS works: curl -I https://yourdomain.com returns 200 OK with Server: nginx
  • HTTP redirects to HTTPS: curl -I http://yourdomain.com returns 301 Moved Permanently
  • PM2 process is running: pm2 list shows your app with status online
  • Logs are clean: pm2 logs myapp --lines 50 shows no error stack traces
  • Firewall is active: sudo ufw status shows only SSH and Nginx rules
  • Port 3000 is not externally accessible: From your local machine, curl http://YOUR_VPS_IP:3000 should time out or be refused

Conclusion

You now have a production-grade Node.js deployment on a Hong Kong VPS with:

  • NVM-managed Node.js LTS for easy version control
  • PM2 cluster mode for multi-core utilisation and automatic restarts
  • Nginx reverse proxy with SSL termination and static file serving
  • Let’s Encrypt HTTPS with automatic certificate renewal
  • UFW firewall with minimal attack surface
  • Zero-downtime deployment script for ongoing updates
  • CN2 GIA network routing for consistent low-latency access from China and Asia-Pacific

This stack handles everything from a lightweight REST API to a full-stack Next.js application — and scales vertically by upgrading your VPS plan as traffic grows.

Looking for a Hong Kong VPS optimised for Node.js deployments? Server.HK’s plans include KVM virtualisation, NVMe SSD storage, and CN2 GIA routing as standard — the right foundation for any production Node.js application serving Asia-Pacific users.


Frequently Asked Questions

Should I run Node.js as root on a Hong Kong VPS?

No. Running Node.js as root is a significant security risk — a vulnerability in your application or its dependencies could give an attacker full system access. Always run Node.js as a non-root user (as created in Step 1) and use Nginx on port 80/443 to proxy requests to your application on an unprivileged port.

How many Node.js instances should I run with PM2 cluster mode?

PM2’s instances: 'max' setting automatically spawns one worker per available vCPU, which is the correct default for most applications. For CPU-bound workloads, this maximises throughput. For I/O-bound applications (most REST APIs), the difference between max instances and a fixed number is smaller — but max is still a safe default.

Can I run multiple Node.js applications on one Hong Kong VPS?

Yes. Each application runs on a different internal port (3000, 3001, 3002 etc.), and Nginx routes traffic to the correct application based on the domain name in the request. PM2 manages all processes in a single dashboard. A 2 GB RAM VPS can comfortably run 3–5 small Node.js applications simultaneously.

How do I update Node.js to a newer version without downtime?

With NVM, install the new version alongside the existing one, test your application against it, then switch the default: nvm install 22 && nvm alias default 22. Update your PM2 startup script to use the new version and perform a pm2 reload for zero-downtime version switching.

Does this setup support WebSockets for real-time applications?

Yes. The Nginx configuration in Step 6 includes the necessary Upgrade and Connection proxy headers for WebSocket support. Socket.io, native WebSocket (ws library), and server-sent events all work with this configuration without modification.

Tags: Hong Kong VPSnginx reverse proxyNode.js deploymentNode.js VPSPM2SSL certificateVPS for developers

Leave a Reply

You must be logged in to post a comment.

Recent Posts

  • How to Deploy a Node.js Application on Hong Kong VPS: Complete Guide
  • How to Set Up a WordPress Site on a Hong Kong VPS with aaPanel (Step-by-Step 2026)
  • How to Choose the Right Hong Kong VPS Plan: A Buyer’s Guide for 2026
  • CN2 GIA vs BGP vs CN2 GT: What’s the Real Difference for China Connectivity?
  • Top 5 Use Cases for a Hong Kong Dedicated Server in 2026

Recent Comments

No comments to show.

Knowledge Base

Access detailed guides, tutorials, and resources.

Live Chat

Get instant help 24/7 from our support team.

Send Ticket

Our team typically responds within 10 minutes.

logo
Alipay Cc-paypal Cc-stripe Cc-visa Cc-mastercard Bitcoin
Cloud VPS
  • Hong Kong VPS
  • US VPS
Dedicated Servers
  • Hong Kong Servers
  • US Servers
  • Singapore Servers
  • Japan Servers
More
  • Contact Us
  • Blog
  • Legal
© 2026 Server.HK | Hosting Limited, Hong Kong | Company Registration No. 77008912
Telegram
Telegram @ServerHKBot