news 2026/9/19 3:59:05

PHP-FPM监听配置:UDS与TCP原理、性能对比及故障排查指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PHP-FPM监听配置:UDS与TCP原理、性能对比及故障排查指南

干了好些年 Web 运维和 PHP 开发,被问得最多的一个问题就是:php-fpm 的listen配置项到底该写/var/run/php-fpm.sock还是127.0.0.1:9000?一搜论坛,两拨人经常吵得不可开交,Unix Domain Socket 党说性能高,TCP Socket 党说简单直观好排错。今天不站队,把这两种方式背后的原理、性能差异、权限管理、故障排查全部掰开揉碎讲清楚,最后再给出一套可以直接“抄作业”的切换步骤。这篇东西适合刚入门 PHP 环境搭建的新手,也适合被 502、Connection refused 折磨过的老运维。

先说结论:这不是一个“谁取代谁”的问题,而是一个“在什么场景下选哪个”的问题。你只有真正理解了这两种 Socket 的工作方式,才能在任何一台陌生服务器上快速定位问题。

1. 先别急着选:两种通信方式到底在做什么

1.1 一个文件路径和一个 IP 端口,为什么能出现在同一个配置项里

很多人在第一次看到listen = /var/run/php-fpm.sock时都会懵:listen 后面不是应该跟 IP 和端口吗?怎么是一个文件路径?

这其实涉及两种完全不同的进程间通信方式:

  • 127.0.0.1:9000TCP Socket。Nginx 要通过 TCP/IP 协议栈,把请求数据封装成网络包,经过 loopback 回环接口发送给 PHP-FPM 监听的 9000 端口。你可以把它理解成两个人打电话,即使电话就在隔壁房间,也要走一遍完整的拨号、建立连接、传输、挂断流程。
  • /var/run/php-fpm.sockUnix Domain Socket(UDS)。它不走网络协议栈,直接在操作系统内核层面,通过一个文件系统路径作为通信端点来传递数据。相当于两个人站在门口用对讲机喊话,中间不需要经过交换机和线路。

这两种方式的本质区别,决定了它们在性能、权限、跨主机能力上的不同表现。

1.2 这些配置你肯定见过:各种 listen 值到底是什么意思

在 PHP-FPM 的 pool 配置文件(常见路径是/etc/php/8.2/fpm/pool.d/www.conf/etc/php-fpm.d/www.conf/usr/local/etc/php-fpm.d/www.conf)里,listen指令可以接受几种类型的值:

; 监听所有 IPv4 和 IPv6 的 9000 端口(很少这么配) listen = 9000 ; 只监听 loopback 地址的 9000 端口,外部无法直接访问 listen = 127.0.0.1:9000 ; 监听本机 IPv6 loopback 地址 listen = [::1]:9000 ; 监听 Unix Domain Socket listen = /var/run/php-fpm.sock ; 监听相对路径的 Unix Domain Socket(基于临时目录) listen = php-fpm.sock

我看到很多人一上来就抄网上的配置,根本不知道自己改的是哪种。最典型的情况是:PHP-FPM 在跑,Nginx 也在跑,但 Nginx 里fastcgi_pass写的是127.0.0.1:9000,而实际上 PHP-FPM 只监听了/var/run/php-fpm.sock,于是就看到一堆connect() failed (111: Connection refused),这就是典型的“两边各说各话”。

2. 一步步掰扯:UDS 和 TCP 的真实差别在哪里

2.1 性能差异:UDS 确实赢,但你可能感知不到

UDS 最大的优势是省掉了 TCP/IP 协议栈的处理过程。每一次请求都不需要经过 TCP 三次握手、不需要做校验和、不需要走路由和队列,数据直接从用户态空间拷贝到内核态,再拷贝给对端进程。在高并发场景下,这种开销的节省是可观的。

我见过一个压测数据:同样一台机器、同样一组 PHP 接口,用 UDS 比用 TCP loopback 的 QPS 能高出 10% 到 20%,尤其是短连接场景更明显。但这里有个容易被忽略的前提:只有在 PHP-FPM 和 Nginx 位于同一台主机时,UDS 才生效。

如果你的架构是 Nginx 在一台服务器、PHP-FPM 在另一台,UDS 完全不可用,因为 Unix Domain Socket 无法跨主机通信。这一点很多人踩坑:在本地单机用 UDS 没问题,把 PHP-FPM 容器化部署后还想用同一个 sock 路径,结果发现根本访问不到。

拿生活中的例子来说:UDS 就像公司内部的对讲机,只能在同一个园区里用;TCP 就像电话,只要有号码,天南海北都能拨通。

