香港 VPS 搭建 Node.js 应用:从环境配置到上线的全流程

摘要:香港 VPS 凭借 CN2 GIA 低延迟线路,是部署面向大陆用户的 Node.js 应用的理想选择。本文从零开始完整演示:安装 Node.js → 配置 PM2 进程守护 → Nginx 反向代理 → 申请 SSL 证书 → 设置自动部署,覆盖从开发到生产上线的完整流程,适合 Express、Next.js、NestJS 等各类 Node.js 应用。

前置条件与架构说明

条件要求说明
服务器系统Ubuntu 20.04 / 22.04本文命令基于 Ubuntu,Debian 完全兼容
服务器配置最低 1核1G,推荐 2核2G+Node.js 本身轻量,内存主要取决于应用逻辑
域名已解析到服务器 IP申请 SSL 必须先完成 DNS 解析
端口放行80、443 已在安全组开放云平台控制面板确认入方向规则
💡 部署架构:本教程采用 Nginx → PM2 → Node.js 应用 的标准生产架构。Nginx 负责 SSL 终止、静态文件服务和反向代理;PM2 负责进程守护、负载均衡和日志管理;Node.js 应用专注于业务逻辑,监听本地端口(如 3000)。

1 安装 Node.js(使用 nvm 管理版本)

推荐使用 nvm(Node Version Manager)安装 Node.js,可以灵活切换版本,不会污染系统环境。

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

# 使 nvm 立即生效(或重新登录 SSH)
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

# 验证 nvm 安装
nvm --version

# 查看可安装的 Node.js LTS 版本
nvm ls-remote --lts

# 安装最新 LTS 版本(推荐生产环境使用 LTS)
nvm install --lts

# 设置为默认版本
nvm alias default node

# 验证安装
node --version
npm --version
为什么用 nvm?直接用 apt 安装的 Node.js 版本通常较旧(Ubuntu 22.04 自带的是 v12),而生产环境推荐 Node.js v18 LTS 或 v20 LTS。nvm 让你可以随时安装和切换任意版本。

配置 npm 国内镜像(香港服务器可跳过,国内开发机建议配置):

# 切换为淘宝镜像源(加速 npm 包下载)
npm config set registry https://registry.npmmirror.com

# 验证
npm config get registry

2 安装并配置 PM2 进程守护

PM2 是 Node.js 生产环境的标配进程管理器,提供进程守护、开机自启、日志管理、负载均衡等功能。

# 全局安装 PM2
npm install -g pm2

# 验证安装
pm2 --version

# 设置 PM2 开机自启(重要!防止服务器重启后应用不自动恢复)
pm2 startup

# 执行上面命令输出的那条 sudo 命令(类似如下,根据实际输出执行)
# sudo env PATH=$PATH:/root/.nvm/versions/node/v20.x.x/bin pm2 startup systemd -u root --hp /root
⚠️ 重要:pm2 startup 命令会输出一条需要执行的命令,必须复制并执行该命令才能真正设置开机自启。不要跳过这一步,否则服务器重启后应用不会自动恢复。

3 部署第一个 Node.js 应用

以一个 Express.js 示例应用演示完整部署流程(实际替换为你自己的应用):

# 创建应用目录
mkdir -p /var/www/myapp && cd /var/www/myapp

# 初始化项目
npm init -y

# 安装 Express
npm install express

# 创建主文件
nano app.js

粘贴示例应用代码:

const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

app.use(express.json());

app.get('/', (req, res) => {
  res.json({
    status: 'ok',
    message: '香港 VPS Node.js 应用运行正常',
    timestamp: new Date().toISOString()
  });
});

app.get('/health', (req, res) => {
  res.json({ status: 'healthy' });
});

app.listen(PORT, '127.0.0.1', () => {
  console.log(`Server running on port ${PORT}`);
});

module.exports = app;
💡 注意:app.listen 绑定 127.0.0.1 而非 0.0.0.0,这样 Node.js 端口只监听本地,外部无法直接访问,所有流量必须经过 Nginx,更安全。

使用 PM2 启动并管理应用:

# 启动应用
pm2 start app.js --name "myapp"

# 查看运行状态
pm2 status

# 查看实时日志
pm2 logs myapp

# 验证应用正在监听
curl http://127.0.0.1:3000/
# 应返回 JSON 响应

创建 PM2 生态配置文件(推荐,便于管理多应用):

