如何将 Caddy Server 与 PHP 结合使用
Caddy 服务器是一个模块化的现代 web 服务器平台,支持自动 HTTPS 证书、QUIC 和 HTTP/2、Zstd 和 Brotli 压缩、各种现代特性以及经典的 web 服务器功能,如可配置虚拟主机、URL 重写和重定向、反向代理等。
2020 年 5 月发布的当前版本 Caddy 2 对其配置语法、自动化、插件等进行了重大改进。
本文介绍了如何将 PHP与 Caddy web 服务器版本2系列集成,以及高级配置。它还将类似的配置与 Apache 和 Nginx 配置进行了比较,以简化从 Apache 和 Nginx 到 Caddy 的迁移。
服务器初始安装
Caddy 在许多操作系统和基于 Linux 的发行版中可用。Caddy 文档解释了如何安装 Caddy 并将其配置为随服务器启动而自动运行的服务/守护进程。
安装完 Caddy 后,就可以使用最小配置来配置 Caddy,如果存在静态文件,则可以为其提供服务,并将其他请求传递给 PHP-FPM。
example.com
本文示例中使用的域名,其源码位于/var/www/example.com
,其中的/var/www/example.com/public/index.php
是网络应用的入口。该应用所有静态资源保存在/var/www/example.com/public
目录下,不过应用的其他部分(包括源文件,Composer 的 vendor 目录,测试,composer.json 文件,npm 的node_modules
目录等)也位于/var/www/example.com
.
Caddy Server 提供了安全且高性能的默认配置,这使得用最少的配置进行配置变得容易。
当 Caddy 作为系统服务安装和配置时,可以使用默认的 /etc/caddy/Caddyfile
作为全局配置文件,并使用建议名称 /etc/caddy/sites
的子目录来包含各个站点的配置文件,类似于 Apache 和 Nginx 配置。
/etc/caddy
├── Caddyfile
├── config/
│ └── php-fpm.conf
└── sites/
└── example.com.conf
全局的 Caddyfile
可以指定全局配置,并引入 config/*
和 sites/*
目录,以包含额外配置。
Caddyfile
{
log default {
format console
output file /var/log/caddy/system.log
exclude http.log.access
}
}
import config/*
import sites/*
以上配置 Caddy 将系统日志写入到 var/log/caddy/system.log
文(但非 HTTP 请求日志),同时从 config
和 sites
目录中加载另外的配置文件。
运行、开启、停止及重载 Caddy Server
如果 Caddy 安装成 systemd
服务,systemctl
命令可用于运行、开启、停止及重载 Caddy 服务器。
对于 ad-hoc 配置,服务器可以使用 caddy
命令进行控制:
systemctl start caddy
caddy start
caddy run # Starts server and blocks indefinitely
systemctl stop caddy
caddy stop
systemctl reload caddy
caddy reload
systemctl restart caddy
caddy stop && caddy start
将 Caddy 与 PHP-FPM 集成
类似于 Apache web 服务器和 Nginx 与 PHP 的集成,Caddy 也可以使用 Caddy 的 FastCGI 反向代理,与 PHP 集成。
其基本思想是,当 Caddy 收到一个应该用 PHP 处理的请求(例如,对扩展名为 .php
的文件名的请求)时,该请求被发送到 PHP-FPM,在那里执行 PHP 应用,并将响应发送回 Caddy 以返回给用户。
最简单地说,以下是一个功能齐全的 Caddy 站点定义:
/etc/caddy/sites/example.com.conf
example.com {
root * /var/www/example.com/public
log {
output file /var/log/caddy/example.access.log
format console
}
# Encode responses in zstd or gzip, depending on the
# availability indicated by the browser.
encode zstd gzip
# Configures multiple PHP-related settings
php_fastcgi unix//run/php/php-fpm.sock
# Prevent access to dot-files, except .well-known
@dotFiles {
path */.*
not path /.well-known/*
}
}
上面的配置文件是一个最小的完整配置文件,它负责几个安全和性能方面。
有关完整指令列表的完整解释以及此处使用的指令的详细信息,请参阅优秀的 Caddy 文档。
Caddy 用于 PHP 的额外配置: php_fastcgi
Caddy 使用额外的配置使之与 PHP 轻松集成。php_fastcgi
指令是多个配置选项的缩写,它将请求传递给 PHP-FPM,尝试从即时目录加载一个 index.php
,并最终将所有请求重写到根 index.php
。这种模式通常被称为“前端控制器模式”,可用于绝大多数 PHP 框架和 CMS,包括 Laravel、Drupal、WordPress,Slim PHP 等。
php_fastcgi
最简单的方式是为 PHP-FPM 服务器提供一个参数。它可以是服务器地址和端口(如 127.0.0.1:9000
),也可以是 Unix 域套接字地址。在 Debian/Ubuntu/derivatives 和 RHEL/Fedora/derivativals 上,它几乎总是作为 Unix 套接字可用,通常采用路径模式 /run/php/php[VERSION]-fpm.sock
。例如,对于 php 8.2,套接字地址为 /run/php/php8.2-fpm.sock
,而 php 8.3 的套接字地址为 /run/php/php8.3-fpm.sock
。
如果 Unix 域名 socket 不可用,请使用 IP 地址或者端口名。
PHP-FPM 监听的 Unix 域名 socket 或者 IP/端口可以使用 PHP-FPM 配置文件进行配置。
URL 重定向到 index.php
默认情况下,php_fastcgi
包含了三种 URL 重写:
1. 如果文件存在,将请求重写到 ./index.php
文件
如果请求到达 example.com/test
并且存在 test/index.php
文件,Caddy 将会将请求重写到 test/index.php
文件。这解决了“尾部斜杠问题”,即 PHP 应用程序存在于文档根目录的子目录中。
2. 如果文件不存在,重定向到 index.php
使用 php_fastcgi
配置的第二件事是, 如果文件存在,Caddy 将会尝试使用该文件为请求提供服务。例如,如果用户请求 example.com/image.png
,并且文档根中存在一个名为 image.png
的文件,Caddy 将其作为一个文件,而根本不调用 PHP。
然后,如果文件不存在,它会尝试将其路径作为目录寻找 index.php
文件,其后再尝试重定向到根目录的 index.php
。
这一步类似于以下 Apache 配置:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]
... 以及以下的 Nginx 配置:
try_files $uri $uri/ /index.php?$query_string;
3. 传递 .php
文件给 PHP-FPM
最后,php_fastcgi
指令将 .php
文件路由到指定的 FPM 服务器地址。Caddy 如何正确设置 FPM 参数,分割路径,并使用经过深思熟虑的默认值执行其他几个“切换任务”。
php_fastcgi
指令是多个配置选项的快捷方式。它可以重写特定的参数,或者如果不适用于特定用例时,可以使用 Expanded Form 进行颗粒度配置。
使用重定向服务单独的 PHP 文件
如果不需要将所有的请求重定向到根目录的 index.php
文件,Caddy 可以配置将所有 .php
文件传给 PHP-FPM,而无需重定向:
route {
# Add trailing slash for directory requests
@canonicalPath {
file {path}/index.php
not path */
}
redir @canonicalPath {http.request.orig_uri.path}/ 308
# If the requested file does not exist, try index files
@indexFiles file {
try_files {path} {path}/index.php
split_path .php
}
rewrite @indexFiles {file_match.relative}
# Proxy PHP files to the FastCGI responder
@phpFiles path *.php
reverse_proxy @phpFiles <php-fpm_gateway> {
transport fastcgi {
split .php
}
}
}
该配置几乎与扩展表单完全相同,不过不重定向到基础的 index.php
文件:
# If the requested file does not exist, try index files
@indexFiles file {
- try_files {path} {path}/index.php index.php
+ try_files {path} {path}/index.php
split_path .php
}
rewrite @indexFiles {file_match.relative}
性能微调
Caddy 内置了一些性能改进,并在默认情况下进行了微调。
例如,如果浏览器在其请求标头中指示浏览器可以处理响应,encode zstd zip
指令会使 Caddy 将响应编码为 zstd
或 gzip
。Caddy 默认情况下还会发送 Vary:Accept-encoding
头,因此 CDN 和其他缓存知道不要通过 Accept-encoding
对缓存进行分段。正是这样的小事让 Caddy 成为了一个现代化的、可选的服务器,具有良好的默认值。
此外,Caddy v2.7 默认启用了一些出色的性能和安全功能,包括 HTTP/3 和 TLS 1.3 支持、OCSP 装订支持(因此浏览器不必查询 OCSP 服务器来检查证书有效性)、自动 Alt-Svc 标头、双 RSA+ECC 证书等。
使用 PHP Curl 扩展发送 HTTP/3 请求
How to make HTTP/3 HTTP requests using PHP Curl extension, along with how to compile Curl with HTTP/3 support for PHP.
对于 PHP,可以进行一些额外的微调:
快速 404 页面
如果请求 URI 是 php 应用程序不处理的静态文件,那么有时缩短并立即结束响应是有意义的,而不是将所有请求重写到 index.php
文件。
以下是一个示例片段,它匹配某些扩展名(如 .jpg
、.png
、.woff2
等)的传入请求,如果不存在此类文件,则立即返回一个未找到页面的响应。这可以防止不必要地调用 PHP(可能更昂贵,而且通常还需要数据库连接),结果却只是在应用中生成 page-not-found 错误。
@static_404 {
path_regexp \.(jpg|jpeg|png|webp|gif|avif|ico|svg|css|js|gz|eot|ttf|otf|woff|woff2|pdf)$
not file
}
respond @static_404 "Not Found" 404 {
close
}
Cache Header
Apache web 服务器带有一个名为 Expires (mod_expires
) 的模块,它提供多个指令来提供 Cache-Control
和 Expires
标头。Expires
标头已过期,使得 Cache-Control
标头来决定浏览器用以控制其请求的缓存。
尽管 Caddy 不提供专门的模块或者一套指令对此进行设置,也可以使用现有的 header
指令来发送 Cache-Control
标头:
@static {
path_regexp \.(jpg|jpeg|png|webp|gif|avif|ico|svg|css|js|gz|eot|ttf|otf|woff|woff2|pdf)$
}
header @static Cache-Control "max-age=31536000,public,immutable"
上面代码片段将发送 Cache-Control: max-age=31536000,public,immutable
到所有以 jpg/jpeg/png
等结尾或者其他静态文件类型的请求中。与 Fast 404 一起,这些微调将使 Caddy 在使用浏览器/CDN 缓存的同时,更快、更高效提供的静态文件服务。
match
指令
Caddy' 的match
指令可用于基于响应标头设置标头。
安全微调
Caddy 的一个重要的特性是,支持自动 HTTPS。这包括从 LetsEncrypt 和 ZeroSSL 等证书颁发机构获得有效证书(使用 ACME 协议),以及自动 HTTPS 重定向。
它还为 TLS 交换曲线和密码套装设置了一组高度平衡和安全的配置值,同时继续确保默认配置始终是安全的。
如果希望通过其他方式获取、验证和续订证书,则可以关闭自动 TLS 证书并重定向。由于 Caddy 总是使用合理和安全的默认值进行配置,因此不建议更改默认的 TLS/HTTPS 相关配置选项。
Security Headers
网站可以进行的最简单、最有效的安全调整之一是在应用程序中发送额外的安全标头。这可以是功能强大且细粒度的标头(如 CSP 和Permissions Policy),也可以是 HSTS 和 X-Content-Type-Options
等标头。
下面显示了 Caddy 配置文件设置的一组可选的的安全标头。下面的例子很可能不适用于任何真实的网站,但这里只是一个起点:
header {
Strict-Transport-Security "max-age=31536000;includeSubDomains;preload"
X-Frame-Options "SAMEORIGIN"
X-Xss-Protection "1;mode=block"
Referrer-Policy "no-referrer-when-downgrade"
X-Content-Type-Options "nosniff"
Permissions-Policy "autoplay=(self),camera=(),geolocation=(),microphone=(),payment=(),usb=()"
?Content-Security-Policy "default-src 'self';script-src 'self';style-src 'self'"
}
Caddy 也支持标头前缀比如
?
,它告诉 Caddy ,只有在该 header 还未设置时发送该 header,或者-
如果存在标头时删除。
Cookie SameSite 标志
使用 Caddy 强大的 header 修改特性,可以改进由应用设置的 HTTP cookie 的安全性:
header >Set-Cookie (.*) "$1; SameSite=Lax;"
请注意,PHP 应用使用 PHP session,建议使用内置的 PHP INI 设置进行 SameSite Cookie 设置。
限制请求方法
如果 PHP 应用不是为了处理某些特定 HTTP 请求方法而设计的,或者只是根本不需要处理某些 HTTP 方法,则可以允许列出 HTTP 请求方法,从而使 Caddy 拒绝所有其他请求类型:
@requestMethodsList {
not method GET HEAD POST OPTIONS
}
respond @requestMethodsList "Not Allowed" 405 {
close
}
这类似于 Apache 的 AllowMethods
指令:
<Location />
AllowMethods GET HEAD POST OPTIONS
</Location>
... 以及 Nginx 的 limit_except
:
limit_except GET HEAD POST OPTIONS { deny all; }
生产就绪
有些额外的微调有助于 PHP,并让 Caddy 指令与 PHP 应用对齐。
信任代理
当 Caddy 在代理、负载均衡器或 CDN 后面时,从 PHP 应用和 Caddy 本身监听到的客户端的 IP 地址将设置为上一层的 IP 地址,而不是真正的客户端。
这可以通过设置代理/负载均衡/CDN 的静态 IP 地址来解决,使 Caddy 验证源 IP 地址是否在可信代理列表中,并在(可配置的)X-Forwarded-For
标头中使用客户端 IP 地址。
例如,如果存在 IP 地址为 192.168.1.16 的负载均衡:
trusted_proxies static 192.168.1.16
由于 Caddy 负责实际验证,PHP 应用可以信赖该客户端 IP 地址,而无需再次验证。
请求 Body 大小及 PHP 上传限制 {#request-body}
Caddy 支持选择性地强制执行 HTTP 请求的最大正文大小限制。如果这个值小于 PHP 的 post_max_size
(这反过来又掩盖了upload_max_filesize
),Caddy 将在将请求交给 PHP 之前终止请求。
request_body {
max_size 20MB
}
设置 request_body
max_size
可以帮助减少请求超过 post_max_size
时,PHP 偶发的预期外行为。
模块化和可复用配置
当单个 Caddy 实例服务多个网站时,Caddy 不仅支持引入配置文件(如上显示在主 Caddyfile
中),也支持引入单独区域。
/etc/caddy/config/php.conf
(php83) {
php_fastcgi unix//run/php/php8.3-fpm.sock
}
(php82) {
php_fastcgi unix//run/php/php8.2-fpm.sock
}
单独区域 (如本例的 (php83)
和 (php82)
)可用于任何其他配置文件中:
/etc/caddy/sites/example.com.conf
example.com {
root * /var/www/example.com/public
import php83
# ...
}
总结
Caddy 是一款现代 web 服务器,具有合理、快速和安全的默认配置。它支持 HTTP/3 和 TLS 1.3 开箱即用,自动 HTTPS 和证书寿命管理,并与 PHP 集成良好。
Caddy 可以使用最常见的“前端控制器”重写与 PHP 集成,也可以通过 PHP-FPM 为单个 PHP 文件提供服务。
当通过额外的性能调整和安全调整进行微调时,Caddy 可以通过额外的安全和缓存标头、Fast 404 页面和其他最佳实践来提供动态和静态内容。此外,Caddy 中的请求限制和可信代理可以配置为与 PHP 应用程序相匹配,以缓解 PHP 的一些不可预测的行为,并简化某些任务,如强制执行可信代理 IP 地址。