你的网站是不是总被异常请求偷袭,却抓不到“凶手”?

服务器突然变慢、页面打不开、后台报警响个不停……你冲过去翻日志,结果只看到一堆 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.lognginx/error.log;在 Kibana 里建个简单看板:按错误信息关键词(比如 Permission deniedSegmentation 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 foundUndefined 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监控启动步骤

别收藏吃灰,现在就打开终端,照着做:

  1. 打开你的 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 这行取消注释,确保它开着。
  2. 在服务器上执行
    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 filebeat
    
    (如果你用 CentOS,换 rpm 安装;Mac 用户直接 brew install filebeat
  3. 打开浏览器,访问你的站点,故意输一个不存在的路径,比如 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 里搜 error404,看这条错误是不是 10 秒内就刷出来了。

能看见,就说明管道通了。今晚睡前,你就能第一次亲手“听见”服务器在报警。