# 创建配置文件
nano /var/www/myapp/ecosystem.config.js
module.exports = {
  apps: [{
    name: 'myapp',
    script: './app.js',
    instances: 'max',        // 根据 CPU 核数自动开启多进程
    exec_mode: 'cluster',    // 集群模式,充分利用多核 CPU
    watch: false,            // 生产环境不要开启文件监听
    max_memory_restart: '500M',  // 内存超过 500M 自动重启
    env: {
      NODE_ENV: 'production',
      PORT: 3000
    },
    log_date_format: 'YYYY-MM-DD HH:mm:ss',
    error_file: '/var/log/pm2/myapp-error.log',
    out_file: '/var/log/pm2/myapp-out.log',
  }]
};
# 创建日志目录
mkdir -p /var/log/pm2

# 用配置文件启动
pm2 delete myapp  # 先删除之前启动的实例
pm2 start ecosystem.config.js

# 保存 PM2 进程列表(配合 startup 实现开机自启)
pm2 save

4 配置 Nginx 反向代理

# 安装 Nginx
apt install -y nginx
systemctl enable --now nginx

# 创建应用的 Nginx 配置
nano /etc/nginx/sites-available/myapp

粘贴以下 HTTP 版本配置(SSL 申请完后升级):

upstream nodejs {
    server 127.0.0.1:3000;
    keepalive 64;
}

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

    # 日志
    access_log /var/log/nginx/myapp-access.log;
    error_log  /var/log/nginx/myapp-error.log;

    # Let's Encrypt 验证
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        proxy_pass         http://nodejs;
        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;
        proxy_read_timeout 300s;
        proxy_connect_timeout 75s;
    }
}
# 启用配置
ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

# 测试反向代理是否正常(应返回 Node.js 的 JSON 响应)
curl http://yourdomain.com/

5 申请 SSL 证书

# 安装 Certbot
apt install -y certbot python3-certbot-nginx

# 申请证书(替换为你的域名和邮箱)
certbot --nginx -d yourdomain.com -d www.yourdomain.com \
  --email admin@yourdomain.com \
  --agree-tos --non-interactive

# Certbot 会自动修改 Nginx 配置添加 SSL

申请成功后手动完善 Nginx 配置,替换为生产级 HTTPS 版本:

nano /etc/nginx/sites-available/myapp
upstream nodejs {
    server 127.0.0.1:3000;
    keepalive 64;
}

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    return 301 https://$host$request_uri;
}

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

    ssl_certificate     /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 10m;

    # 安全响应头
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;

    # 静态文件直接由 Nginx 服务(如有 public 目录)
    location /static/ {
        alias /var/www/myapp/public/;
        expires 30d;
        add_header Cache-Control "public, no-transform";
    }

    # WebSocket 支持
    location /socket.io/ {
        proxy_pass         http://nodejs;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection "upgrade";
        proxy_set_header   Host $host;
    }

    location / {
        proxy_pass         http://nodejs;
        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;
    }
}
nginx -t && systemctl reload nginx

# 验证 HTTPS 正常
curl -I https://yourdomain.com/
✅ 返回 HTTP/2 200 即表示 HTTPS 配置成功。

6 配置自动化部署(Git + 钩子)

每次更新代码不需要手动 SSH 登录服务器,通过 Git 裸仓库 + post-receive 钩子实现自动部署。

# 在服务器创建裸仓库
mkdir -p /var/repo/myapp.git && cd /var/repo/myapp.git
git init --bare

# 创建 post-receive 钩子
nano hooks/post-receive

粘贴以下钩子脚本:

#!/bin/bash
TARGET="/var/www/myapp"
GIT_DIR="/var/repo/myapp.git"
BRANCH="main"

while read oldrev newrev ref; do
  CURRENT_BRANCH="${ref#refs/heads/}"
  if [ "$CURRENT_BRANCH" = "$BRANCH" ]; then
    echo ">>> 收到推送,开始部署..."
    git --work-tree=$TARGET --git-dir=$GIT_DIR checkout -f $BRANCH
    cd $TARGET
    echo ">>> 安装依赖..."
    npm install --production
    echo ">>> 重启应用..."
    pm2 reload ecosystem.config.js --update-env
    echo ">>> 部署完成!"
  fi
done
# 给钩子添加执行权限
chmod +x /var/repo/myapp.git/hooks/post-receive

在本地开发机添加服务器为远程仓库:

# 在本地项目目录执行(替换为你的服务器 IP)
git remote add production root@你的服务器IP:/var/repo/myapp.git

