FrankenPHP 迁移中 IP 封禁需从 Nginx 配置下沉至 Caddy 的 matchers+respond 或 PHP 应用层;Caddy 不解析 deny/allow,须用 remote_ip 匹配并注意真实 IP 提取;复杂规则建议 PHP 层校验,同时需配置 trusted_proxies 防伪造。

ip_restricted 这类基于 IP 的封禁逻辑,在迁移到 FrankenPHP 时不能直接复用 Nginx 的 deny 指令或 PHP-FPM 层面的访问控制,因为 FrankenPHP 没有内置“IP 黑名单语法”,也不解析 Nginx 配置里的 allow/deny 块。
你得把封禁逻辑从 Web 服务器层下沉到应用层或 Caddy 中间件层,否则请求根本不会被拦住。
FrankenPHP 不认 Nginx 的 deny/allow 规则
FrankenPHP 启动后只读取 Caddyfile,它完全忽略 nginx.conf 里写的 deny 192.168.1.100; 或 allow 10.0.0.0/8;。这些配置在 FrankenPHP 进程里压根不生效——连解析都不会解析。
如果你只是把原 Nginx 配置复制进 Caddyfile,Caddy 会报错:unknown directive 'deny',或者静默跳过(取决于写法),导致本该被拦的 IP 畅通无阻。
用 Caddy 的 matchers + respond 实现等效封禁
Caddy 支持基于客户端 IP 的条件匹配,配合 respond 可以返回 403 或重定向。这是最贴近原意、且无需改 PHP 代码的平移方式。
-
match必须放在路由块内,不能写在全局块里;respond要紧随其后,顺序不能错 - 支持 CIDR 写法(如
192.168.0.0/16)、单 IP(203.0.113.5)、IP 范围(203.0.113.10-203.0.113.20) - 注意:Caddy 默认用
remote_ip,但如果你前端还套了 CDN 或反向代理(比如 Cloudflare),必须先用header X-Forwarded-For提取真实 IP,否则封的是 CDN 节点 IP
示例(封掉一段测试网段,同时放行内网):
立即学习“PHP免费学习笔记(深入)”;
yourdomain.com {
@blocked_ip {
remote_ip 192.168.1.0/24
not remote_ip 10.0.0.0/8
}
respond @blocked_ip "Forbidden" 403
php_server
}
PHP 应用层做二次校验更可靠
仅靠 Caddy 封禁适合简单场景;如果封禁规则复杂(比如按 User-Agent + IP 组合、动态黑名单、封禁后记录日志),建议把核心逻辑留在 PHP 里,用 $_SERVER['REMOTE_ADDR'] 判断,并统一走中间件或前置钩子。
- FrankenPHP 的
php_server会把原始请求头和地址透传给 PHP,$_SERVER['REMOTE_ADDR']是真实的(除非你额外配了reverse_proxy且没设trusted_proxies) - 不要依赖
$_SERVER['HTTP_X_FORWARDED_FOR']直接判断——它可被伪造;若必须用,需配合trusted_proxies白名单(在 Caddyfile 里配reverse_proxy时指定) - Laravel 用户可用
Illuminate\Http\Middleware\TrustProxies,ThinkPHP 6 用app/middleware/TrustProxy.php,确保REMOTE_ADDR解析正确
别漏掉 FrankenPHP 的 max_wait_time 和连接数限制
单纯封 IP 不等于防爆破。FrankenPHP 的 max_wait_time 和底层 Go net/http 的连接队列行为,会影响高频恶意请求的实际拦截效果。
- 默认
max_wait_time 10s,意味着一个被封 IP 如果发起长连接或慢速攻击,仍可能占着线程等待超时 - 建议搭配
num_threads保守设置(比如 CPU 核心数 × 1.5),避免恶意请求耗尽线程池 - 真要防 CC,还得加一层限流(Caddy 的
rate_limit插件或外部 Redis 计数)
deny 是静态指令,Caddy 的 respond 是条件响应,PHP 层的判断才是可控边界。三者选哪个,取决于你愿不愿意为安全多维护一层逻辑。



















