先用 lsof -i :9501 或 ss -tulnp | grep :9501 精准定位占用进程,确认是否为僵死 hyperf 实例(查启动路径、运行时长、父进程是否为 1),优先 kill pid 优雅终止,无效再 kill -9;避免盲目 pkill 或杀全部 php 进程。

Hyperf 启动报 Address already in use 怎么快速定位端口占用进程
这不是 Hyperf 自身的问题,而是 Linux/Unix 系统层面的端口冲突。Hyperf 默认用 swoole 启动 HTTP 服务(如 0.0.0.0:9501),若该端口被其他进程(包括已崩溃但未释放端口的旧 worker)占着,就会抛出 Exception: Address already in use。
别急着 kill -9 所有 php 进程 —— 容易误杀线上服务。优先用系统命令精准找:
-
lsof -i :9501(查端口 9501 的持有者;若提示 command not found,先apt install lsof或yum install lsof) -
netstat -tulnp | grep :9501(-n避免 DNS 解析拖慢,-p显示 PID,需 root 权限才可见其他用户进程) -
ss -tulnp | grep :9501(比netstat更快,推荐替代)
输出里会看到类似 php 或 hyperf 的进程名和 PID,确认是僵死的旧实例再操作。
如何判断一个 php 进程是不是 Hyperf 僵死进程
不能单看进程名是 php 就杀 —— 可能是 CLI 脚本、Cron 任务或其它框架在跑。关键看三点:
- 启动路径是否含
bin/hyperf.php或项目路径(用ps -fp PID查完整命令行) - 进程启动时间是否远早于你本次调试时间(
ps -eo pid,etime,cmd | grep hyperf,etime是运行秒数) - 对应进程的父进程是否为
1(即已成孤儿进程,大概率僵死)
例如:ps -o pid,ppid,etime,cmd -p 12345 返回 12345 1 86400 php bin/hyperf.php start,说明它已运行一天且父进程是 init,基本可判定为残留。
杀死僵死进程时要注意什么
直接 kill -9 PID 虽快,但可能跳过 Swoole 的优雅退出逻辑(比如未关闭 Redis 连接、未 flush 日志)。更稳妥的做法是:
- 先尝试
kill PID(发送 SIGTERM),等几秒看进程是否自动退出 - 若无响应,再
kill -9 PID - 如果是一组 worker(如 master + manager + worker),通常只需杀 master 进程(PID 最小的那个),Swoole 会自动回收子进程
- 避免用
pkill -f "hyperf.php"—— 容易匹配到正在编辑的 vim 缓存或日志里的关键词,误杀风险高
Hyperf 的 master 进程默认不写 pid 文件,所以不要依赖 cat runtime/hyperf.pid —— 这个文件只在配置了 pid_file 时才生成,多数开发环境没开。
怎么防止下次又出现端口僵死
根本解法不是每次手动杀,而是让启动流程自带端口检查和清理能力:
- 启动前加 shell 检查:
if lsof -i :9501 > /dev/null; then echo "port 9501 occupied"; exit 1; fi - 在
config/autoload/server.php中启用enable_reuse_port(Swoole v4.5.0+),允许多个进程 bind 同一端口,降低冲突概率(注意:仅适用于多 worker 场景,不能解决「旧进程卡死」问题) - 用
supervisord或systemd管理进程,配置killasgroup=true和stopasgroup=true,确保停止时整个进程组被清理
最常被忽略的一点:Docker 容器重启后宿主机端口没释放,是因为容器内进程没收到 SIGTERM —— 要确保 Dockerfile 用了 exec php bin/hyperf.php start,而不是 php bin/hyperf.php start,否则信号无法透传给 master 进程。