# 推送代码触发自动部署
git push production main
效果:每次 git push production main 后,服务器自动拉取新代码、安装依赖、无缝重启应用,整个过程约 10–30 秒,期间服务不中断(PM2 cluster 模式的零停机重载)。

7 安全加固与性能优化

防火墙配置(只开放必要端口):

# 安装并配置 UFW
apt install -y ufw
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh        # SSH
ufw allow 80/tcp     # HTTP
ufw allow 443/tcp    # HTTPS
ufw enable

# 查看防火墙状态
ufw status verbose

创建专用非 root 用户运行 Node.js(安全最佳实践):

# 创建专用用户
useradd -m -s /bin/bash nodeapp

# 将应用目录权限交给该用户
chown -R nodeapp:nodeapp /var/www/myapp

# 切换到该用户启动 PM2
su - nodeapp
pm2 start /var/www/myapp/ecosystem.config.js
pm2 startup
pm2 save

配置 Node.js 应用环境变量(不要把密码写在代码里):

# 创建 .env 文件
nano /var/www/myapp/.env

# 示例内容
NODE_ENV=production
PORT=3000
DB_HOST=127.0.0.1
DB_PASSWORD=your_strong_password
JWT_SECRET=your_jwt_secret_key

# 在 ecosystem.config.js 中读取 .env
# npm install dotenv
# 在 app.js 顶部添加: require('dotenv').config()

# .env 文件不要提交到 Git
echo ".env" >> .gitignore

开启 Node.js 应用日志轮转(防止日志文件无限增长):

# 安装 PM2 日志轮转模块
pm2 install pm2-logrotate

# 配置轮转参数
pm2 set pm2-logrotate:max_size 50M    # 单文件最大 50MB
pm2 set pm2-logrotate:retain 10       # 保留最近 10 个日志文件
pm2 set pm2-logrotate:compress true   # 压缩旧日志

PM2 常用命令速查表

操作命令说明
查看所有应用状态pm2 status显示所有进程运行状态
查看实时日志pm2 logs myapp按 Ctrl+C 退出
查看最近 200 行日志pm2 logs myapp --lines 200
零停机重启pm2 reload myappcluster 模式下无中断重启
强制重启pm2 restart myapp会有短暂中断
停止应用pm2 stop myapp停止但保留进程记录
删除应用pm2 delete myapp从 PM2 列表中移除
查看详细信息pm2 show myapp内存、CPU、重启次数等
监控面板pm2 monit实时 CPU/内存可视化
清空日志pm2 flush myapp清空该应用所有日志
保存进程列表pm2 save开机自启必须执行
从配置文件启动pm2 start ecosystem.config.js

常见问题解答(FAQ)

Q1:PM2 cluster 模式和 fork 模式有什么区别?什么时候用哪个?

fork 模式启动单个进程,适合不支持多进程的应用(如 WebSocket 有状态服务)或调试阶段;cluster 模式会根据 CPU 核数启动多个 worker 进程,充分利用多核 CPU,适合无状态的 HTTP API 服务,吞吐量提升明显。生产环境的 REST API 推荐 cluster 模式,有状态的 WebSocket 应用推荐搭配 Redis 适配器后再使用 cluster 模式。

Q2:Node.js 应用报 EADDRINUSE 端口被占用,怎么处理?

执行 lsof -i :3000 查看占用 3000 端口的进程,记录 PID 后执行 kill -9 PID 结束该进程。如果是之前的 PM2 进程残留,执行 pm2 delete all && pm2 kill 清空所有 PM2 进程后重新启动。

Q3:Next.js 项目如何用 PM2 部署?

Next.js 需要先构建再启动。在服务器执行 npm run build 生成 .next 目录,然后在 ecosystem.config.js 中将 script 改为 node_modules/.bin/nextargs 设为 start。或者直接用 pm2 start npm --name "nextapp" -- start 启动。

Q4:如何让 Node.js 应用访问 MySQL 数据库?

安装 mysql2 包(npm install mysql2),使用连接池管理数据库连接。推荐搭配 ORM 框架(Prisma 或 Sequelize)简化数据库操作。数据库密码通过 .env 环境变量注入,不要硬编码在代码中。

Q5:后浪云哪款 VPS 最适合跑 Node.js 应用?

轻量 API 服务或个人项目选 香港云服务器 1核2G(约 50 元/月)完全够用;中型 SaaS 应用推荐 2核4G;高并发场景(万级 QPS)建议 4核8G 并开启 PM2 cluster 模式充分利用多核。所有套餐均接入 CN2 GIA 线路,保障大陆用户低延迟访问。

THE END