2.2 权限管理:sock 文件需要“养”,TCP 端口只需要“占”

这是很多新手的第一个坑。127.0.0.1:9000这种方式只需要保证端口没有被占用、防火墙没有拦截,而且因为监听在 loopback,外部网络访问不到,几乎没有权限问题。

但 UDS 不一样。它是以文件形式存在的,那就必须遵守文件权限规则。PHP-FPM 启动时会创建这个 sock 文件,Nginx 进程必须对这个文件有读写权限,否则就会出现connect() failed (13: Permission denied),直接报 502。

常见配置是:

listen = /var/run/php-fpm.sock listen.owner = www-data listen.group = www-data listen.mode = 0660

这里的listen.mode = 0660表示:文件所有者和所属组可以读写,其他人不行。如果你的 Nginx 是用nginx用户跑的,而 PHP-FPM 的 sock 文件所有者和组都是www-data,那 Nginx 就访问不了,需要把 Nginx 进程用户加入www-data组,或者让两者统一。

还有一个我非常想强调的细节:很多发行版的/var/run实际是指向/run的软链接,而/run是 tmpfs,系统重启后会清空。所以你的 PHP-FPM 如果配置的是/var/run/php-fpm.sock,重启之后目录还在,但 sock 文件需要重新创建,这本身没问题,因为 PHP-FPM 启动时会自动创建。但如果你的脚本或监控工具提前检查“sock 文件是否存在”,则可能在 PHP-FPM 启动前就误报故障。

2.3 故障排查方式完全不同:看到报错就能猜到是哪一种

根据我自己的排查经验,这两种方式出问题的症状和排查入口完全不一样:

对比项Unix Domain SocketTCP Socket
常见报错connect() failed (2: No such file or directory)(13: Permission denied)connect() failed (111: Connection refused)(110: Connection timed out)
排查首选命令ls -l /var/run/php-fpm.sockss -xlss -tlnp | grep 9000telnet 127.0.0.1 9000
常见原因sock 文件路径写错、PHP-FPM 未启动、权限不足端口被占用、PHP-FPM 未监听、防火墙拦截、监听地址不匹配
能否跨主机不能可以

看到No such file or directory,第一反应就是 PHP-FPM 没启动,或者 Nginx 里配置的路径和 PHP-FPM 实际创建的路径不一致。看到Connection refused,第一反应则是“端口上没有进程在监听”,这时候优先确认 PHP-FPM 是否真的在监听 9000 端口。

2.4 到底怎么选:一张图理清场景

做了这么多项目,我的个人经验是:

  • 单机部署、Nginx 和 PHP-FPM 在同一台机器,优先用 Unix Domain Socket。
  • 使用 Docker Compose 管理 PHP-FPM 容器,且 Nginx 容器通过内部网络访问时,建议用 TCP(比如php-fpm:9000),因为 sock 文件不能跨容器直接共享,除非你显式挂载共享卷。
  • 使用 Kubernetes、多实例负载均衡、或者 PHP-FPM 独立部署在多台机器上,必须用 TCP。
  • 本地开发调试时,TCP 更方便,因为你可以直接用telnet 127.0.0.1 9000来测试连接,也能配合各种图形化工具查看端口状态。

不要盲目跟风。UDS 虽好,但在容器化、分布式场景下强行用它,带来的麻烦远大于性能收益。

3. 实操记录:两种配置的完整切换过程

3.1 从 TCP 切换到 UDS( Unix Domain Socket )

假设当前 PHP-FPM 配置是listen = 127.0.0.1:9000,我想换成/var/run/php-fpm.sock

第一步,修改 PHP-FPM pool 配置文件。以 Ubuntu 上 PHP 8.2 为例:

# 先备份,别上来就改 cp /etc/php/8.2/fpm/pool.d/www.conf /etc/php/8.2/fpm/pool.d/www.conf.bak # 修改 listen 和相关权限配置 vim /etc/php/8.2/fpm/pool.d/www.conf

在文件里找到下面这几行:

listen = 127.0.0.1:9000 ;listen.owner = www-data ;listen.group = www-data ;listen.mode = 0660

改成:

listen = /var/run/php-fpm.sock listen.owner = www-data listen.group = www-data listen.mode = 0660

注意,配置文件里listen.owner前面的分号是注释符,如果你想启用就必须去掉。另外要确保/var/run/run目录对 PHP-FPM 进程有写权限,一般发行版默认没问题,但如果你自定义了/var/run/php-fpm子目录,就要提前创建并设置属主。

