文档
一个 项目

php_fastcgi

一个有明确意见的指令,用于将请求代理到 PHP FastCGI 服务器,例如 php-fpm。

Caddy 的 reverse_proxy 能够为任何 FastCGI 应用提供服务,但此指令是专门为 PHP 应用量身定制的。此指令是一个方便的快捷方式,替代了更长的配置

它假定站点根目录下的任何 index.php 都充当路由器。如果这不是你想要的行为,可以重新配置 try_files 子指令 来修改默认的重写行为,或采用展开形式 作为基础并根据需要进行自定义。

除了下面列出的子指令外,此指令还支持 reverse_proxy 的所有子指令。例如,你可以启用负载均衡和健康检查。

大多数现代 PHP 应用在不添加额外子指令或自定义的情况下也能正常工作。 子指令通常只在某些边缘情况或旧版 PHP 应用中使用。

语法

php_fastcgi [<matcher>] <php-fpm_gateways...> {
	root <path>
	split <substrings...>
	index <filename>|off
	try_files <files...>
	env [<key> <value>]
	resolve_root_symlink
	capture_stderr
	dial_timeout  <duration>
	read_timeout  <duration>
	write_timeout <duration>

	<any other reverse_proxy subdirectives...>
}
  • <php-fpm_gateways...> 是 FastCGI 服务器的 地址。通常是 TCP 套接字或 unix 套接字文件。

  • root 设置站点的根文件夹。建议始终与 php_fastcgi 一起使用 root 指令,但当你的 PHP-FPM 上游使用与 Caddy 不同的根目录时覆盖此项会很有用(参见示例)。如果使用了 root 指令,默认值为该指令的值,否则默认值为 Caddy 的当前工作目录。

  • split 设置用于将 URI 拆分为两部分的子字符串。第一个匹配的子字符串将用于从路径中分离出“path info”。第一部分会附加上匹配的子字符串并被视为实际资源(CGI 脚本)名称。第二部分将被设置为 CGI 脚本可使用的 PATH_INFO。默认值:.php

  • index 指定要视为目录索引文件的文件名。这会影响展开形式中的文件匹配器。默认值:index.php。可以设置为 off 来在未找到匹配文件时禁用回退重写到 index.php

  • try_files 指定默认 try-files 重写的覆盖。详情见 try_files 指令。默认值:{path} {path}/index.php index.php

  • env 将额外的环境变量设置为给定值。可多次指定以设置多个环境变量。默认情况下,所有相关的 FastCGI 环境变量(包括 HTTP 头)已被设置,但你可以根据需要添加或覆盖变量。

  • resolve_root_symlink root 目录为符号链接(symlink)时,启用将其解析为实际值。有时这用作部署策略,通过简单地将符号链接切换到另一个目录中的新版本来实现部署。默认禁用以避免重复的系统调用。

  • capture_stderr 启用捕获并记录上游 fastcgi 服务器通过 stderr 发送的任何消息。默认以 WARN 级别记录。如果响应为 4xx5xx 状态,则改用 ERROR 级别。默认情况下,stderr 会被忽略。

  • dial_timeout 是一个 持续时间值,用于设置连接上游套接字时的等待时长。默认值:3s

  • read_timeout 是一个 持续时间值,用于设置从 FastCGI 上游读取时的等待时长。默认值:无超时。

  • write_timeout 是一个 持续时间值,用于设置向 FastCGI 上游发送时的等待时长。默认值:无超时。

由于此指令是对反向代理的带有意见的封装,你可以使用任何 reverse_proxy 的子指令来对其进行自定义。

展开形式

不带子指令的 php_fastcgi 指令等同于以下配置。大多数现代 PHP 应用都能很好地与此预设配合工作。如果你的应用不能,请随意借鉴此配置并根据需要进行自定义,而不是使用 php_fastcgi 快捷方式。

route {
	# 为目录请求添加尾部斜杠
	# 如果 "{http.request.uri.path}/index.php" 未出现在 try_files 列表中,则此重定向会自动被禁用
	@canonicalPath {
		file {path}/index.php
		not path */
	}
	redir @canonicalPath {http.request.orig_uri.path}/ 308

	# 如果请求的文件不存在,尝试索引文件并假定 index.php 始终存在
	@indexFiles file {
		try_files {path} {path}/index.php index.php
		try_policy first_exist_fallback
		split_path .php
	}
	rewrite @indexFiles {file_match.relative}

	# 将 PHP 文件代理到 FastCGI 响应者
	@phpFiles path *.php
	reverse_proxy @phpFiles <php-fpm_gateway> {
		transport fastcgi {
			split .php
		}
	}
}

