导读:本期聚焦于Robin创作的《如何解决 Docker 中 Nginx 无法正确代理 PHP-FPM 的问题》,敬请观看详情。把 Nginx 和 PHP-FPM 分容器部署后,页面频繁出现 502 错误或者浏览器直接下载 php 文件,多半是 FastCGI 通信配置有误。两个服务若不在同一网络,Nginx 用 localhost 根本访问不到 FPM 容器。正确的做法是在 docker-compose 中建专用 bridge 网络,Nginx 的 fastcgi_pass 应写容器名加 9000 端口。另外,FPM 的 listen 必须设为 0.0.0.0:9000 而非 unix socket,否则跨容器无法连通。脚本路径也要保持一致,避免 primary script 找不到。理清这些点,代理故障就能快速排除。

在 Docker 容器环境中,Nginx 与 PHP-FPM 通常被拆分到两个独立容器中运行,这样既便于扩展,也能让不同服务使用不同镜像。但很多开发者在完成容器编排、启动服务后,访问 PHP 页面时却遇到 502 Bad Gateway,或者浏览器没有执行 PHP 脚本,而是直接下载 index.php 文件。这些现象并不代表业务代码有问题,更多时候是因为容器间的网络连接没有打通、FastCGI 参数配置不正确,或者两个容器内的文件路径不一致。下面从容器网络、FPM 监听地址、脚本路径映射以及权限日志四个方面,逐一说明如何定位和解决这类故障。

如何解决 Docker 中 Nginx 无法正确代理 PHP-FPM 的问题

一、容器网络与 fastcgi_pass 地址

出现 502 的最常见原因是 Nginx 配置里使用了 fastcgi_pass 127.0.0.1:9000;。在传统单机部署中,Nginx 和 PHP-FPM 位于同一台主机,回环地址可以正常通信。但在 Docker 容器化架构下,每个容器都有独立的网络命名空间,127.0.0.1 只代表 Nginx 容器自身,不会指向运行 PHP-FPM 的另一个容器。因此,如果仍然使用回环地址,Nginx 的连接请求只能到达自己内部,PHP-FPM 根本收不到任何数据,最终返回 502。

解决这个问题需要将两个容器放入同一个自定义 bridge 网络,并且让 Nginx 通过服务名或容器名来访问 PHP-FPM。在 docker-compose 中,可以声明一个 networks 区块,比如名为 appnet,然后将 nginx 和 php-fpm 服务都加入该网络。此时 Nginx 的 fastcgi_pass 应该写作 php-fpm:9000,这里的 php-fpm 是 compose 中的服务名。Docker 内置的 DNS 服务会把服务名解析为对应容器的 IP 地址,即使容器重启后 IP 发生变化,也能自动更新解析结果。

如果使用 docker run 手动启动容器,则需要先创建网络,例如 docker network create mynet,然后启动两个容器时都添加 --network mynet 参数,并且确保 PHP-FPM 容器的名称与 Nginx 配置中 fastcgi_pass 使用的主机名一致。网络建立后,可以进入 Nginx 容器,用 getent hosts php-fpmping php-fpm 来验证名称解析是否正常。网络层连通是后续所有配置生效的前提。

version: '3'
services:
  nginx:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - ./code:/var/www/html
    networks:
      - appnet
  php-fpm:
    image: php:fpm-alpine
    volumes:
      - ./code:/var/www/html
    networks:
      - appnet
networks:
  appnet:
    driver: bridge

二、PHP-FPM 监听地址配置

官方提供的 php:fpm 镜像默认会监听 9000 端口,并且绑定到 0.0.0.0,这意味着容器外可以通过容器 IP 或服务名访问,符合跨容器通信的需求。但很多开发者会挂载自定义的 www.conf 配置文件,其中可能包含 listen = /run/php/php-fpm.sock 这样的 Unix socket 配置。Unix socket 主要用于同一主机内进程间通信,在 Docker 容器之间是完全无法使用的,因为每个容器有独立的文件系统视图,/run/php/php-fpm.sock 只存在于 PHP-FPM 容器内部,Nginx 容器无法访问该路径,最终导致连接被拒绝。

要修复此类问题,需要把 PHP-FPM 的监听地址改为 TCP 套接字,即 listen = 0.0.0.0:9000。同时检查 listen.allowed_clients 是否限制了来源地址,如果只允许特定 IP 访问,则应根据实际情况放行,或者直接设置为 any 以便在开发环境中快速验证。修改完成后需要重启 PHP-FPM 容器使配置生效。

启动后不要立即测试页面,而是先从网络层确认端口连通性。可以执行 docker exec 进入 Nginx 容器,使用 nc -zv php-fpm 9000telnet php-fpm 9000 查看连接是否被接受。如果返回成功,说明 Nginx 容器已经可以通过服务名访问到 FPM 的监听端口,网络与监听配置基本就绪。否则需要回到网络配置和 FPM 监听参数继续排查。

; PHP-FPM 池配置片段
[www]
listen = 0.0.0.0:9000
listen.allowed_clients = any
user = www-data
group = www-data

三、Nginx FastCGI 脚本路径映射