第二步,修改 Nginx 配置。在server块或location ~ \.php$里:

# 原来的写法 # fastcgi_pass 127.0.0.1:9000; # 改成 fastcgi_pass unix:/var/run/php-fpm.sock;

第三步,检查配置并重启:

# 检查 PHP-FPM 配置语法 php-fpm8.2 -t # 检查 Nginx 配置语法 nginx -t # 重启服务 systemctl restart php8.2-fpm systemctl reload nginx

第四步,验证是否生效:

# 查看 Unix Domain Socket 监听状态 ss -xl | grep php-fpm # 输出类似这样 u_str LISTEN 0 511 /var/run/php-fpm.sock 12345

如果你看到u_str LISTEN,说明 PHP-FPM 已经在通过 sock 文件监听了。这时候再访问一个 PHP 页面,确认不再 502,就说明切换成功。

3.2 从 UDS 切换回 TCP( 127.0.0.1:9000 )

这个反向操作也很常见,比如你要把 PHP-FPM 容器化,或者需要别的机器通过内网访问。

同样先备份,然后修改www.conf

listen = 127.0.0.1:9000 ; 保留原来的权限配置也没关系,TCP 模式下不生效 ;listen.owner = www-data ;listen.group = www-data ;listen.mode = 0660

Nginx 里改回:

fastcgi_pass 127.0.0.1:9000;

然后重启验证:

systemctl restart php8.2-fpm nginx -t && systemctl reload nginx # 查看 TCP 端口监听 ss -tlnp | grep 9000

看到LISTEN 0 511 127.0.0.1:9000就说明监听成功。

这里要特别提醒:只监听127.0.0.1意味着只能本机访问,跨机器访问是不通的。如果公司内网有机器需要连接这台 PHP-FPM,必须监听0.0.0.0:9000或具体内网 IP,同时配置防火墙。还要修改listen.allowed_clients,这个参数只在 TCP 模式生效,默认允许所有客户端访问(如果该行被注释)。为了安全,可以设成:

listen.allowed_clients = 127.0.0.1

但注意,这个参数在 PHP-FPM 7.0 之后其实已经被标记为废弃了,真正的访问控制应当由防火墙或网络层来做,不要过度依赖它。

3.3 一个小细节:监听 IPv6 时容易踩的坑

如果你在配置里写了:

listen = [::1]:9000

那么127.0.0.1的 IPv4 连接是连不上的,Nginx 要用:

fastcgi_pass [::1]:9000;

或者更简单一点:统一用127.0.0.1:9000,避免 IPv4/IPv6 混淆。我在调试的时候见过有人在 Nginx 里写fastcgi_pass localhost:9000,而 PHP-FPM 监听了127.0.0.1:9000,看起来没问题,但localhost在某些系统上解析成 IPv6 的::1,于是连接被拒绝。这类玄学问题,根源就是对“localhost 到底解析成哪个 IP”没有概念。

4. 高能预警:这些报错信息你必须看得懂

4.1 最经典的 502:connect() failed (111: Connection refused)

这是我遇到最多的错误,Nginx 错误日志里长这样:

2025/01/15 10:22:31 [error] 12345#0: *678 connect() failed (111: Connection refused) while connecting to upstream, client: 192.168.1.10, server: example.com, request: "GET /index.php HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000"

看到Connection refused,你不要先去怀疑代码,直接按下面的顺序查:

  1. 确认 PHP-FPM 进程是否存活:ps aux | grep php-fpm,如果没有任何进程,说明 PHP-FPM 崩了或被停掉了。
  2. 确认端口是否真的在监听:ss -tlnp | grep 9000,如果没有任何输出,说明 PHP-FPM 虽然活着,但没有监听 9000 端口,很可能是配置里写了 sock 路径,Nginx 却还在用 TCP。
  3. 确认 Nginx 的fastcgi_pass是否和 PHP-FPM 的listen完全一致:一个写端口,一个写 sock,必报此错。
  4. 如果是跨机器连接,还要确认防火墙是否放行端口、目标机器是否监听的是0.0.0.0或可访问的网卡 IP,而不是只监听127.0.0.1

4.2 全网都在问的端口冲突:error: listen tcp 127.0.0.1:11434: bind: only one usage of each socket addre

最近这个报错在不少社区热搜里出现,虽然它不是 PHP-FPM 的错误,而是某个基于 Go 的服务(比如本地模型服务)启动时报的,但原理和 PHP-FPM 端口冲突完全一致:

error: listen tcp 127.0.0.1:11434: bind: only one usage of each socket addre

