用 Uptime Kuma + kuma-mieru 搭一套轻量、好看、可长期维护的监控面板
很多人第一次接触自托管监控,都会先从 Uptime Kuma 开始:安装简单、支持的监控类型多、Telegram 告警也很好接,几乎可以算是轻量监控的默认答案。
这篇文章就把一套比较稳、也比较适合长期使用的方案整理一下。
一、Uptime Kuma 和 kuma-mieru 分别是什么
Uptime Kuma 是一款开源自托管的监控工具,核心就一件事:定期去戳一下你的服务,看它活没活着,挂了就赶紧告诉你。通知方式也现成,Telegram、Email、Webhook 这些直接就能用,自己还带一个状态页。部署简单、社区大,算是轻量监控里相当稳妥的选择。
它能盯的东西很多,常见的几种探测方式都覆盖了:

kuma-mieru 是一款基于 Next.js 16、TypeScript 和 Recharts 构建的第三方 Uptime Kuma 监控仪表盘。解决了 Uptime Kuma 内建公开状态页面不够直观、没有延迟图表等痛点。


二、部署 Uptime Kuma
1)准备目录
mkdir -p /opt/uptime-kuma
cd /opt/uptime-kuma
2)编写 docker-compose.yml
services:
uptime-kuma:
image: louislam/uptime-kuma:2
container_name: uptime-kuma
restart: unless-stopped
ports:
- "127.0.0.1:3001:3001"
volumes:
- ./data:/app/data
3)启动服务
docker compose up -d
4)本地验活
curl -I http://127.0.0.1:3001
正常情况下会看到类似:
302 Found- 跳转到
/dashboard
这说明 Kuma 已经起来了。
三、为 Uptime Kuma 配置 Nginx 反代
假设你的后台域名是:
uptime.example.com
Nginx 示例配置:
点击展开
server {
listen 80;
server_name uptime.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name uptime.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:3001;
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;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
重载 Nginx:
nginx -t && systemctl reload nginx
然后访问:
https://uptime.example.com
如果能正常打开 Kuma 登录页,就说明主链路已经通了。
四、初始化 Kuma 后,建议先做这几件事
1)创建管理员账号
这是首次使用的基础步骤,就不展开了。
2)打开 2FA
如果后台是公网可访问的,强烈建议开启二步验证。
3)配置 Telegram 通知
Kuma 自带 Telegram 通知已经很好用,建议:
- 消息格式选 HTML
- 不建议选 MarkdownV2(容易被域名、下划线、错误文本之类的内容搞翻)

一个比较顺眼、也比较稳的 Liquid 模板示例:
{% assign s = status | downcase %}
{% if s contains "up" %}
✅ <b>服务恢复</b>
<b>服务</b>:<code>{{ name }}</code>
<b>状态</b>:{{ status }}
<b>目标</b>:{{ hostnameOrURL }}
<b>说明</b>:{{ msg }}
{% else %}
🚨 <b>服务异常</b>
<b>服务</b>:<code>{{ name }}</code>
<b>状态</b>:{{ status }}
<b>目标</b>:{{ hostnameOrURL }}
<b>原因</b>:{{ msg }}
{% endif %}
五、添加监控项
1)监控网站和 API 的 /healthz、某个公开端点

监控类型:HTTP(s)
显示名称:会显示在公开显示面板上,如果不想暴露网址就自定义一个名称
其余设置选项推荐默认,也可按需选择
2)监控定时任务、远端脚本