当网络连通、端口监听正常后,如果仍然出现 Primary script unknown 错误,通常是因为 FastCGI 脚本路径映射错误。Nginx 接收到 PHP 页面请求后,会通过 FastCGI 协议把请求转发给 PHP-FPM,同时传递一个名为 SCRIPT_FILENAME 的参数,这个参数指示 PHP-FPM 应该在哪个文件系统路径下查找并执行对应的 PHP 文件。由于 Nginx 和 PHP-FPM 是两个独立容器,它们分别拥有各自的文件系统,因此 Nginx 配置中的 root 路径和传递给 FPM 的脚本路径必须与 PHP-FPM 容器内实际代码存放位置完全一致。

常见的正确配置是,宿主机上的代码目录同时挂载到 Nginx 和 PHP-FPM 容器的 /var/www/html 路径。Nginx 的 root 设置为 /var/www/html,并在处理 PHP 的 location 块中使用 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 这样的写法,让 Nginx 自动把根路径和脚本名拼成完整路径。也可以直接写死 fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name;,但使用 $document_root 更容易维护。

需要特别注意的是,两个容器对代码目录的挂载路径必须完全相同。如果只给 Nginx 挂载了代码目录,而没有给 PHP-FPM 挂载,或者一个挂载到 /var/www/html,另一个挂载到 /app/code,就会导致 PHP-FPM 在预期路径下找不到文件,从而返回脚本未知错误。另外,在 Nginx 中配置 try_files 可以避免 index.php 被当作普通文件下载,确保所有无法匹配静态文件的请求都交给 index.php 处理。

server {
    listen 80;
    root /var/www/html;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ .php$ {
        fastcgi_pass php-fpm:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

四、权限与日志排查

如果网络、监听、路径这三项配置都正确,但页面仍然无法正常加载,就需要查看日志来定位更深层的问题。Nginx 的错误日志通常位于 /var/log/nginx/error.log,在容器环境中也可以直接使用 docker logs nginx_container_name 查看标准输出或文件日志。PHP-FPM 的日志可以查看容器输出,或者进入容器检查 FPM 日志文件。日志中通常会给出明确的错误原因,例如权限拒绝、文件不存在、上游连接超时等。

权限问题是容器化 PHP 部署中容易被忽视的一环。PHP-FPM 默认使用 www-data 用户运行,如果宿主机挂载的代码目录或文件权限设置过严,例如目录为 700 或文件为 600,并且所有者不是 www-data,FPM 进程就会因为无法读取脚本而报错。一种常见的修复方式是把宿主机上的代码目录权限设置为 755,文件权限设置为 644,或者在 compose 文件中通过 user: "1000:1000" 指定 FPM 进程以宿主机当前用户的 UID 和 GID 运行,从而与挂载目录的所有者保持一致,避免因权限不匹配导致读取失败。修改后需要重启 PHP-FPM 容器:

docker compose restart php-fpm

重启后再次查看日志,如果原来的权限错误消失,说明问题已经解决。若仍然存在,可以进入容器手动检查目录和文件权限:

docker exec -it php_fpm_container sh -c 'id && ls -l /var/www/html'

需要注意,如果宿主机启用了 SELinux,容器访问挂载目录时还可能受到安全上下文限制。此时可以在 compose 文件的卷挂载末尾添加 :z:Z,让 Docker 自动重新标记目录,例如:

volumes:
  - ./code:/var/www/html:z

不过 :z 会共享标签,:Z 则更严格地私有化,使用时需要根据业务场景选择。

在 Nginx 的错误日志中,以下几条信息可以帮助快速判断:

FastCGI sent in stderr: "Primary script unknown"
connect() failed (111: Connection refused) while connecting to upstream
upstream timed out (110: Connection timed out) while reading response header from upstream

Primary script unknown 通常表示 Nginx 传递给 PHP-FPM 的脚本路径错误,fastcgi_param SCRIPT_FILENAME 中的 $document_root 与 PHP-FPM 容器内的实际路径不一致。Connection refused 多出现在 PHP-FPM 容器未启动、未监听 9000 端口,或者 Nginx 配置的 fastcgi_pass 地址指向了错误的主机或端口。upstream timed out 则与 PHP 脚本执行时间过长或后端响应缓慢有关,可以适当调大 fastcgi_read_timeout,同时检查 PHP 脚本是否存在死循环或外部请求阻塞。

五、容器网络与最终检查

如果日志中没有明显错误,但请求仍然异常,还要确认容器之间的网络是否正常。使用自定义网络时,容器名可以直接作为主机名解析。可以通过以下命令测试 Nginx 容器到 PHP-FPM 容器的连通性:

docker exec -it nginx_container_name sh -c 'nc -zv php-fpm 9000'

如果输出 succeeded 或端口开放,说明网络链路正常;如果解析失败,则需要检查两个容器是否加入了同一个 Docker 网络,或者 compose 文件中是否正确定义了 depends_onnetworks

至此,从容器状态、端口监听、路径映射、权限日志到网络连通性的排查思路已经形成闭环。日常维护中建议将 Nginx 与 PHP-FPM 的配置纳入版本控制,并在修改后通过 docker compose config 检查配置语法,再用 docker compose up -d 平滑重建容器。遇到 502、404、Permission denied 等问题时,先看容器状态,再看日志,最后核对路径与权限,通常能快速定位并解决大部分 PHP 容器化部署问题。

DockerNginxPHP-FPM修改时间:2026-08-10 04:51:12

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。