这行英文直译是“每个套接字地址只能用一次”,在 Windows 上通常表现为bind: An attempt was made to access a socket in a way forbidden by its access permissions。核心原因就一个:11434 端口已经被某个进程占用了,新的进程无法 bind 同一个端口。

遇到这种情况,我的标准排查路线是:

# 1. 看谁占着这个端口 ss -tlnp | grep 11434 # 2. 如果本机没有输出,可能是 IPv6 地址冲突,加 -A 查看 ss -tlnpa | grep 11434 # 3. 找到 PID 之后,确认进程是不是你需要保留的服务 ps -fp <PID> # 4. 如果确认是可以关闭的旧进程,就 kill 掉;如果不是,就换端口 kill -9 <PID>

这个报错还有一个特殊来源:程序自己起了多个实例。比如你写了一个服务,代码里明确绑定 11434,然后你又手动启动了第二个进程,第二个进程就会报这个错。这时候不是端口被别的程序占,而是你自己重复启动了。

还有一种情况是TIME_WAIT状态引起的。TCP 连接关闭后,端口会进入TIME_WAIT状态,默认持续时间约 60 秒,如果服务立即重启且没有设置SO_REUSEADDR,就有可能在短时间内报 bind 失败。但作为 PHP-FPM 这种成熟的软件,一般不会犯这种低级错误,更多还是配置和进程管理问题。

4.3 权限问题:sock 文件 permission denied,最容易被小白的配置坑

如果 Nginx 错误日志里出现:

connect() failed (13: Permission denied) while connecting to upstream

ss -xl又能看到 sock 文件在监听,那大概率就是权限问题。你可以执行:

ls -l /var/run/php-fpm.sock

正常输出类似:

srw-rw---- 1 www-data www-data 0 Jan 15 10:22 /var/run/php-fpm.sock

注意,开头的s表示这是 Socket 文件。后面的rw-rw----表示所有者和所属组有读写权限,其他人没有。如果 Nginx 的 worker 进程用户是nginx,而这个 sock 文件属主和属组都是www-data,nginx 用户就没有权限,自然连不上。

解决办法有几种:

  • 把 Nginx 用户加入 www-data 组:usermod -aG www-data nginx,然后重启 Nginx。
  • listen.ownerlisten.group都改成nginx
  • listen.mode改成0666,允许所有人访问。我不推荐这样,虽然最容易解决,但增加了本机其他用户读取 PHP-FPM 通信内容的风险。

注意:改完权限配置后,不仅要重启 PHP-FPM,还要让 Nginx 重新加载配置,最好直接systemctl restart nginx,因为进程用户组变更后需要完全重启才生效。

4.4 混合杂症:502 的另一个“嫌疑人”是 timeout 和 buffering

如果你排除了连接问题,页面还是偶尔 502,可以看看 Nginx 日志里有没有upstream timed outupstream sent too big header

upstream sent too big header的意思是 FastCGI 返回的响应头超出了 Nginx 的fastcgi_buffer_size限制。一些框架(比如 Laravel、Symfony)会设置很多 Cookie 和 Header,一不小心就超了。解决办法是在 Nginx 的location ~ \.php$里调大 buffer:

fastcgi_buffers 16 16k; fastcgi_buffer_size 32k;

如果是慢请求导致超时,要检查fastcgi_read_timeoutrequest_terminate_timeout。PHP-FPM 默认的request_terminate_timeout通常是 0 或 30s,如果脚本执行超过这个时间,PHP-FPM 会主动杀掉进程,Nginx 就会报 502。很多支付回调、大数据导出接口都会踩这个坑。

5. 一个真实场景复盘:11314 端口被“抢”之后我做了什么

前面提到bind: only one usage of each socket addre这类报错,我拿一个真实例子复盘一下完整的排查过程,你以后遇到类似问题可以直接套用。

大概是上个月,我在一台测试服务器上部署一个本地 AI 推理服务,它默认监听 11434 端口。启动时终端直接报:

error: listen tcp 127.0.0.1:11434: bind: only one usage of each socket addre

第一反应是“11434 被占了”。于是我执行:

ss -tlnp | grep 11434

输出显示:

LISTEN 0 4096 127.0.0.1:11434 0.0.0.0:* users:(("ollama",pid=29813,fd=3))

原来是一个旧版本的 ollama 进程占着端口。我其实已经忘了之前手动启动过,那这次的目标服务虽然是新版,但只 bind 同一个 IP 端口,所以失败。

我没有直接kill -9,而是先确认这个旧进程是否还在被其他任务调用。查了一下没有业务依赖,于是正常停止:

