宝塔面板部署 Node.js 项目完整实践:从裸机到 HTTPS
把一台只有 SSH 的裸机变成能跑 Node.js 站点的生产环境:装运行时、传项目、PM2 守护、Nginx 反代、签发证书,每一步都附实际命令与踩坑点。
这篇文章记录一次完整的部署过程。目标很明确:在一台 1 GB 内存的服务器上,用宝塔面板把 Node.js 项目跑起来并配上 HTTPS。
#为什么选择宝塔 + Node.js 而不是 PHP 方案
先说结论:内存是唯一的决策依据。
我做过对比测算(1 GB 内存机器):
| 方案 | 常驻内存 | 是否需要额外装数据库 | 适合场景 |
|---|---|---|---|
| Node.js + SQLite | 约 100 MB | 否 | 个人站点、中小流量 |
| PHP + MySQL | 约 400 MB | 是(MySQL 约 300 MB 起) | 传统 CMS |
| Java + MySQL | 约 600 MB 起 | 是 | 企业应用 |
对一台只剩 400 MB 可用内存的机器来说,MySQL 单实例就会把系统压到 OOM 边缘。所以选了 Node.js + SQLite。
#第一步:确认服务器底子
部署前先做一次只读巡检,不要凭感觉判断机器能不能跑:
# 关键指标一次看完
nproc # CPU 核数
free -h # 内存,重点看 available 列而不是 free 列
df -hT | grep -vE 'tmpfs|shm' # 磁盘
ss -ltnp # 端口占用,确认目标端口没被占
关于内存有一个容易踩的坑:
free命令输出的free列并不是真实可用内存。Linux 会把空闲内存拿去做文件缓存,这部分随时可以释放。 真正要看的available列。
#第二步:安装 Node 运行时
宝塔面板自带的软件商店里有 Node 版本管理器,装好之后有两个关键点:
- 版本选择:选 LTS。注意老系统的 glibc 版本限制 —— 例如 Debian 10 的 glibc 是 2.28,而 Node 18 及以上的官方二进制包要求 glibc ≥ 2.28,属于刚好卡在线上,能跑但不要再降级系统。
- 装完之后命令行可能找不到 node:宝塔是把 Node 装在
/www/server/nodejs/vXX.X.X/bin/下的,需要手动加软链:
ln -sf /www/server/nodejs/v20.18.0/bin/node /usr/local/bin/node
ln -sf /www/server/nodejs/v20.18.0/bin/npm /usr/local/bin/npm
node -v # 验证
#第三步:上传项目并安装依赖
项目目录建议放在 /www/wwwroot/ 下,和宝塔的站点管理保持一致:
mkdir -p /www/wwwroot/blog
cd /www/wwwroot/blog
# 上传代码(本地用 scp 或宝塔文件管理器上传压缩包)
npm install --omit=dev --no-audit --no-fund
注意:如果项目里有原生模块(比如 better-sqlite3),安装时会尝试下载预编译包,失败则回落到本地编译。本地编译需要 python3、make、g++:
# Debian / Ubuntu
apt-get install -y build-essential python3
# CentOS
yum install -y gcc-c++ make python3
#第四步:用 PM2 做进程守护
直接 node server.js 的问题是一关 SSH 就退出。PM2 解决三件事:后台常驻、崩溃自动拉起、开机自启。
npm install -g pm2
# 用配置文件启动,参数固化下来便于复现
pm2 start ecosystem.config.cjs
pm2 save # 保存当前进程列表
pm2 startup # 生成开机自启脚本,按提示复制执行它输出的那行命令
配置文件里最值得加的一项是小内存机器的保命参数:
max_memory_restart: '380M', // 超过这个值自动重启,避免被 OOM Killer 干掉整个进程
#第五步:Nginx 反向代理
关键认知:Node 服务只监听 127.0.0.1,对外由 Nginx 承接。 这样做的好处是证书、静态资源缓存、限流、日志统一由 Nginx 处理,Node 只负责业务逻辑。
宝塔里新建站点,然后在「网站」→「设置」→「反向代理」中配置目标 http://127.0.0.1:3000。但图形界面生成的配置往往缺几个关键项,建议直接改配置文件,补上:
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;
这三行的作用是让 Node 拿到真实访客 IP 和真实协议。少了它们,日志里全是 127.0.0.1,而且应用会以为自己在跑 HTTP。
对应地,Node 侧要开:
app.set('trust proxy', 1);
#第六步:签发 HTTPS 证书
宝塔「网站」→「SSL」→「Let's Encrypt」一键申请。前提是域名 A 记录已经指向本机 IP 并且 80 端口可访问,否则验证必然失败。
申请成功后要确认一件事:HTTP 的 80 端口配置里,续期验证目录必须放行:
location /.well-known/acme-challenge/ {
root /www/wwwroot/blog;
}
否则证书到期自动续期会静默失败,三个月后站点直接打不开。
#常见问题排查
502 Bad Gateway
几乎都是 Node 进程没起来。按顺序查:pm2 list 看进程状态 → pm2 logs 看报错 → ss -ltn | grep 3000 看端口有没有在听。
部署完页面样式全丢 Nginx 配置里静态资源被反代走了但没配缓存头,或者路径大小写不一致。Linux 是大小写敏感的,Windows 开发时不会暴露这个问题。
内存持续上涨直到重启
先看 pm2 monit。如果是缓慢增长,多半是会话或缓存对象没做淘汰,给相关结构加个上限即可。
#相关资源
以下资料可直接下载:
服务器只读巡检脚本
一条命令采集系统、CPU、内存、磁盘、Docker、端口与防火墙信息,不修改任何系统状态,适合部署前体检。
以下资料可直接下载:
Nginx 反向代理站点配置模板
含 HTTPS 跳转、证书自动续期目录放行、WebSocket 升级、静态资源缓存与安全响应头的完整配置模板,改两个变量即可用。
以下资料可直接下载:
PM2 进程守护配置模板
Node 服务的 PM2 配置,含内存上限自动重启、日志切割、开机自启与环境变量加载,适配低内存服务器。
#小结
整套流程的核心就三句话:
- 部署前先体检,用数据判断机器能不能扛住,别靠猜。
- 应用只监听 127.0.0.1,对外的一切交给 Nginx。
- 进程必须被守护,PM2 的内存上限重启是小内存机器的保命符。
文末附了巡检脚本和 Nginx 配置模板,可以直接下载改改就用。