创建后会得到一条 Push URL,类似:
https://uptime.example.com/api/push/<token>
在远端机器主动上报
最简单的写法:
curl -fsS "https://uptime.example.com/api/push/<token>?status=up&msg=OK"
如果你想根据服务状态决定上报内容,可以写成:
#!/usr/bin/env bash
set -euo pipefail
PUSH_BASE="https://uptime.example.com/api/push/<token>"
SERVICE_NAME="my_service.service"
if systemctl is-active --quiet "$SERVICE_NAME"; then
status="up"
msg="service_active"
else
status="down"
msg="service_inactive"
fi
curl -fsS \
--get \
--data-urlencode "status=${status}" \
--data-urlencode "msg=${msg}" \
"$PUSH_BASE" >/dev/null
相比 cron,我更推荐 systemd timer,因为:
- 状态更好查
- 更像正式服务
- 排障更顺
示例:
service
[Unit]
Description=Push service health to Uptime Kuma
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/service-kuma-heartbeat.sh
timer
[Unit]
Description=Run service Kuma heartbeat every 300 seconds
[Timer]
OnBootSec=60
OnUnitActiveSec=300
AccuracySec=15
Unit=service-kuma-heartbeat.service
[Install]
WantedBy=timers.target
启动命令:
systemctl daemon-reload
systemctl enable --now service-kuma-heartbeat.timer
Push Monitor 提醒:超时窗口别和上报频率设得太接近。
经验法则是 超时窗口至少设成上报频率的 2 倍,稳一点就设 3 倍——比如300 / 900。
为什么这么设、踩线会发生什么,详见文末附录一。
六、配置 Uptime Kuma 的公开状态页
1)新建一个 Status Page
例如:
- Title:
My Status - Slug:
my-status
生成后地址通常类似:
https://uptime.example.com/status/my-status
对应的 API 地址一般类似:
https://uptime.example.com/api/status-page/my-status
2)记住这个重点
Monitor 不会自动出现在状态页里。
创建了监控项,还得手动把它加入某个 status page group,它才会出现在公开状态页和 kuma-mieru 里。这个坑很反直觉,详细见文末附录二。
3)分组建议
不要把所有服务都堆在一个组里。推荐按用途分:
Public SitesCore InfraBotsExternal
可以拖动调整分组和顺序,保存后与面板显示页同步

