香港 VPS 搭建 Node.js 应用:从环境配置到上线的全流程
- 前置条件与架构说明
- 第一步:安装 Node.js(使用 nvm 管理版本)
- 第二步:安装并配置 PM2 进程守护
- 第三步:部署第一个 Node.js 应用
- 第四步:配置 Nginx 反向代理
- 第五步:申请 SSL 证书
- 第六步:配置自动化部署(Git + 钩子)
- 第七步:安全加固与性能优化
- PM2 常用命令速查表
- 常见问题解答(FAQ)
前置条件与架构说明
| 条件 | 要求 | 说明 |
|---|---|---|
| 服务器系统 | Ubuntu 20.04 / 22.04 | 本文命令基于 Ubuntu,Debian 完全兼容 |
| 服务器配置 | 最低 1核1G,推荐 2核2G+ | Node.js 本身轻量,内存主要取决于应用逻辑 |
| 域名 | 已解析到服务器 IP | 申请 SSL 必须先完成 DNS 解析 |
| 端口放行 | 80、443 已在安全组开放 | 云平台控制面板确认入方向规则 |
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
配置 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 myapp | cluster 模式下无中断重启 |
| 强制重启 | 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/next,args 设为 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 线路,保障大陆用户低延迟访问。
