
1. 项目概述当Nginx遇上PHP一场配置与排错的实战如果你刚把网站从Apache迁移到Nginx或者第一次尝试在Nginx上跑PHP应用大概率会在nginx.conf这个文件上卡壳。这太正常了我刚开始折腾的时候对着满屏的location、fastcgi_param也是一头雾水明明代码没问题浏览器却总是给我返回“File not found”或者直接下载PHP文件。这个项目就是一次完整的“Nginx配置PHP支持”的实战记录我会把从零开始的配置步骤、每一步背后的原理以及我踩过的所有坑和解决方案毫无保留地分享出来。这不仅仅是贴几行配置代码而是要让你彻底明白为什么这么配不这么配会出什么问题以及当问题出现时你该如何像老手一样快速定位。无论是用Mac上的MAMP ProWindows下的集成环境还是纯净的Linux服务器Nginx与PHP通常通过PHP-FPM进程管理器的协作原理是相通的。核心就在于Nginx本身不处理PHP代码它需要将PHP文件的请求“转发”给PHP-FPM进程去解析执行再把执行结果拿回来返回给用户。这个“转发”的桥梁就是我们在nginx.conf里要搭建的。接下来我会带你一步步拆解这个过程。2. 核心原理与架构拆解Nginx如何与PHP-FPM“握手”在动手改配置之前我们必须先搞清楚Nginx和PHP是怎么协同工作的。这能帮你从根本上理解每一行配置的意义而不是机械地复制粘贴。2.1 静态与动态请求的分流逻辑Nginx是一个高性能的HTTP和反向代理服务器它处理静态文件如图片、CSS、JS的速度极快。但当用户请求一个.php文件时Nginx发现自己“看不懂”PHP语法它就需要找帮手。这个帮手就是PHP-FPM。PHP-FPM是一个PHP的FastCGI进程管理器。你可以把它理解为一个常驻后台的、专门处理PHP脚本的“翻译官”团队。Nginx和PHP-FPM之间通过FastCGI协议进行通信这是一种高效、稳定的进程间通信方式。整个工作流程可以这样类比用户访问浏览器请求http://yourdomain.com/index.php。Nginx接收Nginx接收到这个请求。规则匹配Nginx查看配置文件发现这个请求的URI匹配到了处理PHP的location规则例如location ~ \.php$。请求转发Nginx不再尝试去文件系统找这个index.php文件的内容返回而是根据配置将请求的所有信息如请求方法、URI、参数、请求头等打包成FastCGI协议格式。FPM处理Nginx将这个打包好的请求通过Socket可以是Unix Socket文件或TCP端口发送给PHP-FPM进程。脚本执行PHP-FPM收到请求后找到对应的.php文件由PHP解析器执行其中的代码。结果返回PHP脚本执行完毕后生成HTML或其他内容。响应传递PHP-FPM将这个执行结果再次通过FastCGI协议返回给Nginx。最终响应Nginx将收到的内容作为HTTP响应体加上相应的HTTP头返回给用户的浏览器。所以我们的配置核心就是告诉Nginx两件事第一什么样的请求需要转发给PHP-FPM通过location匹配第二怎么找到并连接上PHP-FPM通过fastcgi_pass指令。2.2 关键配置模块解析在nginx.conf或其包含的server块中以下几个部分是关键server块定义一个虚拟主机监听特定的端口和域名。location块根据请求的URI指定不同的处理规则。我们将在这里区分静态文件和PHP文件。fastcgi_*系列指令用于构建和调整转发给PHP-FPM的FastCGI参数。其中最重要的是fastcgi_pass它定义了PHP-FPM的监听地址。一个最常见的误区是只配置了fastcgi_pass却忽略了传递正确的参数导致PHP脚本无法获取到诸如$_SERVER[‘SCRIPT_FILENAME’]这样的关键信息从而引发“Primary script unknown”或“File not found”错误。我们会在实操部分重点解决这个问题。3. 分步配置详解从零搭建NginxPHP环境假设我们已经在服务器上安装好了Nginx和PHP并包含了PHP-FPM。我们的网站根目录是/var/www/my_project/public我们希望所有请求都通过/var/www/my_project/public/index.php这个入口文件来处理这是现代PHP框架如Laravel、ThinkPHP的常见模式。3.1 基础server块配置首先我们打开Nginx的主配置文件通常位于/etc/nginx/nginx.conf或者更常见的做法是在/etc/nginx/conf.d/目录下创建一个独立的配置文件例如my_project.conf这样管理起来更清晰。以下是my_project.conf的基础内容server { # 监听80端口即HTTP默认端口 listen 80; # 设置服务器域名本地测试可用 localhost server_name myproject.local www.myproject.local; # 设置网站根目录这是所有相对路径的起点 root /var/www/my_project/public; # 设置默认索引文件Nginx会按顺序查找 index index.php index.html index.htm; # 字符集设置避免乱码 charset utf-8; # 核心配置处理静态文件 location / { # try_files 指令是处理前端控制器模式的关键 # 它会按顺序检查文件是否存在 # 1. $uri - 直接请求的文件如 /css/style.css # 2. $uri/ - 请求的目录如果以/结尾 # 3. 如果以上都不存在则将请求重写到 /index.php并附带上原始的查询参数$query_string try_files $uri $uri/ /index.php?$query_string; } # 核心配置处理PHP文件请求 location ~ \.php$ { # 安全设置如果请求的PHP文件不存在直接返回404防止恶意访问不存在的PHP文件触发FPM。 try_files $uri 404; # 将请求转发给PHP-FPM处理。这里使用Unix Socket性能通常优于TCP。 # 你需要确认你的PHP-FPM监听地址。常见路径如下 # Ubuntu/Debian: /run/php/php8.1-fpm.sock (版本号需替换) # CentOS/RHEL: /var/run/php-fpm/www.sock # 也可以使用TCP如 fastcgi_pass 127.0.0.1:9000; fastcgi_pass unix:/run/php/php8.1-fpm.sock; # 告诉FastCGI使用默认的索引文件 fastcgi_index index.php; # 设置FastCGI参数文件通常包含一组默认参数。这是关键的一步 fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; include fastcgi_params; } # 禁止访问隐藏文件以点开头的文件如 .git, .env, .htaccess 等提升安全性 location ~ /\. { deny all; access_log off; log_not_found off; } }3.2 关键指令深度剖析try_files $uri $uri/ /index.php?$query_string;这是实现“优雅链接”或“前端控制器”模式的核心。当用户访问/about时Nginx会先检查/var/www/my_project/public/about这个文件是否存在如果存在则直接提供。如果不存在再检查about/这个目录是否存在。如果都不存在则将请求内部重写为/index.php?$query_string。此时$_SERVER[‘REQUEST_URI’]仍然是/about但实际执行的脚本是/index.php。你的PHP框架如Laravel的路由器就可以根据/about来分配合适的控制器。fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;这是解决绝大多数404问题的关键SCRIPT_FILENAME这个参数会传递给PHP-FPM告诉它需要执行哪个具体的物理文件。$realpath_root是经过符号链接解析后的网站根目录绝对路径。$fastcgi_script_name是FastCGI请求的脚本名通常就是$uri但如果经过了try_files重写它可能是重写后的值如/index.php。这行配置确保了无论请求路径如何最终PHP-FPM都能找到正确的index.php入口文件来执行。很多默认的fastcgi_params文件里用的是$document_root$fastcgi_script_name这在根目录有符号链接时可能会出问题$realpath_root更可靠。include fastcgi_params;这行指令引入了一个独立的参数文件通常位于/etc/nginx/fastcgi_params。这个文件预定义了一组标准的FastCGI参数如REQUEST_METHOD,CONTENT_TYPE,QUERY_STRING等这些是PHP脚本正常运行所必需的$_SERVER超全局变量值的来源。永远不要省略这一行。3.3 检查配置与重启服务配置完成后千万不要直接重启Nginx务必先检查语法。sudo nginx -t如果输出syntax is ok和test is successful说明配置文件语法正确。然后重载Nginx配置平滑重启不影响在线连接sudo nginx -s reload # 或者使用systemd sudo systemctl reload nginx同时确保你的PHP-FPM服务正在运行并且监听地址与fastcgi_pass指令中的地址一致。# 检查PHP-FPM状态以php8.1-fpm为例 sudo systemctl status php8.1-fpm # 查看PHP-FPM监听的Socket或端口 sudo netstat -lnp | grep php-fpm # 或查看其配置文件中的 listen 指令通常在 /etc/php/8.1/fpm/pool.d/www.conf 中4. 实战问题排查手册我踩过的那些坑配置写完了服务重启了但访问网站还是出错。别慌下面是我总结的常见问题、原因及解决方案。4.1 问题一访问PHP文件直接下载而不是执行现象浏览器弹出下载框下载你请求的.php文件。根本原因Nginx没有将.php请求正确地转发给PHP-FPM。通常是因为处理PHP的location ~ \.php$块没有被执行或者fastcgi_pass指令配置错误地址不对或PHP-FPM没启动。排查步骤检查Nginx配置确认location ~ \.php$块存在且语法正确。特别注意如果PHP文件位于嵌套目录要确保父级location没有使用break或return指令中断了处理流程。检查PHP-FPM状态运行sudo systemctl status php-fpm或你的具体版本如php8.1-fpm确认服务是active (running)。检查监听地址核对fastcgi_pass的值。如果是Unix Socket检查Socket文件的路径和权限。确保Nginx的工作进程用户通常是www-data或nginx有权限读写这个Socket文件。ls -l /run/php/php8.1-fpm.sock # 如果权限不对可以在PHP-FPM池配置中修改 user 和 group使其与Nginx用户一致。查看错误日志Nginx和PHP-FPM的错误日志是定位问题的金钥匙。# Nginx错误日志通常在 /var/log/nginx/error.log sudo tail -f /var/log/nginx/error.log # PHP-FPM错误日志通常在 /var/log/php8.1-fpm.log 或 syslog中 sudo tail -f /var/log/php8.1-fpm.log访问出错的页面同时观察日志输出通常会看到明确的连接拒绝或权限错误信息。4.2 问题二返回 “File not found.” 或 “Primary script unknown”现象浏览器显示404错误或者Nginx/PHP-FPM日志中记录 “Primary script unknown” 错误。根本原因PHP-FPM收到了请求但找不到SCRIPT_FILENAME参数指定的文件。99%的情况是SCRIPT_FILENAME参数传递不正确。解决方案与深度解析 这是最经典、最折磨新手的问题。关键在于理解$document_root、$realpath_root和$fastcgi_script_name这几个变量的区别。检查SCRIPT_FILENAME的值在我们的配置中我们显式设置了fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;。这是最推荐的做法。为什么不直接用$document_root$document_root就是root指令设置的值。但如果你的root路径是一个符号链接Symbolic Link$document_root指向的是链接本身而不是链接指向的实际路径。这可能导致路径拼接错误。$realpath_root会解析符号链接得到绝对物理路径更安全。$fastcgi_script_name是什么当请求/index.php时它就是/index.php。当请求/about且被try_files重写到/index.php?$query_string后在FastCGI上下文中$fastcgi_script_name的值仍然是重写前的/about除非你使用了fastcgi_split_path_info等复杂指令。但因为我们最终要执行的是入口文件所以通常我们会在location ~ \.php$块里通过try_files $uri 404;或直接固定$fastcgi_script_name为/index.php对于单入口应用。我们的配置采用了第一种方式try_files $uri 404;确保了只有真实存在的.php文件才会进入这个location块此时$uri就是实际的文件路径如/index.php因此$fastcgi_script_name也是正确的。验证文件路径你可以在Nginx配置中临时添加一行来记录这个值帮助调试。location ~ \.php$ { ... # 临时添加记录到错误日志级别为notice error_log /var/log/nginx/debug.log notice; # 注意这个写法需要Nginx支持特定的日志模块更通用的方法是在配置中计算并记录到自定义变量但更简单的办法是直接计算。 # 实际上最直接的调试方法是写一个phpinfo文件。更简单粗暴的调试方法是创建一个info.php文件内容为?php phpinfo(); ?。访问这个文件在phpinfo输出的$_SERVER部分查找SCRIPT_FILENAME变量看它是否指向了正确的物理文件路径。检查文件权限确保PHP-FPM进程的运行用户在www.conf中由user和group指定有权限读取你的.php文件。通常需要至少r读权限。sudo -u www-data cat /var/www/my_project/public/index.php # 用PHP-FPM的用户身份尝试读取文件如果失败就是权限问题。4.3 问题三PHP脚本执行报错500 Internal Server Error现象浏览器显示500错误Nginx错误日志中可能有upstream prematurely closed connection等字样而PHP-FPM日志中则有具体的PHP语法错误或致命错误。根本原因PHP代码本身有错误或者PHP环境缺少必要的扩展。排查步骤首要查看PHP-FPM错误日志500错误的具体原因几乎总是记录在PHP-FPM的错误日志里。根据日志中的错误信息如Call to undefined function,syntax error来修复PHP代码。检查PHP配置某些函数如exec,mail可能被disable_functions列表禁用了。某些扩展如gd,pdo_mysql,mysqli可能没有安装或启用。可以通过phpinfo()页面或命令行php -m来检查。检查文件权限写操作如果你的PHP程序需要写文件、上传文件或生成缓存需要确保目标目录对PHP-FPM进程用户有写权限w。例如Laravel的storage和bootstrap/cache目录通常需要写权限。sudo chown -R www-data:www-data /var/www/my_project/storage sudo chown -R www-data:www-data /var/www/my_project/bootstrap/cache # 或者至少设置正确的组和权限 sudo chmod -R 775 /var/www/my_project/storage4.4 问题四CSS、JS、图片等静态文件无法加载404现象PHP页面能打开但样式全无浏览器开发者工具显示静态资源请求404。根本原因静态资源的请求也被try_files规则匹配并错误地重写到了index.php或者静态文件根本不存在于指定路径。排查与解决检查try_files规则我们的基础配置中location /块的try_files指令会先查找$uri静态文件找不到才重写到index.php。这本身是正确的。问题可能出在静态文件路径错误确认root指令设置的目录下确实存在请求的CSS/JS文件。例如请求/css/app.css那么物理路径必须是/var/www/my_project/public/css/app.css。别名Alias配置冲突如果你使用了alias指令来映射特殊路径需要确保规则精确不会干扰到其他请求。检查Nginx的静态文件类型处理Nginx默认通过mime.types文件来识别文件类型并设置Content-Type响应头。通常不需要额外配置。但如果你的静态文件位于非标准后缀的目录可以显式设置过期时间和禁用日志。location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg)$ { expires 1y; # 设置长期缓存 add_header Cache-Control public, immutable; log_not_found off; # 不记录404日志减少日志量 access_log off; }5. 高级配置与性能调优要点当基础功能跑通后可以考虑以下优化提升安全性和性能。5.1 安全加固配置隐藏PHP版本信息在php.ini中设置expose_php Off可以防止响应头泄露PHP版本。限制特定PHP文件的访问禁止直接访问一些敏感文件如composer.json,.env(Laravel),config.php等。location ~ /(composer\.json|\.env|config\.php) { deny all; access_log off; log_not_found off; }设置合理的PHP执行超时和内存限制在PHP-FPM池配置文件 (www.conf) 中调整request_terminate_timeout、request_slowlog_timeout和php_admin_value[memory_limit]避免脚本无限执行耗尽资源。5.2 性能优化配置调整PHP-FPM进程管理方式在www.conf中pm进程管理器可以设置为dynamic动态。根据服务器内存调整pm.max_children最大子进程数、pm.start_servers启动时进程数、pm.min_spare_servers最小空闲进程数和pm.max_spare_servers最大空闲进程数。一个简单的估算公式max_children ≈ 可用内存 / 单个PHP进程平均内存占用。启用OPcache在php.ini中启用并优化OPcache这是提升PHP性能最有效的手段之一它能将预编译的PHP字节码存储在共享内存中避免重复编译。Nginx缓存静态资源如上文所述为图片、CSS、JS等设置长的expires头利用浏览器缓存。调整Nginx和PHP-FPM的缓冲设置适当增加fastcgi_buffers和fastcgi_buffer_size可以改善处理大响应时的性能。location ~ \.php$ { ... fastcgi_buffers 16 16k; fastcgi_buffer_size 32k; ... }5.3 多项目配置与路径处理如果你在一个服务器上部署多个PHP项目最佳实践是为每个项目创建独立的Nginx配置文件放在/etc/nginx/conf.d/并使用不同的server_name域名来区分。每个项目有自己的root目录。对于使用框架的项目务必确保将Web根目录root设置为框架的public目录而不是项目的根目录。这是最重要的安全实践之一可以防止用户直接访问到app、config、vendor等敏感目录。整个配置过程从原理理解到细节调试是一个典型的“配置即代码”的运维场景。最宝贵的经验是永远信任日志而不是猜测。Nginx的error.log和PHP-FPM的slowlog/error_log是你最忠实的问题诊断伙伴。每次修改配置后养成先nginx -t再reload的习惯能避免很多不必要的服务中断。最后将稳定可用的配置文件进行备份和版本管理下次再遇到类似需求你就能从容应对了。