原生 PHP 应用在生产环境中如何防护和缓解 DDoS 攻击?
原生 PHP 应用应采用分层防护:由上游网络或具备 DDoS 防护能力的代理服务承接大流量,在 Apache/Nginx 层尽早拒绝异常请求,再由 PHP 按接口、用户等业务维度限流。使用 Laravel 或 Symfony 也不会改变这些防护层次。
PHP 限流可以减少数据库查询和业务运算,但请求执行到 PHP 时已经占用了服务器资源。如果攻击耗尽公网带宽,单靠 PHP 或本机防火墙无法恢复可用性,需要由托管商或上游防护服务处理。
1. 是否需要使用 CDN?
对于直接面向公网的生产应用,建议评估带有 DDoS 防护、WAF 和请求限流能力的 CDN 或反向代理服务。Cloudflare 是一种选择,是否使用应根据业务流量、部署地区和服务能力决定,没有某个特定产品是所有 PHP 应用都必须使用的。
接入时,还要处理源站访问:
- 确认网站流量实际经过代理。仅使用其 DNS 解析服务,不等于接入了流量防护。
- 在源站防火墙或安全组中限制 Web 端口的访问来源,只允许受信任的代理网络及必要的运维入口,并维护 IPv4、IPv6 规则。
- 按条件启用源站连接认证,检查其他域名和服务是否暴露了可绕过代理的访问路径。
- 优先缓存公开静态资源;涉及登录状态或个人资料的响应应明确设置缓存策略。
Cloudflare 的源站保护文档介绍了代理接入和限制源站访问的措施。只隐藏 DNS 中的源站地址,不能替代访问控制。
2. 先在 Nginx 层限制请求速率
如果 Nginx 位于 PHP 前方,可以先按客户端 IP 限流,减少进入 PHP 的请求。以下片段需要合并到现有配置中,数值仅用于说明:
http {
limit_req_zone $binary_remote_addr zone=per_ip:10m rate=5r/s;
server {
# 保留现有 listen、TLS、路由和 PHP 转发配置
limit_req zone=per_ip burst=10 nodelay;
limit_req_status 429;
}
}
这里每个 IP 的持续请求速率为 5r/s,并允许一定突发量。nodelay 使突发额度内的请求立即通过,超过允许范围的请求返回 429。具体行为见 Nginx limit_req 文档。
该示例在 server 层配置,会影响站点中继承规则的请求,包括静态资源。实际部署时,应根据页面资源数量和接口成本调整限流范围。登录、搜索、导出等接口通常需要各自设置限制。某个 location 一旦定义自己的 limit_req,就不会继续继承上层的 limit_req,所需规则必须明确配置。
上线前运行 nginx -t 检查配置,并观察正常用户是否被误限。还应根据业务设置请求体大小、读取超时和后端并发上限,避免慢请求、大上传或长时间执行的任务持续占用资源。
代理后的真实 IP 必须可信
接入 CDN 后,服务器直接看到的可能是代理节点 IP。如果没有正确恢复客户端 IP,按 IP 限流可能把大量正常用户算到同一个计数器中。
使用 Nginx 的 set_real_ip_from 指定可信代理地址,再通过 real_ip_header 指定对应请求头。不要信任任意来源提交的 X-Forwarded-For,也不要把所有公网地址加入可信代理范围。Nginx realip 文档说明了这些指令的信任规则。
如果后面还有 Apache 或其他代理,也要正确传递和校验客户端地址,确保 PHP 最终收到的 REMOTE_ADDR 符合预期。
3. Apache 可以配置哪些保护?
Apache 可以使用 mod_reqtimeout 限制请求头和请求体的读取时间,减少慢速发送请求造成的资源占用。例如,在服务器配置或虚拟主机中设置:
RequestReadTimeout header=20-40,MinRate=500 body=20,MinRate=500
使用前需要确认模块已加载。该配置根据接收速率调整读取时间,具体含义见 Apache mod_reqtimeout 文档。上传接口和慢速网络用户需要单独评估,示例中的阈值不一定适合所有业务。
还应根据内存和后端处理能力设置 MaxRequestWorkers 等并发限制。使用 PHP-FPM 时,也要匹配其进程池容量;盲目提高并发上限可能导致内存耗尽。如果前面已有 Nginx,可以把通用请求限流放在 Nginx,Apache 负责后续处理。
4. 原生 PHP 如何实现请求限流?
需要跨 PHP worker 或多台应用服务器共享计数时,可以使用 Redis,不要依赖 PHP 进程内变量。高并发请求下,如果每次访问都向 MySQL 写入限流记录,也可能增加数据库压力。
下面的示例使用 PhpRedis 扩展,限制某个接口的每个 IP 在一个 60 秒窗口内最多请求 60 次。窗口从该 IP 的第一次请求开始计算,计数和过期设置通过 Lua 原子执行。
示例假设 Redis 位于本机且已做好访问控制。实际部署时需补充所需的认证和连接配置,并将代码放在该接口的入口处,在数据库查询、昂贵运算和输出响应之前执行。
<?php
$limit = 60;
$window = 60;
// 使用服务器确认过的客户端地址,不直接读取任意转发请求头。
$ip = $_SERVER['REMOTE_ADDR'];
$key = 'rate:example-endpoint:ip:' . hash('sha256', $ip);
$script = <<<'LUA'
local count = redis.call('INCR', KEYS[1])
if count == 1 then
redis.call('EXPIRE', KEYS[1], ARGV[1])
end
return {count, redis.call('TTL', KEYS[1])}
LUA;
try {
$redis = new Redis();
if (!$redis->connect('127.0.0.1', 6379, 0.2)) {
throw new RedisException('Connection failed');
}
$redis->setOption(Redis::OPT_READ_TIMEOUT, 0.2);
$result = $redis->eval($script, [$key, $window], 1);
if (!is_array($result) || count($result) !== 2) {
throw new RedisException('Invalid limiter result');
}
$count = (int) $result[0];
$retryAfter = max(1, (int) $result[1]);
} catch (RedisException $e) {
// 此示例在限流服务异常时暂时拒绝请求。
http_response_code(503);
header('Retry-After: 1');
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['error' => 'Service temporarily unavailable']);
exit;
}
if ($count > $limit) {
http_response_code(429);
header('Retry-After: ' . $retryAfter);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['error' => 'Too many requests']);
exit;
}
// 继续处理业务请求。
将 INCR 和首次设置的 EXPIRE 放在同一个 Lua 脚本中,可以避免计数增加后未能设置过期时间的问题,参见 Redis INCR 的限流示例。
使用时还要考虑以下边界:
- 共享出口:公司网络、校园网和移动网络可能让多个用户共用一个 IP,阈值过低会误伤。
- 业务身份:完成身份验证后,可增加用户 ID、API key 等维度的限制。只按 IP 无法约束大量来源分散的攻击请求。
- 窗口突发:固定窗口在切换前后可能短时间放过较多请求;对突发敏感的接口可考虑滑动窗口或令牌桶。
- Redis 故障:示例选择返回
503。公开页面是否允许降级放行、敏感接口是否暂时关闭,需要根据业务决定,并配合告警。
Redis 需要连接和读取超时,也不应暴露在公网。这个限流器仍会消耗 PHP 和 Redis 资源,因此必须与前面的服务器层防护配合。
5. 如何过滤恶意流量并保护 MySQL?
尽量在执行昂贵操作前完成检查:限制 HTTP 方法、请求体大小、字段长度、分页大小和批量操作数量,并对搜索、导出等高成本功能设置单独配额。针对可疑请求,可以在边缘服务实施临时封禁或挑战验证。
IP 封禁适合处理明确、持续的异常来源,应设置有效期并留意误伤。攻击来源不断变化时,单纯维护 IP 黑名单效果有限;User-Agent、Referer 等客户端可控字段也不能作为可信身份依据。
数据库侧应减少单个请求的成本:检查索引和慢查询、限制返回行数、缓存可复用结果,并把耗时任务放入有并发上限的队列。预处理语句用于防范 SQL 注入,不能阻止大量合法格式的请求耗尽数据库资源。
6. 实际部署应先做什么?
对于这套技术栈,可以按以下顺序落地:
- 确认托管商的 DDoS 处理能力和应急入口,按需要接入边缘防护,并限制绕过代理直连源站。
- 在 Nginx/Apache 配置请求速率、读取超时、请求大小和并发限制。
- 对高成本接口加入 Redis 业务限流,明确限流服务故障时的处理方式。
- 监控入口流量、
429和5xx、PHP worker 占用、Redis 延迟及 MySQL 慢查询,在授权环境中进行受控压测并调整阈值。
Nginx 自带的限流模块、Apache 的 mod_reqtimeout,以及 Redis 配合 PhpRedis,可以组成一套基础工具组合。是否增加 WAF 或专门的上游防护,应根据攻击类型和实际容量决定。评估措施效果时,要看正常用户能否持续完成请求,以及后端资源是否仍有余量。
备注:内容仅供参考。
