对于个人站长来说,Nginx 的访问日志往往是最容易被忽视、却又最有价值的运维资产。平时它只是磁盘上一个不断变大的文本文件,但一旦网站出现异常流量、被人恶意刷接口、或者页面突然变慢,日志就是还原真相的第一手资料。这篇文章从一个草根站长的实际运维经验出发,把 Nginx 访问日志的格式定制、轮转切割、分析与排障完整过一遍,全部都是生产环境可以直接照抄的配置。
一、先认识 Nginx 的日志配置
Nginx 的日志分为访问日志(access log)和错误日志(error log)两类,分别在 http、server、location 三个层级都可以配置,遵循就近覆盖原则。默认情况下,访问日志写在 /var/log/nginx/access.log,错误日志写在 /var/log/nginx/error.log。日志是否记录、记录到什么格式,由 access_log 指令决定,而格式本身由 log_format 指令定义。
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent"';
access_log /var/log/nginx/access.log main;
}上面这段是 Nginx 编译安装后的默认配置。很多人直接用默认配置跑了好几年,等到需要排查问题时才发现,默认格式里缺少了最关键的信息,比如请求耗时 request_time、上游响应时间 upstream_response_time、请求方法、协议版本等。所以第一步,建议先根据自己的需要定制日志格式。
再来说 error_log。它记录的是 Nginx 运行过程中的错误信息,包括配置错误、连接超时、上游不可达等,排障价值同样很高。error_log 可以指定日志级别,从低到高依次是 debug、info、notice、warn、error、crit、alert、emerg,生产环境一般用 error 级别,既能过滤掉大量无意义的调试信息,又不会漏掉关键错误。注意 debug 级别会记录非常详细的信息,同时也会严重影响性能,只在排查疑难问题时临时开启,用完立刻改回来。
二、自定义日志格式:log_format 详解
log_format 支持非常多的内置变量,对站长来说最常用的是下面这些:$remote_addr 客户端 IP,$remote_user 认证用户名,$time_local 本地时间,$request 完整的请求行,$status 响应状态码,$body_bytes_sent 响应体大小,$http_referer 来源页面,$http_user_agent 浏览器标识,$request_time 请求总耗时,$upstream_response_time 上游服务器响应时间,$http_x_forwarded_for 透传的真实 IP。
我个人在服务器上使用的生产格式是这样,把耗时和状态码放在显眼的位置,方便后面用 awk 统计:
log_format main '$remote_addr - [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" rt=$request_time '
'up=$upstream_response_time';需要注意的是,如果网站前面还有一层 CDN 或者反向代理,$remote_addr 拿到的就是 CDN 的 IP,而不是真实访客 IP,这时候需要用 $http_x_forwarded_for 配合 set_real_ip_from 和 real_ip_header 指令来还原真实 IP,否则后面按 IP 统计流量时会全部算到 CDN 头上,数据完全失真。
三、日志轮转与切割:logrotate 实战
日志文件如果不做切割,几个月下来就能涨到几个 GB,不仅占磁盘,打开、分析都变得非常慢。Linux 下最标准的做法是用 logrotate 做定时轮转。Debian 系安装 Nginx 后会自带 /etc/logrotate.d/nginx 配置文件,默认策略是每周切割一次、保留 52 周。对于个人站来说,我建议改成每天切割、保留 30 天,并且开启压缩:
/var/log/nginx/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 640 www-data adm
sharedscripts
postrotate
if [ -f /var/run/nginx.pid ]; then
kill -USR1 $(cat /var/run/nginx.pid)
fi
endscript
}这里最关键的技巧是 postrotate 里的 kill -USR1:Nginx 收到 USR1 信号后会重新打开日志文件,如果不发这个信号,Nginx 仍然把日志写到已经被改名切割的旧文件里,新的 access.log 永远是空的,这是新手最容易踩的坑。改完配置可以用 logrotate -d /etc/logrotate.d/nginx 做一次演练,确认脚本没有问题,再用 logrotate -f 强制切割一次验证效果。
如果你的服务器环境比较特殊,不想用 logrotate,也可以自己写一个简单的切割脚本,思路完全一样:先 mv 改名,再 kill -USR1 让 Nginx 重新打开日志文件,最后用 gzip 压缩旧文件。把脚本丢进 crontab 每天凌晨执行一次即可。脚本本身并不复杂,但要注意执行时机和权限,建议用 root 或者有权限操作日志目录的用户来跑,并且加上 set -e,任何一步失败就停止,避免出现日志丢失的情况。
四、减少无用日志:静态资源与健康检查
很多个人站的访问日志里,大量记录的是图片、CSS、JS 等静态资源的请求,这些请求对排查问题几乎没有帮助,却占满了日志文件。建议给静态资源单独开一个 location,把访问日志关掉:
location ~* \.(css|js|png|jpg|jpeg|gif|ico|webp|svg|woff2?)$ {
access_log off;
expires 7d;
}同理,如果你用了监控宝、UptimeRobot 之类的第三方监控,每几分钟就有一个探测请求,也可以在对应的 location 里用 access_log off 屏蔽掉,只保留 error_log。日志量小了,切割和备份的压力都会小很多,分析时噪音也少。
五、日志分析实战:GoAccess 与 awk 统计
日志分析工具有很多,最轻量、最适合个人站长的是 GoAccess,一条命令就能在终端里生成实时统计面板。安装后用下面的命令分析昨天的日志:
zcat /var/log/nginx/access.log.*.gz | goaccess - --log-format=COMBINED如果不想装额外工具,纯 awk 也能做很多事。比如统计访问量最大的 20 个 IP:
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20统计各状态码的占比,判断有没有异常:
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn把 rt= 字段里的请求耗时提取出来排序,找出最慢的 10 个请求:
grep -oP 'rt=\K[0-9.]+' /var/log/nginx/access.log | sort -rn | head -10再比如统计今天的独立访客数,只需要把 IP 去重计数:awk '!seen[$1]++ {print $1}' /var/log/nginx/access.log | wc -l。如果想知道搜索引擎爬虫的抓取情况,可以按 UA 过滤:grep -i 'Baiduspider' /var/log/nginx/access.log | wc -l,分别统计百度、谷歌、必应等爬虫的访问量,结合站点收录情况判断爬虫是不是被什么东西挡住了。这些命令组合起来,基本可以覆盖个人站长百分之九十的日志分析需求:谁在刷你的站、哪个页面最慢、有没有 500 报错,一眼就能看出来。
六、利用日志排查故障:502、慢请求与爬虫
实际排障时日志的价值最明显。比如网站突然大量 502,先看 error.log:
tail -n 100 /var/log/nginx/error.log | grep 'upstream'如果看到 connect() failed (111: Connection refused),说明后端 PHP-FPM 挂了或者进程数耗尽;看到 upstream timed out,则是后端处理超时,需要调大 proxy_read_timeout 或检查 PHP 脚本里的死循环。又比如某段时间页面特别慢,用上面的耗时统计找出 rt 很大的请求,再看对应 IP 是不是在短时间内疯狂请求同一个接口,如果是,基本可以断定被爬虫或脚本刷了,配合限流和封禁就能解决。
还有一种常见情况:网站的 404 数量突然暴增。这通常是有人在扫描你的网站目录,或者你的站被某些采集站盯上了。用 grep ' 404 ' /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -20 看一下访问的都是哪些不存在的路径,如果全是 wp-login.php、.env、admin 之类,基本可以确定是扫描器,直接在 Nginx 层面把这些路径返回 444 或者封掉来源 IP 即可。
七、日志安全与隐私
最后提醒两件容易被忽略的事。第一,日志里可能包含访客的 IP、UA 等个人信息,如果网站面向真实用户,建议对日志做定期清理并限制访问权限,日志文件权限设为 640 即可,不要让其他用户可读。第二,要防止日志注入,也就是有人把脚本代码塞进 UA 或者 URL 里,如果日志分析程序直接拼接执行就会出问题,所以日志只用于查看统计,不要直接当脚本输入源,必要时对特殊字符做转义处理。
另外别忘了给日志做异地备份。对于个人站来说,最简单的方式是写一个定时任务,把前一天的日志压缩后 rsync 到另一台机器或者对象存储,成本几乎为零,但真的遇到服务器被入侵、磁盘损坏的时候,历史日志是重建现场、排查问题的唯一依据。备份时注意只保留必要的周期,比如 90 天,避免存储成本无限增长。
访问日志管理是服务器运维的基础功,配置好格式、做好切割、定期分析,很多网站故障都能在发生前发现苗头。把这套流程跑顺之后,你会发现排查问题的时间能省下一大半。