PM2 这名字做 Node.js 服务端开发的基本都听说过,但很多新手第一次用它,是在某台服务器上node app.js跑起来一个接口,然后第二天发现服务挂了,日志也找不到,前端甩过来一句"接口不通了",你连从哪儿查起都不知道。PM2 就是来解决这件事的。简单说,它是 Node.js 的进程管理器,负责让你的应用一直在后台跑着、崩了自动拉起来、日志统一收走、还能管多个应用。
这篇文章是一份新手操作手册,我会从安装开始,把启动、重启、日志、配置文件、开机自启这些日常操作全部过一遍,顺便把我自己踩过的坑和排查思路也写进去。适合刚接触服务器部署、被"进程一关就没了"折磨过的朋友看,哪怕你完全没用过 PM2,照着我下面的步骤走一遍,也能跑起来。
1. 项目概述:PM2 到底解决什么问题
1.1 核心需求解析:没有 PM2 的日子
先说说没有 PM2 的时候是怎么过来的。最原始的做法就是 SSH 连上服务器,node app.js,跑起来之后窗口就一直挂着,你一关终端,进程跟着就没了。后来有人教你用nohup node app.js &,好一点,终端能关了,但服务万一崩了,没人管它,你就得自己手动登录查看再拉起来,如果是在半夜,那体验就非常酸爽。
还有个问题是多个应用怎么管理。一个服务器上可能同时跑着接口服务、定时任务脚本、前端静态资源服务,每个都是一个进程。你靠ps aux | grep node去找,找到 PID 再kill,这操作一次两次还行,天天这样搞,迟早会出事故。我就见过有人手误把系统关键进程给 kill 了的,跑完还一脸茫然。
PM2 做的就是把这一堆脏活累活接过去:守护进程、崩溃自动重启、日志统一收集、进程状态可视化、开机自启。你只需要告诉它"跑哪个文件、用什么名字、占多少资源",剩下的交给它。这不是什么花哨的新技术,而是每个 Node 应用上了生产环境之后都该有的基础配置。
1.2 适用场景与读者画像
说下这东西适合什么场景。如果你是个人项目,一台低配云服务器上跑着一两个 Node 服务,PM2 是最省事的方案。如果你在小团队,服务器没接容器化编排那套东西,PM2 依然是非常主流的选型。如果你的应用已经到了 K8s 那种规模,PM2 就不是主角了,容器环境更倾向于让编排平台负责进程生命周期,但很多团队即使在 Docker 里也还是会用 PM2 托底,这事后面我会专门讲。
读者画像的话,我默认你满足三个前提:会点 Node.js 基础语法、知道怎么操作 Linux 命令行、能把你的应用通过node xxx.js本地跑起来。如果你还不会这些,建议先把 Node 基础和 Linux 常用命令补一补再来,不然下面的操作你会看得很吃力。
另外一个可能被忽略的点是:PM2 不只是生产环境的工具。本地开发也可以拿它来管理多个微服务进程,特别是你同时起前端构建服务和后端接口服务的时候,比开一堆终端窗口清爽得多。我身边就有人拿 PM2 当本地进程管理器的,配合 watch 模式改代码自动重启,挺好用。
2. 环境准备与安装
2.1 安装前的环境要求
PM2 是 npm 包,安装前需要 Node.js 和 npm。Node 版本建议不要太老,我见过有人还在用 8.x,跑新版本 PM2 会有兼容警告。现在项目一般至少 Node 14 起步,你能装到 16 或 18 就没问题。npm 跟 Node 一起装上,不用额外操心。
操作系统方面,生产服务器基本是 CentOS 7 或 Ubuntu 系,教程里的命令在这两类系统上通用。Windows 上也能装能用,但如果你是部署生产环境,我不建议用 Windows 当服务器,PM2 的很多行为在 Linux 上才表现完整,比如开机自启那一整套,Windows 上支持度就差一些。开发机器用 Windows 的话,装好跑跑基本命令没问题,但遇到问题别在 Windows 上钻牛角尖,很多坑是系统差异导致的。
另外注意权限问题,安装全局 npm 包通常需要写全局目录,Linux 上如果你用普通用户,可能碰到 EACCES 权限错误。几个解决办法:用 sudo 装(简单,但要注意后面跑 PM2 的权限也会受影响);或者给 npm 配置好用户级全局目录,这种方式更干净。新手建议先 sudo 装,跑通流程最重要,权限洁癖可以后面再改。
2.2 安装方法与验证
安装命令就一条:
npm install -g pm2装完检查版本:
pm2 -v能输出版本号就是装成功了。如果卡在安装那一步很久不动,大概率是 npm 源的问题。国内服务器直接连默认源经常很慢,换成镜像源再装:
npm config set registry https://registry.npmmirror.com npm install -g pm2换源之后速度会快很多,这是我在云服务器上实测的结果。顺便说一句,不要用 cnpm 装全局工具,cnpm 装出来的包有时候软链有问题,直接改 registry 更省心。
还有种情况是你服务器上有 nvm 管理 Node 版本,这时候npm install -g pm2会装到当前 Node 版本对应的全局路径里。如果你经常切换 Node 版本,会发现切完版本pm2命令找不到了,这是正常的,因为 PM2 装在那个版本的全局目录里。解决办法是每个版本都装一次,或者用 nvm 的 alias 固定一个版本给生产环境用。这事坑了不少人,记一下能省事。
3. 核心命令操作手册
3.1 启动进程与查看状态
装好之后,进入你的项目目录,假设入口文件是 app.js,启动命令:
pm2 start app.js跑完你会看到一张表格,里面有应用的 id、name、模式、状态、重启次数、内存占用等列。第一次跑的时候,新手很容易被这些术语吓到,其实不用怕,先记住两个标识:id 和 name。id 是数字,name 默认是 app.js 去掉后缀的名字,你后续所有的操作基本都靠这两个来指定进程。
建议启动的时候给应用起个好认的名字,不然多个 Node 应用都叫 server 就乱了:
pm2 start app.js --name api-server查看运行状态用:
pm2 list如果你发现状态列是 online,说明进程活着;如果是 errored,说明进程起不来或者已经挂了,这时候马上看日志。还有个命令pm2 show api-server,能看这个进程的详细情况,包括启动路径、node 版本、环境变量、重启次数、日志文件位置,排查问题的时候非常有价值。
想看资源占用实时变化,用pm2 monit,会进入一个实时面板,显示 CPU、内存、请求数。这个命令适合观察应用是否异常占资源,看完按 q 退出。
3.2 重启与平滑重载:restart 和 reload 的区别
日常更新代码之后需要让进程重新加载,最直接的是:
pm2 restart api-serverrestart 是粗暴地杀掉进程再拉起来,会有一瞬间的服务中断,本地开发无所谓,但线上如果用户正在请求,可能就碰到 502 了。所以 PM2 提供了另一个命令,reload:
pm2 reload api-serverreload 做的是优雅重载。它会在 PM2 内部做负载均衡,逐个把旧进程替换成新进程,保证全程有实例在处理请求,实现零停机更新。但有个前提:你的应用得运行在 cluster 模式下(多实例),单实例下 reload 和 restart 基本没有区别。
我自己的习惯是:开发环境随便 restart,生产环境如果改了业务代码,先pm2 reload试试,不行再 restart。然后配合 pm2 scale 做多实例场景下的滚动更新,这套后面讲 cluster 模式的时候再展开。
3.3 停止与删除进程:别搞混这两件事
停止和删除是两个概念,新手经常搞混。stop 是暂停进程,进程还在 PM2 的列表里,你可以随时 start 拉起来,状态记录都保留。delete 是彻底移除,进程从 PM2 列表里消失,想再跑得重新 start。
pm2 stop api-server pm2 start api-server pm2 delete api-server对应全量操作:pm2 stop all、pm2 delete all。注意 delete all 之后,之前 save 的进程列表还在,如果你执行了pm2 resurrect,它会按上次保存的列表重新拉起。这个机制后面讲 save 的时候会细说。
这里有个场景我提醒一下:你如果只是想临时停一下服务排查问题,用 stop 而不是 delete,因为 delete 之后 PM2 对这个进程的日志文件、监控数据也一并清理了,排查材料就没了。
3.4 日志查看:线上排错的第一入口
PM2 把进程的标准输出和标准错误分别收集到日志文件里。查看实时日志:
pm2 logs api-server默认会跟着打印新的日志,像 tail -f。查看最近 100 行且不跟随的话:
pm2 logs api-server --lines 100 --nostream如果你想看错误日志和普通日志分开的,可以直接打开日志文件,路径一般在~/.pm2/logs/,文件名是"应用名-out.log"和"应用名-error.log"。用 tail 直接看:
tail -f ~/.pm2/logs/api-server-out.log tail -f ~/.pm2/logs/api-server-error.log日志刷太多把磁盘塞满的问题很常见,后面我专门写一节怎么处理。新手先记住:应用起不来、崩溃、被重启,第一件事就是看 error 日志,八成问题一眼就能定位。
3.5 保存进程列表:重启机器不慌的前提
你手动 start 了一堆应用,如果服务器重启,这些进程不会自动回来。所以 PM2 提供了 save 机制:
pm2 save这条命令会把当前 PM2 管理的进程列表保存到一个 dump 文件里。之后服务器重启了,你可以用:
pm2 resurrect按保存的列表把进程全部拉起来。但手动 resurrect 还是不够自动化,更完整的方案是配开机自启,这个到第 5 节讲 pm2 startup 的时候一起说。这里先记住 save 和 resurrect 是一对。
3.6 查看进程退出码与重启历史
排查频繁重启的问题时,pm2 describe里有 Process Configuration、重启时间和退出码记录。退出码能给你很多线索:比如退出码是 1 一般是代码抛了未捕获异常,退出码是 137 表示被 OOM killer 杀了,退出码是 0 反而是主动退出的可能更大。看日志里退出码,比瞎猜进度要靠谱得多。
4. 配置文件 ecosystem.config.js 详解
4.1 为什么需要配置文件
前面用的都是pm2 start app.js加参数的简单模式,适合快速跑一个单实例应用。但当你管理的应用多了,每个都有一堆参数,环境变量、日志路径、内存上限、启动模式,全写在命令行里既容易错也没法维护。配置文件就是把这些东西固化下来,存到项目里跟着代码走,团队成员 clone 下来就能用同一个配置启服务。
生成配置文件:
pm2 init simple这会在当前目录生成一个 ecosystem.config.js,内容大概是:
module.exports = { apps: [ { name: 'api-server', script: './app.js', }, ], };然后用配置文件启动:
pm2 start ecosystem.config.js加了配置文件之后,以后更新代码重启就是这个流程:改代码 ->pm2 reload ecosystem.config.js-> 完事。配置的修改也会在 reload 时生效。
4.2 常用配置项解析
我把我常用的配置写一个示例,然后逐个解释:
module.exports = { apps: [ { name: 'api-server', script: './app.js', instances: 2, exec_mode: 'cluster', watch: false, max_memory_restart: '300M', env: { NODE_ENV: 'development', PORT: 3000, }, env_production: { NODE_ENV: 'production', PORT: 8080, }, out_file: '/var/log/pm2/api-server-out.log', error_file: '/var/log/pm2/api-server-error.log', merge_logs: true, time: true, }, ], };每个字段说下作用:
- name:应用名,全局轮询时靠它区分。
- script:入口脚本,必填。
- instances:实例数量,配合 exec_mode: cluster 做负载均衡,后面单讲。
- exec_mode:默认 fork,也就是单进程;cluster 是多进程。如果配了 instances 大于 1,PM2 会自动切成 cluster 模式。
- watch:监听文件变化自动重启。开发环境好用,生产环境慎开,后面单讲。
- max_memory_restart:内存超过这个值自动重启,防止内存泄漏把服务器拖垮,这个值得长期开着。
- env:基础环境变量,会全部注入到进程里。
- env_production:通过
--env production启动时,会覆盖 env 里的同名变量。 - out_file / error_file:指定日志输出路径,默认在 ~/.pm2/logs 下。
- merge_logs:多实例时把日志合并到一个文件,否则每个实例一个文件,查日志很痛苦。
- time:日志行前加上时间戳,强烈建议开,不然线上看日志时不知道先后顺序。
4.3 多环境配置:同一份代码区分开发与生产
上面配置里 env 和 env_production 的写法就是多环境管理的思路。启动开发环境:
pm2 start ecosystem.config.js启动生产环境:
pm2 start ecosystem.config.js --env production注意--env production并不是自动对应 env_production 这个键,而是 PM2 会去找env_加你传的参数的配置对象。所以命名要规范,env_production 配production,env_staging 配staging。
环境变量的另一个注意点是:process.env.NODE_ENV 在 PM2 里默认是 undefined,不会自动帮你设置。很多人以为 PM2 启动就自动是 production,其实没有,必须在配置文件里显式设置,不然很多框架会以开发模式跑,性能差一截还有安全风险。这是我最早踩过的一个坑。
5. 高级功能实战
5.1 Cluster 模式:多核 CPU 不用白不用
现代服务器 CPU 基本都是多核的,Node.js 默认是单线程模型,一个进程跑不满多核。PM2 的 cluster 模式可以在每个 CPU 核心上跑一个实例,再用内置的负载均衡把请求分发到各个实例上。
配置方式很简单:
module.exports = { apps: [ { name: 'api-server', script: './app.js', exec_mode: 'cluster', instances: 'max', // 或者写具体数字,比如 4 }, ], };instances 设为 max 就是按 CPU 核心数自动创建实例。设具体数字也可以。但我提醒一句:不是实例越多越好。每个实例都会消耗内存,如果你服务器是 2G 内存,开 8 个实例,内存直接爆炸。我用过一台 2G 的云服务器,Node 应用每个实例稳定吃 200M 左右,开 4 个都用得小心翼翼。
cluster 模式最大的问题是状态同步。如果你的应用用了内存变量存储登录状态、session、简单的计数器,多实例下每个实例的内存是隔离的,就会出现"用户第一次请求打到实例 A,第二次打到实例 B,结果登录态丢了"这种诡异问题。解决方案是把状态放到 Redis 这类外部存储里。如果你的应用没有做无状态化改造,谨慎开多实例,先单实例跑着,等架构梳理清楚再扩。
cluster 模式下还有一个命令值得记一下:
pm2 scale api-server 4这是线上不重启的情况下动态扩缩容,把实例数从当前值调成 4。扩到 0 就等于全部停掉。这个命令在实际运维中很有用,比如业务高峰前把实例扩上去,结束后再缩回来。
5.2 Watch 模式:开发好帮手,生产请三思
配置文件里 watch: true 会监听文件变化,一旦有文件改动,PM2 自动重启进程。这功能在本地开发时非常好用,改完代码不用手动重启,配合 nodemon 的功效类似。
生产环境我不建议开。原因很直接:生产环境你更新代码通常是 git pull 或者 CI/CD 推送文件,文件一变化进程就重启,但有时候你正在传静态资源,传到一半触发了重启,新的进程加载到一半的文件,就可能出问题。另外 watch 会监控指定目录的所有文件变化,如果日志文件写在项目目录里,日志一直写,watch 会一直触发重启,直接死循环。配置里可以指定 ignore_watch 来避免:
watch: true, ignore_watch: ['node_modules', 'logs', '*.log'],开发环境开的建议同时加上 ignore_watch 排除 node_modules,否则依赖安装那种海量文件变动,PM2 会崩溃给你看。
5.3 开机自启:服务器重启后服务自动恢复
服务器重启不稀奇,内存故障、内核升级、机房维护,都可能重启。如果不配开机自启,你就得半夜爬起来手动 resurrect。PM2 提供了非常友好的自启配置:
pm2 startup执行后 PM2 会提示你复制一行命令,带上 sudo 运行。运行完它会生成一个 systemd 服务单元,这样服务器开机时系统会把 PM2 拉起来,PM2 再按照上次 save 的列表恢复所有进程。
所以开机自启的完整配置是两条:
pm2 startup pm2 save顺序不能反,必须先 startup 生成服务,后 save 保存进程列表。如果以后你新增了应用,记得再执行一次pm2 save,否则进程列表还是旧的,重启后新应用不会自动起来。
这个方案我在多台服务器上实测下来很稳。唯一需要注意的是,如果你换了服务器或者换了用户,startup 要重新执行一次,因为 systemd 服务绑定的是当前用户。
5.4 PM2 的进程管理哲学:你是编排者,不是保姆
用 PM2 一段时间后你会发现,它其实在引导你建立一种"声明式"的管理习惯。凡是能写进配置文件的,就不要手动去命令行里敲。凡是能自动恢复的,就不要依赖人肉盯梢。这跟我平时做技术方案的原则一致:把重复的事情自动化,把状态尽量收敛到代码里,人只处理真正需要判断的事情。
6. 常见问题与排查技巧实录
6.1 端口被占用:EADDRINUSE 报错
启动时报 EADDRINUSE,意思是端口被占了。排查思路:
lsof -i :3000 netstat -tunlp | grep 3000找到占用进程的 PID 之后,确认是不是自己之前的进程没关干净。如果是,kill 再重启;如果不是,考虑换端口。这类问题的根源通常是之前node app.js直接跑过,Ctrl+C 没杀掉,或者 PM2 的实例还没释放端口。另一个场景是多个实例启动时,如果配置里每个实例都监听同一个端口靠 PM2 分发是没问题的,但你手动同时跑了两个独立进程监听同一端口,必报这个错。
6.2 应用一直 Crash,重启循环怎么办
状态列一直 restart,说明进程启动后很快就崩了。第一步先看日志,确认崩溃原因。如果日志里是找不到模块(MODULE_NOT_FOUND),多半是启动路径不对或者 node_modules 没装全。如果日志里是语法错误,那是代码本身的问题。
有一种诡异的情况,应用启动时依赖数据库等外部服务,数据库没就绪,应用连不上就抛出异常退出,PM2 马上重启,又退出,形成循环。这种情况除了修代码加重试和容错之外,也可以用 PM2 的重启延迟设置来减轻压力:
min_uptime: '10s', max_restarts: 10, restart_delay: 3000,这几个参数的作用是:进程最少要稳定运行 10 秒,否则算启动失败;如果失败重启超过 10 次,就不再做无限重启;每次重启间隔 3 秒。这样既保证临时故障时进程能自动恢复,又避免故障持续时服务器资源被反复创建进程耗尽。
6.3 内存溢出与 OOM Killer
服务器上如果发现 Node 进程突然消失,状态变成 offline,去系统日志里查可能看到 OOM Killer 的记录。这通常是物理内存不够,系统把大内存进程杀了。云服务器 1G、2G 内存很常见,Node 进程单个占几百 M,再叠加多实例,很容易触发。
应对方式:给每个进程设内存上限:
max_memory_restart: '300M'让 PM2 在进程接近内存上限时主动重启,千万别等到系统 OOM 再被动杀。同时排查代码里是不是有内存泄漏,比如全局变量无限制增长、定时器没清理、数据库连接池没控制。内存问题不只是运维能解决的,更需要从代码层面排查。
6.4 日志文件把磁盘塞满
跑上几个月的服务,日志文件动不动几个 G,磁盘被塞满之后服务各种奇怪问题。两个层面的处理:
日常清理用:
pm2 flush把当前缓冲的日志写完并清空。但是 flush 只清 PM2 内存中的日志缓冲,不删除磁盘上的历史日志文件,要删文件得手动truncate或者用 logrotate。Linux 上最正统的方案是配置 logrotate 定期切割轮转日志,让日志按天或按大小滚动,旧的自动压缩或删除。你可以建一个/etc/logrotate.d/pm2的配置,把~/.pm2/logs/*.log作为切割对象,设置 daily、rotate 7、compress 这些参数。
如果你不想折腾系统级配置,最简单粗暴的办法是把应用日志输出到指定的文件,然后用 shell 定时任务每天 truncate 一次。能接受日志只保留最近 7 天的话,这个方案够用。我强烈建议所有上线的项目都提前考虑日志轮转,等磁盘告警了再处理,那已经是事故了。
6.5 容器里要不要用 PM2
现在很多人直接把 Node 服务打包成 Docker 镜像跑,这时候 PM2 的角色有点尴尬。在容器里,进程生命周期本来就该由容器平台管,你用 PM2 的话,它仍然是 PID 1,如果没处理好信号转发,docker stop 的时候容器可能卡住好一会儿才能被强杀。
但也不是完全不能用。如果你的容器里要同时跑两个 Node 进程,PM2 作为容器内的多进程管理器是合理的。做法上注意:容器里不要跑pm2 startup,因为那是给物理机配 systemd 服务的;日志要输出到 stdout 而不是文件,否则 docker logs 看不到。在 Dockerfile 里用:
CMD ["pm2-runtime", "start", "ecosystem.config.js"]pm2-runtime 是官方专门给容器场景准备的命令,能正确处理 PID 1 信号转发和日志 stdout 输出。如果你在容器里坚持用pm2 start,docker stop 的时候就可能出现服务无法优雅退出的情况。这是我在迁移容器化过程中实测踩过的坑。
6.6 时区错乱与日志时间不对
如果你开了日志时间戳,却发现打印时间和本地时间差 8 个小时,说明服务器系统时区没设对。看看:
date如果显示的 UTC 时间,设置成 Asia/Shanghai,改完时区后 PM2 里已有的进程可能需要重启才会带出新时间戳。这个不影响业务逻辑,但排查问题时日志时间错乱会让人抓狂,尤其是跨时区协作的时候。
6.7 cluster 模式下请求随机 502
cluster 模式配好之后,如果偶尔出现 502,原因通常是实例在 reload 过程中新实例还没就绪,旧实例已经被回收了。解决办法是给 PM2 一点"听启动完成"的信号。代码里在服务监听成功后:
if (typeof process.send === 'function') { process.send('ready'); }配置文件里加上:
wait_ready: true, listen_timeout: 3000,这能让 PM2 等到实例主动上报 ready 之后再把流量分给它。这个是很多人忽略的细节,属于"配置没问题但线上偶发故障"的典型来源。
7. 新手推荐的上手路径
其实你不需要一次性掌握 PM2 的所有功能,我建议按这个顺序逐步来:
第一步,先装好,启动一个简单应用,练习 list、stop、start、restart、delete、logs 这几个命令,让自己"不怕操作"。
第二步,引入 ecosystem.config.js 配置文件,把环境变量、日志路径、自启动配置写好,从命令式操作变成配置式管理。
第三步,理解 cluster 模式的前置条件和坑,确认应用无状态化之后再上多实例。
第四步,把开机自启和日志轮转配置好,这时候你的服务才算真正达到"挂了自愈、重启自启、日志可查"的基本生产标准。
我在实际项目里见过太多人把 PM2 用成了"高级 node 启动器",只用 start 和 logs,其他全不碰,结果一出服务器重启就手忙脚乱。工具本身不难,难的是你愿不愿意把运维那套习惯建立起来。我的体会是:越是基础的工具,越值得把它的每个能力摸清楚,因为你可能永远用不到很高级的配置,但生产环境下一次半夜的故障告警,就能把它的价值全体现出来。