说明

  • 第一部分处理请求路径的规范化。目标是确保指向磁盘上目录的请求实际上在请求路径上添加尾部斜杠 /,从而使该目录只有一个有效的 URL。

    仅当 try_files 子指令包含 {path}/index.php(默认行为)时,才执行此规范化。

    这是通过使用仅匹配那些不以斜杠结尾且在磁盘上映射到包含 index.php 文件的目录的请求匹配器来实现的。如果匹配,则执行带有尾部斜杠的 HTTP 308 重定向。例如,如果磁盘上存在 /foo/index.php,则会将路径为 /foo 的请求重定向到 /foo/(附加 / 以规范化为目录路径)。

  • 下一部分处理基于磁盘上是否存在匹配文件的路径重写。这也带有记住 .php 之后路径部分的副作用(如果请求路径中包含 .php)。这对于 Caddy 正确设置 FastCGI 环境变量非常重要。

    • 首先,它检查 {path} 是否为磁盘上存在的文件。如果是,则重写为该路径。这会本质上短路其余流程,确保对磁盘上确实存在的文件的请求不会被其他步骤重写(见下面的步骤)。例如,如果磁盘上有 /js/app.js 文件,则对该路径的请求将保持不变。

    • 其次,它检查 {path}/index.php 是否为磁盘上存在的文件。如果是,则重写为该路径。对于像 /foo/ 这样的目录请求,它会查找 /foo//index.php(会被规范化为 /foo/index.php),如果存在则将请求重写为该路径。当你在 webroot 的子目录中运行另一个 PHP 应用时,这种行为有时很有用。

    • 最后,它会始终重写到 index.php(对于现代 PHP 应用它几乎总是存在)。这允许你的 PHP 应用将任何不映射到磁盘文件的路径的请求交由 index.php 脚本作为入口点处理。

  • 最后一部分就是实际将请求代理到你的 PHP FastCGI(或 PHP-FPM)服务以实际运行 PHP 代码。请求匹配器仅匹配以 .php 结尾的请求,因此,任何不是 PHP 脚本且确实存在于磁盘上的文件将不会由此指令处理,而是会被下放处理。

单独使用 php_fastcgi 指令通常不足以完成所有工作。它几乎总是应与 root 指令 配合使用以设置磁盘上文件的位置(对于现代 PHP 应用,这可能是 /var/www/html/public,其中 public 目录包含你的 index.php),并与 file_server 指令 配合使用以服务那些未被此指令处理并被下放的静态文件(如 JS、CSS、图片等)。

示例

将所有 PHP 请求代理到监听在 127.0.0.1:9000 的 FastCGI 响应者:

php_fastcgi 127.0.0.1:9000

相同,但仅针对 /blog/ 下的请求:

php_fastcgi /blog/* localhost:9000

当使用通过 unix 套接字监听的 PHP-FPM 时:

php_fastcgi unix//run/php/php8.2-fpm.sock

root 指令 几乎总是用于指定包含 PHP 脚本的目录,而 file_server 指令 则用于提供静态文件:

example.com {
	root * /var/www/html/public
	php_fastcgi 127.0.0.1:9000
	file_server
}

在使用 Caddy 提供多个 PHP 应用时,每个应用的 webroot 必须不同,这样 Caddy 才能分别读取并提供静态文件,并检测 PHP 文件是否存在。

如果你使用 Docker,通常你的 PHP-FPM 容器会将文件挂载到相同的根目录。在这种情况下,解决方法是将文件挂载到 Caddy 容器的不同目录,然后使用 root 子指令 为每个容器设置根目录:

app1.example.com {
	root * /srv/app1/public
	php_fastcgi app1:9000 {
		root /var/www/html/public
	}
	file_server
}

app2.example.com {
	root * /srv/app2/public
	php_fastcgi app2:9000 {
		root /var/www/html/public
	}
	file_server
}

对于不使用 index.php 作为入口点的 PHP 站点,你可以回退为发出 404 错误。该错误可以使用 handle_errors 指令 捕获并处理:

example.com {
	php_fastcgi localhost:9000 {
		try_files {path} {path}/index.php =404
	}

	handle_errors {
		respond "{err.status_code} {err.status_text}"
	}
}