你的网站是不是总被异常请求偷袭,却抓不到“凶手”?
服务器突然变慢、页面打不开、后台报警响个不停……你冲过去翻日志,结果只看到一堆 PHP Warning: Invalid argument supplied for foreach() 或 Connection refused,连谁干的、怎么干的、干了多久都搞不清。
别硬啃了——errorlog 不是天书,它是服务器在喊“有人闯进来了”,关键是你得听懂它在说什么。
为什么只看访问日志,根本抓不住狡猾的异常?
很多人盯着 access log 看:IP 是谁?URL 是啥?状态码多少?这没错,但 access log 只告诉你“谁敲了门”,errorlog 才告诉你“门被踹坏时发出的那声闷响”。
比如一个慢速扫描器,每分钟只发一次请求,全走正常路径,返回全是 200。access log 里它像位绅士,errorlog 里却满是 file_get_contents(): failed to open stream: No such file or directory——因为它正一遍遍试 /etc/passwd、/proc/self/environ 这些不该碰的地方。
再比如 SQL 注入试探。攻击者在登录框里输 ' OR 1=1--,如果后端没做参数过滤,数据库直接报错:SQLSTATE[42000]: Syntax error or access violation。这个错误不会出现在 access log 的状态码里,但它会狠狠砸进 errorlog。
你漏掉的不是日志,是攻击者留下的指纹。
第一步:给你的Errorlog“立规矩”,统一格式才好分析
乱糟糟的日志等于没日志。今天就动手,让每条 errorlog 至少说清五件事:什么时候出的事、严重到什么程度、谁发起的请求、请求了哪个地址、到底哪错了(最好带堆栈)。
Nginx 的 error_log 指令能调路径和级别,但真正管用的是应用层的日志配置。PHP 就别用 error_log() 直接打字符串了,换成 Monolog;Python Django 开启 LOGGING 配置,输出 JSON;Node.js 用 pino 或 winston,关掉彩色输出,打开结构化日志。
我们帮过一家电商团队,他们 PHP 日志全是这样的:
[23-May-2024 14:22:01 Asia/Shanghai] PHP Warning: array_merge(): Argument #2 is not an array in /var/www/app/OrderService.php on line 87
时间、错误、文件、行号全挤在一起,没法 grep,更没法导入工具。换成 Monolog 的 JSON 格式后,当天晚上就从日志里揪出一个反复触发 Too many connections 的爬虫 UA——之前它藏在几百行杂乱文本里,根本没人注意。
第二步:实时监控与告警,别等出了问题再翻旧账
日志不是存档,是哨兵。你不需要搭一整套 ELK,先让错误“跳到你眼前”。
最轻量的组合是:Filebeat(收日志) → Elasticsearch(存+搜) → Kibana(看)。三件套都能免费用,Docker 一条命令就能拉起来。
重点不在装,而在“通”。用 Filebeat 的 filestream 输入,指向你的 php-error.log 或 nginx/error.log;在 Kibana 里建个简单看板:按错误信息关键词(比如 Permission denied、Segmentation fault)分组统计,再加个“最近 15 分钟错误数趋势图”。
告警也别等复杂规则。就在 Kibana 里点几下:当 error.message: "Out of memory" 在 3 分钟内出现超过 3 次,立刻发 Slack 消息。不用写代码,不用配邮箱 SMTP,Slack Webhook 粘贴进去就行。服务器还没崩,你手机已经震了。
第三步:从海量错误中,精准定位真正的“异常请求”
不是所有报错都要追。盯紧这四类:
- 同一个 IP 在 5 分钟内报错 10 次以上
- 错误信息里带
eval(、base64_decode、../、union select这种词 - 错误伴随 access log 里的 400/403/500 状态码
- 应用刚上线就高频出现的
Class not found或Undefined index
关联分析比单看 errorlog 有用十倍。在 Kibana 里,把 access-log-* 和 error-log-* 两个索引按 client.ip 和 @timestamp 关联。你会发现:某个 IP 在 access log 里请求很“正常”,每 30 秒一次 /api/v1/user?token=xxx,但每次 token 都是错的——errorlog 里对应着一模一样的 Invalid API token 报错。这不是用户手滑,是程序在暴力扫密钥。
我们上周就靠这招发现一个伪装成安卓 App 的爬虫:它 User-Agent 写得特别真,但 errorlog 显示它反复触发 cURL error 7: Failed to connect,一查 access log,它只扫 /wp-login.php 和 /xmlrpc.php,真人谁这么干?
第四步:针对高频异常模式,设置主动防御规则
监控不是终点,是防守的起点。分析出规律,就该往前拦。
如果你在 errorlog 里反复看到 open(/etc/shadow): Permission denied,说明有人在试路径遍历。别等它成功——直接在 Nginx 配置里加一行:
if ($request_uri ~* "(\/\.\.\/|etc\/passwd|proc\/self)") {
return 403;
}
或者,用你正在用的云 WAF(阿里云、腾讯云、Cloudflare 都行),新建一条规则:URI 包含 ../ 或 /etc/ 就拦截。规则生效后,errorlog 里这类错误会断崖式下降,服务器负载也跟着松一口气。
再比如,你发现某 IP 半小时里触发了 20 次 password reset failed 的错误日志,大概率是撞库。这时用 Nginx 的 limit_req 就够了:
limit_req_zone $binary_remote_addr zone=auth:10m rate=5r/m;
limit_req zone=auth burst=10 nodelay;
限制每个 IP 每分钟最多 5 次密码重置请求。简单,有效,不用改一行业务代码。
今天下班前,就能完成的Errorlog监控启动步骤
别收藏吃灰,现在就打开终端,照着做:
- 打开你的 Nginx 配置(通常是
/etc/nginx/nginx.conf或/etc/nginx/conf.d/default.conf),找到error_log行,确认路径和级别(至少是warn)。然后顺手打开你的 PHP-FPM 配置(如/etc/php/8.1/fpm/php.ini),把error_log = /var/log/php/error.log这行取消注释,确保它开着。 - 在服务器上执行:(如果你用 CentOS,换
curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-8.13.4-amd64.deb sudo dpkg -i filebeat-8.13.4-amd64.deb sudo filebeat modules enable nginx php sudo systemctl enable filebeat && sudo systemctl start filebeatrpm安装;Mac 用户直接brew install filebeat) - 打开浏览器,访问你的站点,故意输一个不存在的路径,比如
https://yoursite.com/robots.txt.bak。然后立刻打开 Kibana(如果你还没装,用 Docker 跑一个:docker run -p 5601:5601 -e "ELASTICSEARCH_HOSTS=http://host.docker.internal:9200" docker.elastic.co/kibana/kibana:8.13.4),在 Discover 里搜error或404,看这条错误是不是 10 秒内就刷出来了。
能看见,就说明管道通了。今晚睡前,你就能第一次亲手“听见”服务器在报警。