kill 29813 sleep 2 ss -tlnp | grep 11434

发现端口已经释放,再启动新服务就成功了。

这里我想强调一个容易忽略的点:很多时候你不止遇到“端口被占”,还可能遇到“端口看起来没被占,但 bind 仍然失败”。这种情况要检查是不是不同协议栈的端口冲突,比如你监听0.0.0.0:9000[::]:9000是两个不同的地址,如果某个服务只绑了 IPv6 的 9000,在部分系统上 IPv4 的服务再 bind 9000 也会报错。解决办法是用ss -tlnpa看清楚所有地址族的监听情况。

还有一个我自己踩过的坑:在 Docker 容器里,容器内端口和宿主机端口是两套体系。如果你在宿主机上执行ss -tlnp | grep 9000,是看不到容器内 PHP-FPM 监听的;要进入容器检查。反过来,如果 Nginx 在宿主机上,PHP-FPM 在容器里,Nginx 无法通过127.0.0.1:9000访问到容器里的 PHP-FPM,必须使用宿主机 IP 或php-fpm:9000这样的 Docker 内部网络地址。很多人部署 LNMP 容器环境时出现“外部能连 Nginx,PHP 却一直报 504”,就是这个原因。

6. 我的个人结论和配置建议

如果现在让我去新装一套 PHP 环境,单机场景下我会直接用 Unix Domain Socket:

listen = /var/run/php-fpm.sock listen.owner = www-data listen.group = www-data listen.mode = 0660

Nginx 对应:

fastcgi_pass unix:/var/run/php-fpm.sock;

这样既避免了 TCP 连接建立的开销,也减少了端口被扫描的风险。

但如果是 Docker Compose、Kubernetes、或者 PHP-FPM 要单独拆分到多台机器的场景,我会毫不犹豫改成 TCP:

fastcgi_pass php-fpm:9000;

因为 UDS 根本无法跨主句,强行用反而是在给自己挖坑。

最后说一个我实践中的体会:很多问题不是出在“选 sock 还是选 TCP”,而是出在“改了某一侧配置,忘了同步另一侧”。无论你使用哪种方式,请务必在做完改动后同时检查两侧:

  • ss -xl看 UDS 是否在监听;
  • ss -tlnp看 TCP 端口是否在监听;
  • nginx -t && systemctl reload nginx确保 Nginx 配置已生效;
  • 随便请求一个 PHP 页面,确认返回不是 502。

养成这个习惯后,你会发现类似问题基本都能在一分钟内定位。

如果你现在正纠结要不要把 9000 改成 sock,我建议你按自己的架构来,不要纯为了性能做迁移。单机几千并发,UDS 和 TCP 的差距不明显;但如果哪天真上了高并发,你再回过头把listen改成 UDS,也就是改两行配置的事。最怕的是“知其然不知其所以然”,一遇到问题就在 Nginx 和 PHP-FPM 两边来回改,最后越改越乱。希望这篇文章能帮你真正把这两个 Socket 的脾气摸透。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 3:56:49

北京正规的弹簧支吊架制造厂家排名前五:客户真实体验口碑盘点

在北京找弹簧支吊架采购资源的时候&#xff0c;很多工程从业者都会搜这样几个问题。北京正规的弹簧支吊架制造厂家排名前五都有哪些?在北京做管道工程&#xff0c;怎么选靠谱的弹簧支吊架制造厂家?什么样的弹簧支吊架厂家&#xff0c;能适配各类工程的特殊工况需求?先来说第…

作者头像 李华
网站建设 2026/9/19 3:55:57

三步下载 DASH/HLS 流媒体:N_m3u8DL-RE 使用指南

三步下载 DASH/HLS 流媒体:N_m3u8DL-RE 使用指南 【免费下载链接】N_m3u8DL-RE Cross-Platform, modern and powerful stream downloader for MPD/M3U8/ISM. English/简体中文/繁體中文. 项目地址: https://gitcode.com/GitHub_Trending/nm3/N_m3u8DL-RE N_m3u8DL-RE 是…

作者头像 李华
网站建设 2026/9/19 3:55:46

搜索引擎用户满意度评估:从搜索日志到SUE指标的完整实战

简介&#xff1a;《搜索引擎用户满意度评估》PDF是一份面向Web开发、互联网搜索与信息检索技术的研究型文献&#xff0c;聚焦用户满意度这一搜索引擎性能评价的核心指标&#xff0c;适合搜索引擎研发人员、高校科研人员及中高级Web技术爱好者阅读。压缩包内含1个PDF文件&#x…

作者头像 李华