Uptime Kuma:自托管状态监控
用 Docker 运行 Uptime Kuma 监控网站、端口、DNS 和 cron 任务,通过邮件或 Telegram 告警,并从独立 VPS 发布状态页。
您将搭建什么
一个小巧的容器,从外部监视您的其他服务器和网站,一旦某个目标停止响应,就立即通过邮件、Telegram、Discord 或 webhook 通知您。Uptime Kuma 是一个由 SQLite 文件支撑的 Node 进程,因此在 256-512 MB 内存中就能轻松运行,并为您提供实时仪表盘、历史图表和公开状态页。安装只需一个十行的 Compose 文件;真正重要的是您把它运行在哪里,以及您的告警是否曾在测试中真正触发过,因为一个从未被证明能联系到您的监控比没有监控更糟:它让您以为有保障,实际上什么也没监视。
把监控运行在故障波及不到的地方
这一个决定决定了整件事的成败,所以放在最前面。不要把 Uptime Kuma 运行在它所监视的同一台机器上。如果监控就住在它监视的服务器上,那么您最关心的那个事件(那台机器宕机或内存耗尽)也会一并杀死监控,于是您根本收不到告警:一个已死监控的沉默,与“一切正常”看起来完全一样。即使机器还活着,也有一个更隐蔽的陷阱:指向 localhost 的监控与工作负载共享 CPU,于是一次负载峰值会让它自己的检查超时,把目标翻转为 down,这是误报,而真实用户其实被正常服务着。
所以请把 Uptime Kuma 运行在一台不同于它所监视的 VPS 上,最好是不同的服务商或区域,并像您的用户那样访问您的服务:通过公网、用主机名。一台便宜的实例就足够了,一台小小的监控 VPS 就能监视您所有的服务器。为了发现 Kuma 本身宕机,可以从别处的 cron 加一个推送心跳。
前置条件与配置规格
- 一台全新的 Ubuntu 24.04 VPS,装有 Docker Engine 和 Compose v2 插件,从 Docker 自己的 apt 仓库安装,而不是滞后的
docker.io发行版软件包。 - 256 MB 内存可运行少数几个监控;512 MB 到 1 GB 足以从容运行几十个监控外加反向代理,而两次检查之间 CPU 几乎空闲。
- 一个域名和一条 DNS
A记录(比如status.example.com指向该 VPS),仅当您想要 TLS 和公开状态页时才需要。私有实例可以跳过 DNS,改用 VPN 或 SSH 隧道。 - 到告警去向的出站网络:到您邮件服务商的 SMTP,或到 Telegram 和 Discord 的 HTTPS。
Compose 文件
把下面的内容放进 /srv/uptime-kuma/compose.yaml。
services:
uptime-kuma:
image: louislam/uptime-kuma:2
container_name: uptime-kuma
restart: unless-stopped
ports:
- "127.0.0.1:3001:3001"
volumes:
- kuma-data:/app/data
volumes:
kuma-data:启动它并观察首次启动:
sudo mkdir -p /srv/uptime-kuma
# save the file above as /srv/uptime-kuma/compose.yaml, then:
cd /srv/uptime-kuma && sudo docker compose up -d
sudo docker compose logs -f uptime-kuma正确启动会打印 Listening on 3001 然后安静下来。那个文件里有三处是刻意为之的。
127.0.0.1:3001:3001,而不是 3001:3001。Docker 用 DNAT 规则发布端口,这些规则在 ufw 看到数据包之前就已生效,所以裸的 3001:3001 会把您的仪表盘放到公网上,无论您的防火墙怎么配置。绑定到回环地址可让它保持私有,只暴露反向代理;私有实例可以跳过代理,改为通过 自托管的 WireGuard VPN 访问 3001。
位于 /app/data 的命名卷。Uptime Kuma 记住的一切(SQLite 数据库、您的监控、通知设置和状态页 logo)都住在那里。丢了它,您就得从一个空白的管理界面重新开始;它是您唯一必须备份的东西。
镜像固定到一个主版本标签 :2。那是当前的稳定线;复制之前请在 Docker Hub 上确认最新的主版本,永远不要跟踪像 latest 这样的移动标签,项目已经弃用它。这个镜像的主版本跳跃是一次单向的数据库迁移,您会想要有意地触发它,而不是在一次例行拉取中稀里糊涂撞上。
一个注意事项:/app/data 必须位于支持 POSIX 文件锁的文件系统上。本地 Docker 卷没问题;在 NFS 上,SQLite 数据库会损坏,您会看到 SQLITE_BUSY 和 database disk image is malformed,所以永远不要用网络共享。
首次运行:创建管理员账户
通过您的代理在 https://status.example.com 访问该实例,或通过 SSH 隧道:运行 ssh -L 3001:127.0.0.1:3001 user@your-vps 并打开 http://localhost:3001。第一个页面是设置管理员用户名和密码的表单;没有默认登录凭据。请设一个真正的密码:这个仪表盘能看到您所监控的一切的内部地址和令牌。之后忘了密码?从宿主机重置,而不是从浏览器:
sudo docker compose exec uptime-kuma npm run reset-password先添加您的通知渠道,并测试它们
在添加监控之前先设置好告警,这样您在创建每个监控时就能顺手挂上一个渠道。进入 Settings 然后 Notifications 然后 Setup Notification,用每个渠道的 Test 按钮确认消息能送达,因为一个未经测试的通知是设置悄无声息失败的第二常见方式。
邮件(SMTP)。填入主机、端口、加密方式、用户名、密码,以及一个 From 和一个 To。两种可用的组合是:465 配合把“Secure”设为 TLS/SSL,或 587 配合 STARTTLS。对于 Gmail 和大多数启用了两步验证的服务商,您必须生成一个应用专用密码;用普通账户密码会返回 Error: Invalid login: 535-5.7.8 Username and Password not accepted。
Telegram。给 @BotFather 发消息,发送 /newbot,复制机器人令牌。要获取您的 chat ID,先给这个新机器人发一次消息,打开 https://api.telegram.org/bot<token>/getUpdates,从 JSON 里读出 chat.id。一个您从未先发过消息的机器人,其 getUpdates 是空的,也就无处可发。
Discord。在频道里,打开 Edit Channel 然后 Integrations 然后 Webhooks 然后 New Webhook,复制 URL,把它粘贴为一个 Discord 通知。
通用 webhook。对于其他任何东西(一个 Slack incoming webhook、一个自定义端点、一个家庭自动化钩子),Webhook 类型会把一个 JSON 载荷 POST 到您提供的 URL,而内置的 Apprise 集成覆盖了列表上其余九十多个服务中的大多数。
添加监控,一次一种类型
点击 Add New Monitor,选择一个类型,设置 Friendly Name、Check Interval(60 秒是合理的)、Retries(判定为“down”前连续失败的次数;设 2 或 3,这样一个丢包不会触发呼叫),以及要触发的通知。您会用到的类型:
- HTTP(s)。一个完整的 URL。判定为 up 意味着一个可接受的状态码(默认 200-299;如果
301或401对您是正常的,就在 Accepted Status Codes 里放宽)。这是您监控网站和 API 的主力。 - HTTP(s) - Keyword。同样的请求,但“up”还要求正文中存在某个字符串,或在勾选 Invert 时不存在。这能抓住站点返回
200 OK却渲染出“Error establishing a database connection”的情况,而一个普通的 HTTP 检查会把它当成健康的。 - TCP Port。一个到主机和端口的裸 TCP 连接,用于非 HTTP 的东西:22 端口上的 SSH、5432 上的 Postgres、25 上的 SMTP 服务器、一个游戏服务器。
- Ping。ICMP echo:廉价的可达性和延迟检测。但许多网络和云防火墙会丢弃 ICMP,所以一个红色的 ping 监控可能意味着“主机宕机”或“服务商屏蔽了 ping”;用一个 TCP 监控来确认。
- DNS。对您指定的解析器解析一条记录(A、AAAA、MX、TXT 等等),并且可以断言返回结果,从而尽早发现注册商或 DNS 故障。
- Push。由内向外的监控,接下来介绍。
用推送(心跳)监控来监视 cron 任务
上面每一种监控都是从外部伸进您的服务。推送监控反过来工作:Uptime Kuma 等待,由您的任务来呼叫它,说“我运行过了”。这是监视备份或 cron 唯一诚实的方式:一个 HTTP 检查知道某个 URL 有响应,但只有任务本身知道它完成了。
创建一个类型为 Push 的监控。Uptime Kuma 会生成一个唯一的 URL,比如:
https://status.example.com/api/push/j8Xa2Kd9Qe?status=up&msg=OK&ping=把 Heartbeat Interval 设为任务运行的频率,再加一点余量。然后在脚本的末尾加一行,让它只在成功时才触发:
#!/usr/bin/env bash
set -euo pipefail
# ... your backup or job runs here; set -e aborts on any failure ...
curl -fsS --retry 3 "https://status.example.com/api/push/j8Xa2Kd9Qe?status=up&msg=backup+ok&ping="如果任务失败,set -e 会在 curl 之前中止;如果机器宕机,它也根本不会运行。无论哪种情况,心跳都会停止,一旦过了间隔加重试的窗口,Uptime Kuma 就会把监控翻转为 down 并告警您。把那个推送令牌当作机密对待:任何拿到它的人都能伪造一个健康的心跳。
搭建一个公开状态页
状态页是面向客户的视图:哪些服务在线以及它们近期的历史,而不暴露您的仪表盘。进入 Status Pages 然后 New Status Page,给它一个名字和一个 slug(公开路径,比如 /status/main),把您想要的监控拖进像“Websites”和“APIs”这样的分组,加一个 logo 和一段简短描述,然后保存。您也可以把这个页面绑定到它自己的域名,让 status.example.com 直接提供它。
两点注意:只添加您愿意公开的监控,因为状态页会暴露一个服务存在以及它是否在线;仪表盘则始终在您的登录之后,而状态页是有意公开的,不需要认证。
用带 TLS 的反向代理把它挡在后面,并留意 websocket
对于公开实例,在绑定到回环地址的容器前面放一个反向代理,用于 TLS 和主机名。让所有人栽跟头的细节是:Uptime Kuma 的界面是一个实时的 Socket.IO 应用,所以代理必须升级 WebSocket 连接。漏掉它,页面能加载但永远连不上;仪表盘卡在“Connecting...”,实时心跳永远不更新,浏览器控制台显示 WebSocket connection to 'wss://.../socket.io/...' failed。
安装 nginx 和 certbot,然后写一个把请求代理到回环端口的 vhost。先把它放在 80 端口上,之后让 certbot 加上 TLS;证书申请、续期定时器及其失败模式在 用 certbot 和 nginx 签发 Let's Encrypt 证书 中有介绍。
sudo apt install -y nginx certbot python3-certbot-nginx把下面的内容保存为 /etc/nginx/sites-available/status.example.com;那两行 WebSocket 才是关键:
server {
listen 80;
server_name status.example.com;
location / {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
proxy_read_timeout 3600s;
}
}启用站点,测试配置,然后让 certbot 把这个块改写为监听 443、放入证书并加上一个 HTTP 到 HTTPS 的重定向:
sudo ln -s /etc/nginx/sites-available/status.example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d status.example.comUpgrade 与 Connection "upgrade" 这一对就是全部胜负所在,而 proxy_read_timeout 3600s 能阻止 nginx 拆掉这个长连接的 socket;certbot 会把两者都复制进它生成的 443 块里。如果您已经在一个代理后面运行了好几个容器,用 Traefik 路由它们并自动配置 TLS 用容器标签做到同样的事,而且默认就转发 WebSocket 升级。
不要对整个 vhost 做 basic-auth,因为那也会把公开状态页和 /api/push 端点一并锁在外面。保留 Uptime Kuma 自带的登录,如果它面向公网,就加上 用 fail2ban 监视反复失败的登录;如果仪表盘从不需要公开,就去掉代理,改为通过 VPN 访问它。
把证书到期监控做对
一个 HTTP(s) 监控也能在 TLS 证书到期前警告您:勾选 Certificate Expiry Notification,Uptime Kuma 就会在到期前设定的天数发出告警。有两个错误会让它误读。要按主机名而不是 IP来监控,否则一个没有 SNI 的请求会拿到服务器的默认证书,您会看到 Hostname/IP does not match certificate's altnames。并且,不要在您想要到期警告的监控上勾选 Ignore TLS/SSL Error:那个开关是给自签名的内部主机用的(unable to verify the first certificate、DEPTH_ZERO_SELF_SIGNED_CERT),但它会让 Uptime Kuma 干脆完全不检查证书,到期检查也包括在内。
备份:就是一个目录
因为一切都住在 /app/data,所以备份就是在容器停止时对那个卷做一份拷贝,这样 SQLite 文件才是一致的:
cd /srv/uptime-kuma
sudo docker compose stop
sudo docker run --rm \
-v uptime-kuma_kuma-data:/data \
-v /var/backups/kuma:/backup \
alpine tar czf /backup/kuma-$(date -u +%Y%m%dT%H%M%SZ).tgz -C /data .
sudo docker compose start先用 docker volume ls | grep kuma 确认卷的真实名称,因为 Compose 会用项目目录名给它加前缀。然后把 tar 包拷贝离开这台机器,因为同一台 VPS 上的备份是一份拷贝,不是备份。恢复则相反:停掉整套服务,解压到一个空的 /app/data 卷里,再启动它。
升级
升级就是拉取一次镜像:
cd /srv/uptime-kuma
sudo docker compose pull
sudo docker compose up -d新容器会在首次启动时运行任何数据库迁移;观察 docker compose logs -f。在拉取之前先做上面的备份,并且停留在同一个主版本标签内:从 :1 移到 :2 是一次单向迁移,所以先备份并查看发布说明。
故障模式,附上您会看到的字符串
指向 localhost 的监控出现误报“down”。监控变红,显示 timeout of 48000ms exceeded 或 connect ETIMEDOUT,可是服务在您的笔记本上明明有响应。如果它指向 Uptime Kuma 运行所在的同一台主机,那是一次 CPU 或内存峰值饿死了检查,而不是目标出了问题。把监控挪到一台独立的 VPS 上,并指向公开主机名。
connect ECONNREFUSED 127.0.0.1:443(或任何端口)。那个端口上没有任何东西在监听:要么服务宕了,要么您是从容器内部监控 localhost,而在那里 127.0.0.1 指的是容器,不是您的服务器。请监控公开主机名,而不是回环地址。
邮件测试时出现 Invalid login: 535-5.7.8 Username and Password not accepted。SMTP 凭据错了,或者服务商要的是应用专用密码而拿到的是您的账户密码。生成一个应用专用密码并粘贴它。
邮件测试时出现 connect ETIMEDOUT 或 queryA ETIMEDOUT <host>。端口不对,或者服务商屏蔽了出站 SMTP。确认 465 或 587 与 Secure/STARTTLS 设置相匹配,并从宿主机用 nc -vz smtp.example.com 587 测试。许多服务商屏蔽出站 25,有些会屏蔽提交端口,直到您提出申请。
邮件测试时出现 self signed certificate 或 unable to verify the first certificate。您的 SMTP 服务器出示了一张 Node 不信任的证书;请修好邮件服务器的证书,而不是遮遮掩掩地绕过它。
仪表盘卡在“Connecting...”,控制台显示 WebSocket connection ... failed。反向代理没有升级 WebSocket。在 nginx 上加上 Upgrade 和 Connection "upgrade" 头,或者用一个默认就转发它们的代理,比如 Traefik 或 Caddy。HTML 能加载是因为那是一次普通的 HTTP GET;只有实时 socket 才需要升级。
证书到期监控从不告警,或告警有误。要么勾选了 Ignore TLS/SSL Error(它会禁用证书检查),要么监控指向的是 IP,因缺少 SNI 而读到了错误的证书,显示 Hostname/IP does not match certificate's altnames。取消勾选 ignore,按主机名监控。
日志里出现 SQLITE_BUSY 或 database disk image is malformed。/app/data 卷位于一个没有正确文件锁的文件系统上,通常是 NFS;把它挪到本地 Docker 卷,并从备份恢复。
FAQ
我该把在线监控运行在哪里?
运行在一台不同于它所监视的服务器上,最好是另一个服务商或区域,像您的用户那样通过公网用主机名访问它们。如果监控与它的目标共享一台机器,那么杀死服务器的故障也会杀死监控,而一台过载的主机会对着本来正常的服务哭喊“down”。一台微小的独立 VPS 能同时避开这两个问题。
我怎样在 Telegram 或邮件上收到告警?
在 Settings 然后 Notifications 下添加渠道,然后把它挂到每个监控上。对于 Telegram,用 @BotFather 创建一个机器人,从 https://api.telegram.org/bot<token>/getUpdates 读出 chat.id;对于邮件,用 465 走 SSL 或 587 走 STARTTLS,如果您的服务商启用了两步验证就用应用专用密码。按 Test 并确认消息送达之后再依赖它。
Uptime Kuma 能监控 cron 任务或备份脚本吗?
能,那就是 Push 监控:Uptime Kuma 给您一个 URL,您在脚本末尾 curl 它,让它只在成功时才触发。如果任务失败或机器宕机,心跳永远不会到达,过了间隔之后您就会被告警。这是知道一个计划任务是否真正运行过的唯一可靠方式,因为外部检查看不到它的内部。
Uptime Kuma 与 Zabbix,我该用哪个?
Uptime Kuma 用十分钟、几乎不占资源就回答“它在线吗,从外部看,它有没有告警我”,还附带一个状态页。它不采集像 CPU、内存和磁盘趋势这样的深层指标,也不做全机群的阈值;要那些,一台完整的 Zabbix 监控服务器 才是更重的、基于 agent 的工具,很多人两个都跑。还在纠结到底该跑什么?我们的 2026 年自托管清单 把监控放进了更大的背景里。