第三方统计用着用着就变了味。Google Analytics 在国内打开要先翻墙,脚本几百 KB,访客数据还得先送一趟别人的服务器;百度统计后台能看的东西越来越少,广告拦截插件一开就是一片空白。想要一份干净、自己能掌控的访客数据,其实花不了多少钱。
Umami 就是干这个的,一个开源的网站统计程序,前端脚本几 KB,不写 Cookie,不跨站追踪个人,数据全躺在你自己的 Postgres 里。今天拿一台 2 核 2G 的小机器,用 Docker Compose 把它跑起来,再挂到 WordPress 上。
目标与适用场景
适合已经有一台 VPS、想用自建统计替掉 GA 或百度统计的个人站长,也适合单纯不想让第三方脚本拖慢站点的人。
做完之后你会得到一个 https://stats.你的域名 站点,能看 PV/UV、来源、页面排行、设备分布,底层是一个 Umami 容器加一个 Postgres 容器,数据能备份、能迁移、能随时搬走。
想要热力图和会话录屏的话,Umami 不做这些,得另找工具。
前置条件
- 一台能装 Docker 的 Linux 服务器,2C2G 够跑,Umami 加 Postgres 常驻内存大概几百 MB。
- 一个子域名,比如
stats.example.com,已经解析到这台机器。 - 服务器上有 nginx 或者宝塔面板,能新建站点、加反向代理。
- 会用 ssh 敲命令。
具体步骤
1. 建目录,生成两个密钥
mkdir -p /opt/umami && cd /opt/umami
openssl rand -hex 32 # 这条的输出给 APP_SECRET
openssl rand -hex 32 # 这条的输出给 TWO_FACTOR_ENCRYPTION_KEY
APP_SECRET 用来给登录令牌签名,每个部署都该是独立的一串,复制粘贴现成的值会让登录态变得不靠谱。第二条 64 位十六进制串是以后开两步验证要用的,现在用不上也先留着。
顺手定一个数据库密码,等下 compose 文件里要用两处,写一样的。
2. 写 docker-compose.yml
services:
umami:
image: ghcr.io/umami-software/umami:latest
container_name: umami
ports:
- "127.0.0.1:3000:3000"
environment:
DATABASE_URL: postgresql://umami:你的数据库密码@db:5432/umami
APP_SECRET: 第一步生成的第一个字符串
TWO_FACTOR_ENCRYPTION_KEY: 第一步生成的第二个字符串
depends_on:
db:
condition: service_healthy
init: true
restart: always
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: umami
POSTGRES_USER: umami
POSTGRES_PASSWORD: 你的数据库密码
volumes:
- umami-db-data:/var/lib/postgresql/data
restart: always
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 5
volumes:
umami-db-data:
两个细节容易踩。端口写成 127.0.0.1:3000:3000,3000 只监听本机,公网扫不到,外面统一走 nginx;DATABASE_URL 里的主机名是 db,也就是 compose 里的服务名,写成 localhost 会连不上,容器里的 localhost 是它自己。
Umami 官方仓库里那份 compose 是同样的结构,postgres:15-alpine 和 Postgres 版本按你环境实际情况调整,只要 Postgres 不低于 12 就行。
3. 起服务
docker compose up -d
docker compose logs -f umami
第一次启动会先检查数据库连接、跑一遍表结构迁移,日志里的具体文字随版本变化,看到它在反复刷正常运行信息、没有退出就算起来了。用另一种方式确认:
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:3000/api/heartbeat
返回 200,容器就是活的。这一步不通先看 docker compose logs db,多半是密码或卷权限的问题。
4. nginx 反代加证书
server {
listen 443 ssl http2;
server_name stats.example.com;
ssl_certificate /your/cert/fullchain.pem;
ssl_certificate_key /your/cert/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
证书交给 certbot 或者宝塔面板去签,别手抄 pem 路径。用宝塔建站加反向代理的话,模板里一般会带上 X-Real-IP 和 X-Forwarded-For,改完配置去文件里核一眼有没有少,少了统计出来全是 127.0.0.1。
X-Forwarded-For 就是 Umami 认访客真实 IP 的地方。你的站点前面如果还套了 CDN,比如 Cloudflare 或者阿里云 ESA,真实 IP 藏在别的请求头里,这时给容器加一个 CLIENT_IP_HEADER 环境变量指过去,Cloudflare 是 CF-Connecting-IP。
5. 登录并拿到 website-id
浏览器打开 https://stats.example.com,默认账号 admin、密码 umami,登进去先去设置里把密码改掉,这个默认密码是公开写在文档里的。
接着 Websites 里新增站点,填名称和你站点的域名,保存后进详情页,Tracking code 那一段 <script> 里的 data-website-id 就是这个站点的唯一 ID,抄下来。
6. 挂到 WordPress
丢进子主题的 functions.php,或者用代码片段插件:
add_action('wp_head', function () {
if (is_user_logged_in() || is_admin()) {
return;
}
printf(
'<script defer src="https://stats.example.com/script.js" data-website-id="你的-website-id" data-domains="www.example.com"></script>'
);
}, 20);
defer 让脚本不挡首屏渲染,排除登录用户是为了自己写文章时不把数据带偏,data-domains 限死只在正式域名跑,本地和测试站不会污染统计,注意你的站带不带 www 要写准。
不想动主题文件的话,用 WPCode 这类代码片段插件,或者把同一段 <script> 塞进页脚也行,效果没差别。
7. 验证
打开自己的站点点几页,回 Umami 后台看实时的 Realtime 面板,能看到当前在线人数和你刚访问的路径就对了。再单独确认脚本本身能取到:
curl -s -o /dev/null -w "%{http_code}\n" https://stats.example.com/script.js
常见坑与回滚
- 3000 端口别裸奔:绑 127.0.0.1,或者防火墙只放 80/443,后台管理界面暴露在公网是等着被打。
- 访客 IP 全是内网地址:反代少了
X-Forwarded-For,前面还有一层 CDN 的话按第 4 步设CLIENT_IP_HEADER。 - 升级之前先备份:镜像用的是
latest就会跟着上游一起升,v3 之后数据库表结构有变动,升之前先导出:docker compose exec db pg_dump -U umami umami > umami-$(date +%F).sql。想稳一点就把镜像 tag 固定成具体版本号,比如ghcr.io/umami-software/umami:3.4.0。 - 页面缓存:前端脚本不受站点缓存影响,但你的缓存插件如果对登录用户也走缓存,
is_user_logged_in()的判断会被缓存住,用无痕窗口确认一遍统计里有没有混进自己的访问。 - 备份策略:整套东西的资产只有一个数据卷加一个 compose 文件,定期把 SQL 导出来扔到别的机器上就够了。
- 要回滚:先删掉 WordPress 里那段 script,再
docker compose down,删掉 nginx 站点,然后把/opt/umami目录整个删掉。down后面别顺手加-v,那个参数会把数据卷一起删掉,历史数据不留。
挂上之后我第一个看明白的事是,AI 日报那几篇的阅读高峰基本都压在早上八点多,晚上发的那一篇反而沉得快,这跟凭感觉猜的完全不一样。自建这套的路子和之前写的 Uptime Kuma 监控告警、Artalk 评论系统 是一模一样的:一个 compose 文件、一个反向代理、前端加一段脚本。三套服务跑在同一台 2 核小机器上,内存还剩得下,先把最贵的那份数据拿回自己手里,比什么都值。你如果也在纠结要不要换掉 GA,可以先只挂统计看一周,数据不好看再关掉,成本几乎为零。



![[教程][服务器]在阿里云服务器上卸载AliYunDunMonito(阿里盾)-小绵羊的小窝](https://www.littlesheep.cc/wp-content/uploads/2025/12/114612929920251229005735.png)

![[免费][教程]子比主题美化,优化教程-小绵羊的小窝](https://www.littlesheep.cc/wp-content/uploads/2026/09/3b06fd6a0520260920160148.webp)
![[免费][插件]将WordPress图片上传到LskyPro蓝空图床的插件-小绵羊的小窝](https://www.littlesheep.cc/wp-content/uploads/2026/09/b9e1998d8420260920160152.webp)

![[教程][笔记]Centos7宝塔下安装ImageMagick,无法安装webp扩展解决办法-小绵羊的小窝](https://www.littlesheep.cc/wp-content/uploads/2026/09/8e227dbfcc20260920155618.webp)
![[资源][教程]AI SEO - WordPress插件-小绵羊的小窝](https://www.littlesheep.cc/wp-content/uploads/2026/01/9f396016b220260112151830.png)



暂无评论内容