我这个博客上的自托管统计,整套东西大约四十行配置加一个 systemd 定时器。页面里没有 JavaScript,没有 Cookie 提示条,没有第三方脚本在读者等正文的时候往家里打电话。如果你正在找 Google Analytics 替代品,诚实的答案是:我们大多数人根本不需要一个追踪平台,我们需要的是 一个日志阅读器。这篇文章写的是我真跑过的无 Cookie 统计光谱、为什么对这样一个站点 GoAccess 读服务器日志就够了,以及基于日志这条路究竟在哪里开始不够用。
贯穿始终的框架:只测那些会改变你决策的东西,别的都不测。
为什么客户端追踪既是隐私问题,也是性能问题
客户端统计意味着页面里有一段 <script>,在读者的浏览器里运行。这是两个问题披着同一件外套。
隐私那一半,说穿了就很明显:一个第三方信标让别的人能够跨所有嵌了同一段脚本的站点观察 你的读者,而 Cookie 存在的意义正是把这些观察缝合成一份画像。哪怕是一个「隐私友好」的厂商, 仍然意味着你读者的 IP 地址和访问记录离开了你的服务器,去了别人的地盘。Cookie 提示条、同意 字符串、数据处理协议——这一整套装置的存在,只是为了让你做一个你本不必做的选择。
性能那一半更安静,对我来说也更具决定性。每一个追踪器都意味着:
- 在关键路径上的一次阻塞渲染、至少是阻塞网络的请求,和你的 CSS、字体、图片抢同一条连接;
- 一次到你不控制的域名、你修不了硬件的第三方 DNS 查询 + TLS 握手;
- 一段JavaScript 字节,在一部已经发烫的手机上被解析和执行,只为了生成一份你每月看一次的 报告。
一篇只有 30 KB 正文的博客,不该携带一个比自己还重的追踪器。而且上面还有一层哲学税:脚本运行 在读者的浏览器里,所以任何客户端方案——哪怕是自托管的——都是你在别人的机器上执行的代码。 基于日志的统计永远不做这件事。
自托管统计的光谱
没有单一的「最佳」工具;存在的是一条从「读我已经有的文件」到「一整套产品分析平台」的光谱。 下面是我真跑过的这些选项的实际对比。
| 选项 | 数据来源 | 擅长什么 | 代价 |
|---|---|---|---|
| GoAccess | nginx / Apache 服务器日志 | 零 JS、零数据库、秒出、单个二进制 | 粗糙;对客户端事件是盲的 |
| GoatCounter | 极小的 JS 信标 | 单个 Go 二进制、私密、界面够用 | 仍是一段脚本;需要一个数据库 |
| Umami | JS 信标 | 会话、事件、漏斗、清爽界面 | 要跑 Node + 数据库,并要备份 |
| Plausible | JS 信标(可代理) | 打磨精致、接近 GA、有 EU 托管理念 | 活动部件更多;托管要付费 |
| Matomo | JS 信标 | GA 有的一切,外加各种报告 | PHP + MySQL;最重的一个 |
规律很简单:越往左移,你要跑的东西越少、测到的东西也越少;越往右移,你得到的越多、要照看 的也越多。 当你需要漏斗、目标、营销活动和逐个用户的旅程时,Plausible 和 Matomo 就是正确答案。 当你想要一个好看的仪表盘和一段轻量脚本时,GoatCounter 和 Umami 是正确答案。而当你想在页面里 一行代码都不加的前提下,回答「是什么把一个陌生人带了进来、人们实际在读哪些页」这个问题时, GoAccess 就是正确答案。
其中有两个和另外三个不一样。GoatCounter、Umami、Plausible、Matomo 全都是信标式的:它们 注入一段脚本、接收一个事件。GoAccess 是日志式的:它解析你的 Web 服务器本来就为排障而写的 访问日志。正是这个区别,构成了整篇论证。
日志式服务端 vs JS 信标:取舍是真实存在的
两边都不会完胜。这里给一份诚实的账本,因为一篇假装日志是免费的「隐私统计」文章,不过是另一种 广告罢了。
日志式赢在哪:
- 读者的浏览器里什么都不跑。 没有脚本、没有 Cookie、没有同意提示条、没有第三方连接。你的 页面变快,是因为你减了重量,而不是加了重量。
- 没有新东西要跑、要守。 日志本来就在。没有数据库、没有管理面板、没有会话存储、没有某个 周二才想起来要做的 schema 迁移。
- 它挡不住也拦不掉。 广告拦截器和隐私扩展没有东西可拦;你和任何一个别的请求无从区分。
- 便宜到不用去想。 一个二进制,读一个文件。
日志式输在哪:
- 没有客户端事件。 你看不到按钮点击、滚动深度、表单放弃,或者 JS 报错。你看得到请求,也 只看得见请求。
- 静态站点没有会话时长。 一个打开一页、读了九分钟的读者,和一个两秒钟就跳走的读者,看起来
一模一样。GoAccess 能通过
$request_time显示每个请求的服务耗时,但那是服务器的时间, 不是注意力。 - 刻意不做身份识别。 回访者只能从 IP 推断(在 NAT、移动网络和隐私 VPN 背后很不精确)。你 永远不会知道「这个人又回来了」。
- 铁轨上的噪声。 爬虫、扫描器、预览抓取器、引荐垃圾——全都会打到日志里。你会越来越会过滤, 但永远到不了清零。
如果你真的需要左边那列,那就上一个信标工具。如果你是一个博客、文档站或店面,做的决策是 「多写点这个主题」,那左边那列就是全部工作,右边那列是纯开销。
一套具体的 GoAccess 配置
这是我在 nginx 机器上的实际配置。它假设日志格式是 vcombined——nginx 的 combined 加上虚拟
主机——这样一份报告就能覆盖多个站点。
首先是 nginx 里的日志格式。vcombined 与 GoAccess 内置的 VCOMBINED 解析器匹配,所以你不用
手写格式字符串。
# /etc/nginx/nginx.conf(http 上下文)
log_format vcombined '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" "$host"';
access_log /var/log/nginx/access.log vcombined;
然后是报告本身。GoAccess 读取日志、套用过滤、写出一张自包含的 HTML 页面,你可以静态地提供 它——没有后端,没有数据库。
# 先手动生成一次报告,确认它能工作
goaccess /var/log/nginx/access.log \
--log-format=VCOMBINED \
--output=/var/www/analytics/index.html \
--ignore-crawlers \
--anonymize-ip
--ignore-crawlers 丢掉明显的机器人 user-agent;--anonymize-ip 截掉最后一个八位组,这样连
报告本身都泄露不出完整地址。如果想让统计跨日志轮转累积、而不是每次都从头开始,就加上
--persist --restore。
接着用定时器跑它,单实例互斥,避免一次慢报告和下一次堆在一起:
# /etc/systemd/system/goaccess-report.service
[Unit]
Description=生成 GoAccess HTML 报告
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/flock -n /run/goaccess.lock \
/usr/bin/goaccess /var/log/nginx/access.log \
--log-format=VCOMBINED \
--output=/var/www/analytics/index.html \
--ignore-crawlers --anonymize-ip --persist --restore
# /etc/systemd/system/goaccess-report.timer
[Unit]
Description=每 15 分钟重建一次统计报告
[Timer]
OnCalendar=*:0/15
Persistent=true
[Install]
WantedBy=timers.target
启用一次(这一小步就是普通的 systemctl enable --now,不需要 flock),报告就会永远自己刷新。
为了不让它上公网,要么把它藏在 overlay 后面,要么在 nginx 里拦一道:
location /analytics/ {
allow 10.144.144.0/24; # 私有 overlay
deny all;
}
整套系统就这些:一个二进制、一个定时器、一条受保护的路由。它占的内存也就一台笔记本的量, 而且永远不碰读者。
日志式统计诚实地做不到什么
我宁愿把边界点清楚,也不想让你在迁移之后才发现它们。
- 它测不了注意力。 跳出率、滚动、阅读时长、漏斗——一个都测不了。对静态站点来说,没有第二个 事件可以拿来对比。如果「他们待了多久」对你来说是一个真实的决策输入,日志答不了,再怎么聪明地 解析也答不了。
- 机器人流量是常态。
--ignore-crawlers能去掉蠢的那批。其余的——无头浏览器、AI 爬虫、 链接预览抓取器、可用性监控——会虚高你的数字,而且有些还会谎报 user-agent。请把原始计数当作 方向性的,而非精确值。 - 引荐数据充满噪声。 隐私代理会剥掉
Referer头,应用内浏览器什么都不发,有些站点还会发 自我引荐。「直接访问」是一个桶,不是一个真相。你真正有用的引荐洞察,往往来自你在意的那一两个 来源,而不是长尾。 - 唯一性只是个猜测。 基于 IP 的计数会高估共享网络、低估在 Wi-Fi 和移动网络之间切换的人。 对「大概多少」够用,对「精确多少」是错的。
- CDN 和缓存会藏起请求。 如果 CDN 替你提供静态资源,大量流量根本到不了你的源站,日志看到的 就不到全量。这没关系——它只意味着日志是一份抽样,不是一次普查。
这些对博客来说都不致命,但它们全都对「日志报告是一套精确的漏斗工具」这个信念致命。它不是, 也从来不该是。
原则:只测那些会改变你决策的东西
我能接受粗糙数字的原因,是我只对其中两个数字采取行动:哪篇文章带来了一个新读者,以及 哪些文章真的有人读。其余一切——页面停留时长、滚动深度、精确到像素的设备分布——从来没有 改变过我做过的任何一件事。在一百次失败的实验之后,真正推动过这个站点的指标,只有那些告诉我 下一篇该写什么的指标。
所以对任何隐私友好统计的门槛,都不是「它能不能和 GA 功能对功能地平起平坐」。而是「它能不能 告诉我一件我会据此行动的事,同时不让我的读者付出隐私、速度或一条 Cookie 提示条的代价」。 GoAccess 读日志以很大的余量跨过了这条线。如果你的站点长出了目标、漏斗,以及一个需要共享仪表盘 的团队,那就升级到 Umami 或 Plausible——带着理由刻意地升级,而不是默认就上。
如果你想看这套东西跑在什么机器上,入口和 TLS 在 不用 Docker 的自托管里,而它在更大图景中的位置在 2026 年我到底自托管了什么里。
少测一点,多行动一点。