Uptime Kuma Docker 自建监控与告警配置
在独立 VPS 上用 Docker 部署 Uptime Kuma,监控网站、端口、DNS 和 cron 任务,并通过邮件或 Telegram 告警。文中提供 Compose 配置、状态页发布及告警测试方法。
构建内容
运行一个小型容器,从外部监控其他服务器和网站,并在某个目标停止响应时立即通过电子邮件、Telegram、Discord 或 webhook 通知您。Uptime Kuma 由一个 Node 进程和一个 SQLite 文件组成,因此在 256-512 MB 内存中即可稳定运行,并提供实时仪表板、历史图表和公共状态页面。安装只需一个十行的 Compose 文件;真正重要的是将它运行在哪里,以及是否在测试中确认过告警确实能够触发。如果从未验证过监控系统能联系到您,那么它甚至不如完全没有监控:它会让您以为系统受到保护,实际上却什么都没有监控。
在故障无法影响的位置运行监控
这个决定会直接影响整个方案,因此必须首先确定。不要将 Uptime Kuma 运行在被监控对象所在的同一台服务器上。 如果监控程序与被监控服务器位于同一台机器上,那么你关心的事件——服务器宕机或内存耗尽——也会同时终止监控程序,导致完全收不到告警:宕机监控程序没有任何响应,与“一切正常”看起来完全相同。即使服务器仍在运行,也存在一个更隐蔽的问题:指向 localhost 的监控程序与业务负载共享 CPU,因此负载突增可能导致监控检查超时,并将目标错误地标记为 down,产生误报,而真实用户仍可正常访问服务。
因此,应将 Uptime Kuma 运行在与被监控服务器不同的 VPS 上,最好还使用不同的服务商或区域,并按照用户访问服务的方式,通过公网和主机名访问这些服务。一台低成本实例即可满足需求,一台小型监控 VPS 就能监控所有服务器。对于托管的重负载应用,这种隔离尤其重要。例如,PhotoPrism 或 Immich 照片库在为新导入的内容建立索引时,可能让 CPU 持续满载数小时;如果监控程序与其共享硬件,就会将仅仅处于繁忙状态的服务错误地判定为 down。若要检测 Kuma 自身是否宕机,可在其他位置通过 cron 添加 push 心跳。
前置条件和资源规格
- 一台全新的 Ubuntu 24.04 VPS,已安装 Docker Engine 和 Compose v2 插件。应从 Docker 自己的 apt 软件源安装,而不是使用更新滞后的
docker.io发行版软件包。 - 256 MB RAM 可运行少量监控项;512 MB 到 1 GB 足以舒适地运行几十个监控项及反向代理。检查之间 CPU 基本处于空闲状态。
- 一个域名和 DNS
A记录(例如status.example.com指向该 VPS),仅当您需要 TLS 和公开状态页时才需要。私有实例可以跳过 DNS,改用 VPN 或 SSH 隧道。 - 到告警目标的出站网络连接:通过 SMTP 连接邮件服务商,或通过 HTTPS 连接 Telegram 和 Discord。
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 会将仪表板暴露到公网,不受防火墙配置影响。绑定到 loopback 后,服务保持私有状态,只有反向代理对外暴露;私有实例也可以跳过代理,通过自托管的 WireGuard VPN访问 3001。
在 /app/data 使用命名卷。 Uptime Kuma 保存的所有数据都位于此处,包括 SQLite 数据库、监控项、通知设置和状态页徽标。丢失该卷后,服务会从空的管理界面开始;这是唯一必须备份的内容。
将镜像固定到主版本标签 :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 then Notifications then Setup Notification,使用每个渠道的 Test 按钮确认消息能够送达,因为未经测试的通知是导致配置静默失败的第二常见原因。
Email (SMTP)。 填写主机、端口、加密方式、用户名、密码、From 和 To。可用的两种组合是 465,并将“Secure”设置为 TLS/SSL;或使用 587,并启用 STARTTLS。对于 Gmail 和大多数启用双因素身份验证的服务商,必须生成 app password;使用普通账户密码会返回 Error: Invalid login: 535-5.7.8 Username and Password not accepted。
Telegram。 向 @BotFather 发送消息,发送 /newbot,然后复制机器人令牌。要获取聊天 ID,先向新机器人发送一条消息,打开 https://api.telegram.org/bot<token>/getUpdates,再从 JSON 中读取 chat.id。如果从未先向机器人发送消息,getUpdates 会为空,机器人也没有可发送消息的目标。
Discord。 在频道中打开 Edit Channel then Integrations then Webhooks then New Webhook,复制 URL,然后将其粘贴为 Discord 通知。
Generic webhook。 对于其他服务,例如 Slack incoming webhook、自定义端点或家庭自动化钩子,Webhook 类型会将 JSON 负载通过 POST 请求发送到你提供的 URL;内置的 Apprise 集成涵盖列表中大多数其他服务,共九十多种。如果不希望在故障与手机之间经过第三方服务,请选择内置的 ntfy 类型,并将其指向 你自行运行的 ntfy 服务器。它会通过由你完全控制的渠道将通知推送到手机。
逐次添加监控
单击 Add New Monitor,选择类型,然后设置 Friendly Name、Check Interval(60 seconds 是合理值)、Retries(连续失败多少次后判定为“down”;设为 2 或 3,可避免单个丢包就触发告警)以及要触发的通知。以下是你会使用的类型:
- HTTP(s)。 完整 URL。状态码被接受即表示正常(默认范围为 200-299;如果
301或401对你来说是正常响应,可在 Accepted Status Codes 中扩大范围)。这是网站和 API 的主要监控方式。 - HTTP(s) - Keyword。 发送相同请求,但只有响应正文中包含指定字符串,或启用 Invert 后不包含指定字符串,才判定为“up”。它可以发现网站返回
200 OK,但页面显示“Error establishing a database connection”的情况;普通 HTTP 检查会将其误判为正常。对于与独立后端通信的浏览器前端,这也是正确的检查方式,例如 基于 Jellyfin 的 Halcyon 视频商店界面:页面外壳可以正常返回200,但其后端媒体服务器可能无法访问。 - TCP Port。 对主机和端口建立基本 TCP 连接,适用于非 HTTP 服务,例如 22 端口上的 SSH、5432 端口上的 Postgres、25 端口上的 SMTP 服务器以及游戏服务器。
- Ping。 ICMP 回显检查,用于低成本检测可达性和延迟。但许多网络和云防火墙会丢弃 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 会将监控状态切换为宕机并向您告警。请将该推送令牌视为密钥:任何获得它的人都可以伪造正常心跳。
构建公共状态页
状态页是面向客户的视图:显示哪些服务正常运行及其近期历史记录,但不会公开您的仪表板。进入 Status Pages then New Status Page,设置名称和 slug(公共路径,例如 /status/main),将所需监控项拖入 “Websites” 和 “APIs” 等分组,添加徽标和简短说明,然后点击 Save。您还可以将状态页绑定到独立域名,使 status.example.com 直接提供该页面。
请注意以下两点:只添加您愿意公开的监控项,因为状态页会显示某项服务是否存在以及当前是否正常运行;仪表板仍受登录保护,而状态页是有意公开的,无需身份验证。
将其置于带 TLS 的反向代理后,并正确处理 WebSocket
对于公网实例,应在绑定 loopback 的容器前放置反向代理,为其提供 TLS 和主机名。最容易出错的地方是:Uptime Kuma 的 UI 是实时 Socket.IO 应用,因此代理必须升级 WebSocket 连接。如果遗漏此配置,页面可以加载,但始终无法连接;控制面板会停留在“正在连接...”,实时心跳不会更新,浏览器控制台会显示 WebSocket connection to 'wss://.../socket.io/...' failed。
安装 nginx 和 certbot,然后编写将请求代理到 loopback 端口的 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 会为其添加项目目录前缀。然后将 tarball 复制到服务器之外,因为 VPS 上的副本只是副本,不算备份。恢复过程相反:停止整个堆栈,将内容解压到空的 /app/data 卷中,然后启动堆栈。
升级
升级就是拉取新镜像:
cd /srv/uptime-kuma
sudo docker compose pull
sudo docker compose up -d新容器首次启动时会执行数据库迁移;请监控 docker compose logs -f。请在拉取镜像之前完成上文所述的备份,并保持在同一主版本标签内:从 :1 迁移到 :2 是单向迁移,因此请先备份,再查看发行说明。
故障模式及其对应提示信息
监控指向 localhost,却误报“宕机”。 监控显示红色,并出现 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 信任。应修复邮件服务器的证书,不要通过绕过证书校验来掩盖问题。
仪表板停留在“正在连接...”,控制台显示 WebSocket connection ... failed。 反向代理未升级 WebSocket。在 nginx 中添加 Upgrade 和 Connection "upgrade" 请求头,或者使用默认转发这些请求头的代理,例如 Traefik 或 Caddy。HTML 可以正常加载,因为它使用普通的 HTTP GET;只有实时套接字需要升级。
证书到期监控始终不告警,或告警不正确。 可能勾选了 忽略 TLS/SSL 错误,导致证书检查被禁用;也可能是监控目标使用 IP,因缺少 SNI 而读取了错误的证书,并显示 Hostname/IP does not match certificate's altnames。取消勾选忽略错误,并按主机名进行监控。
日志中出现 SQLITE_BUSY 或 database disk image is malformed。 /app/data 卷位于不支持正确文件锁定的文件系统上,通常是 NFS。将其迁移到本地 Docker 卷,并从备份恢复。
FAQ
应该在哪里运行 uptime monitor?
应将其运行在与被监控服务器不同的服务器上,最好选择其他服务商或区域,并像用户一样通过主机名从公网访问这些服务器。如果监控服务与目标服务共用一台服务器,导致服务器宕机的故障也会同时终止监控服务;主机负载过高时,还可能让监控服务将实际正常的服务误报为“down”。单独使用一台小型 VPS 可以避免这两种情况。
如何通过 Telegram 或电子邮件接收告警?
在 Settings then 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 监控服务器 是功能更重的基于代理的工具,许多人会同时运行两者。如果您还没有决定是否运行监控,我们关于 2026 年自托管项目的总结 可以帮助您了解监控在整体方案中的位置。