七、部署 kuma-mieru
推荐使用 vercel 或者 Cloudflare Workers 部署(参考Alice39s/kuma-mieru)
以下是本地部署方案:
1)准备目录
mkdir -p /opt/kuma-mieru
cd /opt/kuma-mieru
2)编写 .env
UPTIME_KUMA_URLS=https://uptime.example.com/status/my-status
KUMA_MIERU_TITLE=My Status
KUMA_MIERU_DESCRIPTION=My uptime dashboard
KUMA_MIERU_ICON=/icon.svg
KUMA_MIERU_EDIT_THIS_PAGE=false
KUMA_MIERU_SHOW_STAR_BUTTON=false
ALLOW_INSECURE_TLS=false
ALLOW_EMBEDDING=false
3)编写 docker-compose.yml
services:
kuma-mieru:
image: ghcr.io/alice39s/kuma-mieru:1
container_name: kuma-mieru
restart: unless-stopped
env_file:
- .env
ports:
- "127.0.0.1:3883:3000"
注意:不同镜像版本内部端口可能存在差异,实际部署时以镜像说明为准。
4)启动
docker compose up -d
5)本地验活
curl -I http://127.0.0.1:3883
curl http://127.0.0.1:3883/api/health
如果本地能正常返回,说明展示层已经起来了。
八、给 kuma-mieru 做 Nginx 反代
假设状态页域名是:
status.example.com
Nginx 示例配置:
点击展开
server {
listen 80;
server_name status.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name status.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:3883;
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;
}
}
重载:
nginx -t && systemctl reload nginx
然后访问:
https://status.example.com
附录:实战时踩过的几个坑
下面这几个坑,都是这套方案里很值得提前知道的。
附录一、Push Monitor 不要把“上报频率”和“超时窗口”设成一样
这是最典型、也最容易让人误判的一类问题。
现场现象
服务明明没挂,但监控里会出现这种节奏:
- 先
Down - 紧接着又
Up - 过几分钟再
Down - 然后又
Up
如果刚好是每隔 5 分钟抖一次,十有八九就是时间窗口踩线了。
根因
比如:
- 远端脚本每 300 秒 push 一次
- Kuma 也按 300 秒 判 heartbeat 超时
那只要有一点点误差:
- systemd timer 晚几秒
- 网络慢几秒
- Kuma 自己检查点早几秒或晚几秒
它就会在下一次 heartbeat 抵达前,先一步把服务判成:
No heartbeat in the time window
然后新 heartbeat 一到,又立刻恢复成 Up。
正确做法
让两边明显错开。
例如:
- Push 频率:
300 秒 - 超时窗口:
900 秒
经验法则可以直接记成一句:
Push Monitor 的超时窗口,至少设成上报频率的 2 倍;想更稳,就设 3 倍。
也就是:
300 / 600:能用300 / 900:更稳
如果只是拿来监控普通服务是否存活,我会更推荐直接上 300 / 900。
附录二、Monitor 建好了,不代表它会自动出现在状态页里
这个坑特别容易踩,因为它很反直觉。
很多人会默认以为
只要在 Uptime Kuma 后台里新建了一个 monitor,它就应该:
- 后台能看到
- 状态页也能看到
- kuma-mieru 当然也该能看到
但实际上不是。
真正的逻辑是
- Monitor 列表 是后台的监控项清单
- Status Page 是你手动挑出来要公开展示的那部分 monitor
- kuma-mieru 读取的是 Kuma 的公开状态页,不是后台 monitor 总表
所以只要少了“加入状态页分组”这一步,就会出现:
- 后台有 monitor
- 状态页没有它
- kuma-mieru 也没有它
正确理解
创建 monitor 和把 monitor 加入 status page,是两步,不是一步。
如果你发现某个服务明明已经在 Kuma 后台里了,但前台展示死活不出来,先别怀疑 kuma-mieru,先检查它有没有被加入对应的 status page group。
附录三、kuma-mieru 读取的是公开状态页,不是 Kuma 私有后台
这一点值得单独拎出来强调,因为很多后续误会都从这里来。
- kuma-mieru 读取的是类似:
/status/<slug>- 或
/api/status-page/<slug>
也就是说,它依赖的是:
- 你先在 Kuma 里创建了公开状态页
- 这个状态页已经发布
- monitor 已经被加入这个状态页
- kuma-mieru 配置里填的是正确的状态页 URL
这意味着什么
它是个很清晰的“展示层”思路:
- Kuma 后台负责监控与管理
- 状态页负责挑选公开内容
- kuma-mieru 负责把公开内容展示得更好看
也正因为如此,kuma-mieru 这层本身并不会替你决定“哪些 monitor 要公开”。这个决定仍然在 Kuma 的 status page 那一层完成。
附录四、Telegram 通知模板想顺眼,重点不只是模板本身
很多人第一次玩 Kuma 自定义通知,都会先去折腾模板语法。但真要让告警看起来舒服,通常影响最大的其实是两件事:
- 消息格式选 HTML
- monitor 名称本身别太难看
为什么推荐 HTML
因为监控消息里经常会带这些东西:
- 域名
- URL
- 错误信息
- 下划线
- 括号
- 连字符
如果用 MarkdownV2,很容易在转义规则出问题。
为什么命名也很重要
模板再顺,如果 monitor 名称本身还是:
rss_botcli.example.comservice_check_01
观感也会被拖下去。
更适合展示和告警的命名会像:
📰 Nodeseek RSS🤖 CPA API🌐 Blog🧭 OneNav📈 Umami
所以别把“模板优化”理解成只改模板字符串,它其实是:
模板 + 命名 + 分组,一起决定最终观感。
附录五、不要把“端口监听正常”误当成“整条链路没问题”
这类问题也很常见。
比如你本地去 curl 容器端口,发现:
127.0.0.1:3001正常127.0.0.1:3883正常
这只能说明:
- 容器本身活着
- 本机监听没问题
但对外访问是否正常,还取决于:
- Nginx 是否命中正确的
server_name - TLS 证书是否正常
- 反代配置是否写对
- 域名是否真正走到了预期站点
尤其是在一台宿主机上挂了多个站点、默认站点还在工作的情况下,“访问能打开页面”不一定等于“命中了你以为的那个 vhost”。
所以遇到展示不对、域名结果怪怪的场景,排查顺序最好是:
- 先看本地端口
- 再看 Nginx 配置
- 再用
Host/SNI方式确认实际命中链路