很多站长一开始都会直接用 Google Analytics。它免费、功能强、资料多,但也有几个绕不开的问题:加载慢、被广告拦截器屏蔽、隐私合规压力大,而且数据始终放在别人那里。
如果你只是想知道网站每天有多少访问、访客来自哪里、哪些页面更受欢迎,其实没必要上一个很重的统计平台。Umami 更像一个轻量的自托管网站统计工具:界面干净,不依赖 Cookie,默认更尊重隐私,放在一台小 VPS 上就能跑。
这篇文章讲的是怎么在 VPS 上部署 Umami。我们会用 Docker Compose 跑 Umami 和 PostgreSQL,再用 Caddy 自动申请 HTTPS。后面还会讲跟踪脚本、防广告拦截、备份、升级和常见问题。
自建网站统计常见选择有 Umami、Plausible、Matomo 等。
Matomo 功能很完整,但相对更重,更像一套完整分析平台。Plausible 的体验很好,但自托管 Community Edition 需要 PostgreSQL 加 ClickHouse,资源占用和备份复杂度都更高。
Umami 的优势很直接:
- 部署简单,应用容器加 PostgreSQL 就够了。
- 对小 VPS 友好,1GB 内存也能跑轻量站点。
- 后台清爽,不需要学习复杂报表。
- 支持多站点统计,适合个人站长和小团队。
- 可以自定义跟踪脚本名称,降低被广告拦截器命中的概率。
- 数据在自己的数据库里,备份和迁移可控。
如果你的网站访问量已经很大,或者强依赖 ClickHouse 级别的查询性能,Plausible 也值得考虑。但对大多数 VPS 用户来说,Umami 更适合轻量自托管,尤其适合预算有限的小机器。
Umami 不算重,真正增长的是 PostgreSQL 里的访问数据。
| 场景 | 推荐配置 |
|---|---|
| 个人博客、工具站、小流量官网 | 1 vCPU / 1GB RAM / 20GB SSD |
| 多个站点、每天几千到几万 PV | 2 vCPU / 2GB RAM / 40GB SSD |
| 团队使用、长期保留大量历史数据 | 2-4 vCPU / 4GB RAM / 80GB+ SSD |
如果你已经有一台跑 Caddy、Nginx、Uptime Kuma、Vaultwarden、NocoDB 的 VPS,Umami 可以作为附加服务一起跑。但如果这台机器只有 512MB 内存,就要谨慎,PostgreSQL 加其他服务可能会被 OOM killer 盯上。
本文默认你已经有:
- Ubuntu 22.04 / 24.04 或 Debian 12。
- Docker 和 Docker Compose V2。
- 一个域名,例如
analytics.example.com。 - 域名 A 记录已经指向 VPS 公网 IP。
- 80、443 端口可以开放给 Caddy 申请 HTTPS。
检查 Docker:
docker version
docker compose version
如果还没有安装 Docker,先安装好再继续。本文重点放在 Umami 本身,不重复讲 Docker 基础。
创建目录:
sudo mkdir -p /opt/umami
sudo chown -R "$USER":"$USER" /opt/umami
cd /opt/umami
mkdir -p backups caddy-data caddy-config
生成两个随机密钥:
openssl rand -hex 32
openssl rand -base64 32
第一个作为 APP_SECRET,第二个作为 PostgreSQL 密码。APP_SECRET 必须长期保持不变,后续重启、升级、迁移都要继续使用同一个值;不要每次部署都重新生成。
创建 .env:
nano .env
写入下面内容,把域名和密码换成你自己的:
UMAMI_DOMAIN=analytics.example.com
POSTGRES_PASSWORD=replace_with_a_long_database_password
APP_SECRET=replace_with_openssl_rand_hex_32
TRACKER_SCRIPT_NAME=metrics.js
COLLECT_API_ENDPOINT=/api/web-events
几个变量解释一下:
UMAMI_DOMAIN:Caddy 反代用的域名。POSTGRES_PASSWORD:PostgreSQL 数据库密码。APP_SECRET:Umami 用来保护认证 Token 的随机字符串,每个实例都应该不同,并且上线后不要随意更换。TRACKER_SCRIPT_NAME:把默认跟踪脚本改成自定义名字,减少被广告拦截器命中。COLLECT_API_ENDPOINT:把默认收集接口改成自定义路径,也可以减少被规则匹配。
.env 文件就是生产密钥,不要提交到 Git,也不要放在 Web 可访问目录。
创建 docker-compose.yml:
nano docker-compose.yml
写入:
services:
umami:
image: ghcr.io/umami-software/umami:latest
container_name: umami
restart: unless-stopped
env_file:
- .env
environment:
DATABASE_URL: postgresql://umami:${POSTGRES_PASSWORD}@db:5432/umami
APP_SECRET: ${APP_SECRET}
TRACKER_SCRIPT_NAME: ${TRACKER_SCRIPT_NAME}
COLLECT_API_ENDPOINT: ${COLLECT_API_ENDPOINT}
DISABLE_TELEMETRY: "1"
depends_on:
db:
condition: service_healthy
ports:
- "127.0.0.1:3000:3000"
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/api/heartbeat || exit 1"]
interval: 30s
timeout: 5s
retries: 5
start_period: 30s
db:
image: postgres:15-alpine
container_name: umami-db
restart: unless-stopped
environment:
POSTGRES_DB: umami
POSTGRES_USER: umami
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- umami-db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U umami -d umami"]
interval: 10s
timeout: 5s
retries: 5
caddy:
image: caddy:2-alpine
container_name: umami-caddy
restart: unless-stopped
env_file:
- .env
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./caddy-data:/data
- ./caddy-config:/config
depends_on:
- umami
volumes:
umami-db-data:
这里有几个重点:
umami只绑定到127.0.0.1:3000,外网不能直接访问 3000。- PostgreSQL 没有映射 5432,数据库只在 Docker 网络内部使用。
- 真正对外的是 Caddy 的 80 和 443。
DISABLE_TELEMETRY=1是可选项,不想参与匿名遥测可以打开。
生产环境更稳的做法是固定 Umami 镜像版本,而不是长期使用 latest。第一次部署可以用 latest 跑通,正式长期使用时建议去 Umami GitHub Release 或镜像仓库确认当前稳定版本,再手动固定标签。
创建 Caddyfile:
nano Caddyfile
写入:
{$UMAMI_DOMAIN} {
encode zstd gzip
reverse_proxy umami:3000
}
开放防火墙:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw reload
如果你用的是云厂商 VPS,还要去安全组里放行 80 和 443。很多 Caddy 证书申请失败,最后都是因为云安全组没放行。
执行:
cd /opt/umami
docker compose up -d
查看容器:
docker compose ps
查看日志:
docker compose logs -f --tail=100 umami
第一次启动会初始化数据库,等一两分钟很正常。浏览器打开:
https://analytics.example.com
默认账号是:
用户名:admin
密码:umami
第一次登录后马上修改密码。这一步不要拖,公网服务使用默认密码是最常见的安全事故之一。
登录后台后,点击添加网站,填入:
- 网站名称:例如
My Blog - 域名:例如
example.com
保存后,Umami 会生成一段跟踪代码。大概长这样:
<script defer src="https://analytics.example.com/metrics.js" data-website-id="your-website-id"></script>
把它放到你网站的 <head> 里。对于大多数静态博客、WordPress 主题、Next.js 项目,只要放在所有页面都会加载的位置即可。
如果你只希望脚本在指定域名生效,可以加 data-domains:
<script defer src="https://analytics.example.com/metrics.js" data-website-id="your-website-id" data-domains="example.com,www.example.com"></script>
如果你想尊重浏览器的 Do Not Track 设置,可以加:
<script defer src="https://analytics.example.com/metrics.js" data-website-id="your-website-id" data-do-not-track="true"></script>
访问你的网站后,回到 Umami 后台,通常几秒到几十秒内就能看到数据。
广告拦截器经常拦截明显的统计脚本路径,例如 analytics、track、script.js、/api/send 之类。
Umami 提供了两个很实用的变量:
TRACKER_SCRIPT_NAME=metrics.js
COLLECT_API_ENDPOINT=/api/web-events
这样跟踪脚本不再是默认的 script.js,收集接口也不再是默认路径。只改脚本名是不完整的,最好连收集接口一起改。它不能保证 100% 绕过所有规则,但比直接使用默认路径更稳。
修改 .env 后重启:
docker compose up -d
然后重新复制后台生成的跟踪代码,或者手动把网站里的脚本地址改成新的路径。
注意:不要为了统计而欺骗用户或绕过明确的隐私选择。如果用户开启 Do Not Track,建议尊重它。
如果你的统计域名经过 Cloudflare 代理,Umami 可能会看到 Cloudflare 节点 IP,而不是访客真实 IP。
可以在环境变量里指定客户端 IP 头:
CLIENT_IP_HEADER=CF-Connecting-IP
然后在 docker-compose.yml 的 umami.environment 里加上:
CLIENT_IP_HEADER: CF-Connecting-IP
改完后重启 Umami。
这个配置只适合流量确实全程经过 Cloudflare 的情况。如果源站 IP 可以被绕过直连,就不要盲目信任外部传来的 CF-Connecting-IP 头,最好配合防火墙或安全组只允许 Cloudflare 回源 IP 访问 80/443。
如果你没有用 Cloudflare,或者只使用 DNS 解析,不需要配置这个变量。
Umami 的核心数据都在 PostgreSQL。最重要的是定期导出数据库。
创建备份脚本:
nano backup-umami.sh
写入:
#!/usr/bin/env bash
set -euo pipefail
APP_DIR="/opt/umami"
BACKUP_DIR="$APP_DIR/backups"
DATE="$(date +%F-%H%M)"
mkdir -p "$BACKUP_DIR/$DATE"
cd "$APP_DIR"
docker exec umami-db pg_dump -U umami umami | gzip > "$BACKUP_DIR/$DATE/umami.sql.gz"
tar czf "$BACKUP_DIR/$DATE/umami-config.tar.gz" docker-compose.yml Caddyfile .env
find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d -mtime +14 -exec rm -rf {} \;
echo "Backup saved to $BACKUP_DIR/$DATE"
加执行权限并测试:
chmod +x backup-umami.sh
./backup-umami.sh
ls -lh backups
加定时任务:
crontab -e
每天凌晨 3 点备份:
0 3 * * * /opt/umami/backup-umami.sh >> /opt/umami/backups/backup.log 2>&1
本地备份只能防误操作,不能防硬盘损坏。更稳妥的做法是再用 rclone 同步到对象存储、NAS 或另一台 VPS。
恢复前先准备同样的 docker-compose.yml、Caddyfile 和 .env。下面的恢复命令默认目标数据库是全新的空库;如果你要覆盖已有实例,先停止应用并确认要删除旧数据,再重建数据库或 volume,避免把新旧数据混在一起。
启动数据库:
cd /opt/umami
docker compose up -d db
sleep 20
恢复 SQL:
gunzip -c umami.sql.gz | docker exec -i umami-db psql -U umami umami
再启动全部服务:
docker compose up -d
恢复后检查:
- 能否登录后台。
- 网站列表是否存在。
- 历史访问数据是否还在。
- 跟踪脚本是否仍然是当前域名。
备份脚本写完后,最好找一台测试 VPS 演练一次恢复。没有恢复演练的备份,只能算心理安慰。
升级前先备份:
cd /opt/umami
./backup-umami.sh
如果使用 latest:
docker compose pull
docker compose up -d
升级后检查日志:
docker compose logs --tail=100 umami
Umami 官方文档提醒,重大版本升级后 PostgreSQL 的统计信息可能变旧,导致大实例查询变慢。升级后可以进入数据库执行:
docker exec -it umami-db psql -U umami -d umami
然后执行:
ANALYZE;
这个命令会刷新 PostgreSQL 查询规划器统计信息,通常可以安全在线执行。
至少做到这些:
- 登录后立即修改默认密码。
- 不要把 3000 和 5432 暴露到公网。
APP_SECRET和数据库密码使用随机强密码。.env和备份文件不要放到 Web 目录。- 只开放 80、443 和 SSH 必要端口。
- 定期更新系统和容器镜像。
- 后台如果只给自己看,可以再套一层 Cloudflare Access、Tailscale 或 WireGuard。
- 离职成员、外包账号、临时账号及时删除。
如果你把 Umami 给团队一起用,还要注意权限和站点隔离。不要所有人都用同一个管理员账号。
先看容器状态:
docker compose ps
再看 Caddy 日志:
docker compose logs --tail=100 caddy
重点检查:
- 域名 A 记录是否正确。
- VPS 防火墙是否放行 80/443。
- 云安全组是否放行 80/443。
- Cloudflare 是否开启了错误的代理模式。
第一次申请证书时,建议 Cloudflare 先用“仅 DNS”,跑通后再按需要开启代理。
看应用日志:
docker compose logs --tail=100 umami
常见原因:
DATABASE_URL密码和 PostgreSQL 初始化密码不一致。.env改过,但旧数据库 volume 里仍然是旧密码。db容器还没 healthy。- Compose 文件缩进错误。
如果你第一次启动后又改了数据库密码,PostgreSQL volume 不会自动跟着改。测试环境可以删除 volume 重来;生产环境要进数据库改密码。
打开浏览器开发者工具,检查 Network:
metrics.js是否加载成功。- 收集接口是否返回 200 或 204。
- 是否被广告拦截器拦截。
- 网站 CSP 是否禁止加载外部脚本。
data-website-id是否复制错。
如果用了 data-domains,确认域名写对了,包括 www 和非 www。
可能原因:
- 广告拦截器拦截了一部分请求。
- 用户开启了 Do Not Track。
- 你的脚本只放在部分页面。
- SPA 路由或前端框架集成方式不正确。
- CDN 缓存了错误脚本。
网站统计永远不可能 100% 精准。它更适合看趋势,而不是当成财务级别的绝对数字。
先看容器资源:
docker stats
如果 PostgreSQL 查询变慢,可以执行前面提到的:
ANALYZE;
访问量很大的实例,还要考虑增加 VPS 内存、缩短数据保留时间,或者迁移到更强的数据库服务器。
简单结论:
- 小站、个人博客、工具站、小团队:优先 Umami。
- 高访问量、非常看重漂亮报表和邮件报告:可以考虑 Plausible。
- 想要更轻、更容易备份、更少维护:优先 Umami。
- 已经有 4GB 以上 VPS,能接受 ClickHouse:Plausible 也不错。
Plausible 的自托管版本需要 PostgreSQL 和 ClickHouse。ClickHouse 查询性能强,但对小 VPS 来说就是额外负担。备份时也不是只导出一个 PostgreSQL 就完事。
Umami 的好处是够轻、够简单。对大多数中文站长来说,先把统计跑起来、备份做好、HTTPS 配好,比追求一个更复杂的分析系统更重要。
正式使用前检查:
- 域名已经解析到 VPS。
- 80/443 已在防火墙和安全组放行。
- Umami 只绑定
127.0.0.1:3000。 - PostgreSQL 没有暴露 5432。
-
.env里的密码和APP_SECRET已替换。 -
APP_SECRET已记录到密码管理器,后续升级不会重新生成。 - 已登录后台并修改默认密码。
- 已添加第一个网站。
- 跟踪脚本已放到网站
<head>。 - Network 面板能看到收集请求成功。
- 备份脚本跑通过。
- 至少做过一次恢复演练。
Umami 最适合回答这些问题:今天访问量有没有涨,搜索流量来自哪里,哪篇文章最受欢迎,哪个国家或地区访问最多,最近推广有没有效果。
它不适合做特别重的用户行为分析,也不应该被当成唯一业务数据来源。网站统计本质上是趋势工具,不是绝对真相。
如果你已经有一台 VPS,想摆脱 Google Analytics,又不想维护 ClickHouse 这种更重的分析栈,Umami 是很适合的选择。它部署简单、备份清楚、资源占用低,用来给博客、工具站、官网和小团队项目做访问统计,刚刚好。
