定时任务监控服务 Healthchecks.io 从 2015 年上线到现在,一直只有一个人在做。创始人 Pēteris Caune 在官网 About 页公布,截至 2026 年 10 月 2 日,月经常收入(MRR)是 26,000 美元,付费账户 1,236 个。他至今没有招人,也明确说过不追企业大单、不给付费档涨价。
下面的收入和用户数字都来自 Pēteris 自己的博客和官网,属于创始人自述。About 页只写了「盈利、可持续」,没有公开利润额。
它解决的是「任务没跑」这件事
Healthchecks.io 用的是死人开关(Dead man's switch)的思路。你的定时任务每跑完一次,就请求一下它给的地址,相当于报个平安。超过约定时间没收到,它就发告警。
Pēteris 在 2018 年的博客里举过一个例子。数据库备份脚本配好了,手动跑一遍,S3 里也出现了备份文件。半年后它悄悄停了,你会发现吗?可能是 DBA 改了数据库密码忘了改脚本,可能是磁盘慢慢满了,也可能是有人清理了 IAM 权限。这些情况任务自己不会报错,日志里也没人看。让它每次跑完报个到,报到断了就告警,问题就能被发现。
他做这个产品的起因也很简单。2015 年他自己想找一个这样的服务,市面上已经有 Dead Man's Snitch 和 Cronitor,但他觉得要监控的东西没那么重要,这些服务太贵了。用他的话说,他有点自大地认为自己能做一个更便宜也更好的。
从 5 美元到 2.6 万美元
2018 年 8 月 24 日,Healthchecks.io 三周年时,Pēteris 写了一篇 My One-person SaaS Side Project Celebrates its Third Birthday,里面有一张时间表。2015 年 6 月 11 日第一次提交代码,用的是一个空白的 Django 项目。7 月 29 日网站上线,跑在一台每月 5 美元的 DigitalOcean 机器上。2016 年 3 月 31 日来了第一个付费客户,MRR 5 美元。2016 年 10 月 31 日 MRR 到 100 美元。2017 年 10 月迁到 Hetzner 的独立服务器。
写那篇文章时,他有大约 90 个付费客户,月收入刚过 700 美元,大部分又花回了运行成本。他当时在一家爱尔兰小公司远程上班,Healthchecks.io 是副业。他写道,扣掉账单和税,剩下的利润很少。他也知道怎么省钱,换几台便宜 VPS、去掉负载均衡、大幅收紧免费档,逼人升级。但他不愿意,因为那样就违背了最初的目标:个人免费,公司付合理的价钱,服务质量要好。
2021 年 7 月的 六周年更新 里,他说已经减少了其他工作,Healthchecks.io 成了他的主业和主要收入来源,「还不算全职,但快了」。收入慢慢涨上来以后,收购询问的邮件也多了,他说短期内没有卖的打算,投入了太多心血,而且自己就是喜欢做这件事。
2024 年 7 月 29 日的 Running One-man SaaS, 9 Years In 给了一组完整数字:652 个付费客户,MRR 14,043 美元。从 2022 年 1 月起,Healthchecks.io 是他唯一的收入来源,但他仍然只用部分时间做。他没用任何收入分析服务,MRR 曲线是自己写脚本加电子表格画的,理由是尽量减少数据子处理方。两年前那篇讲他不想赚更多、也不想把公司做大的文章,写的就是这个阶段。
到 2026 年 10 月 2 日,官网 About 页 的数字是这样的:
| 指标 | 数值 |
|---|---|
| 运营年数 | 11 年 |
| 免费账户 | 143,000 |
| 开源项目和非营利账户 | 106 |
| 付费账户 | 1,236 |
| MRR | 26,000 美元 |
| 被监控的任务数 | 477,000 |
| 每天处理的 ping | 1.02 亿次 |
| 每天发出的通知 | 173,000 条 |
页面上还写着,Healthchecks.io 把 MRR 的 5% 捐给它直接或间接依赖的开源项目,大部分通过 GitHub Sponsors 发出。
不招人,不追大客户,不涨价
2018 年那篇文章里,Pēteris 写过等财务上允许了,会考虑扩大团队,降低只有一个人的风险。到 2024 年,他的想法变了。他说暂时不打算招人,自己做独立开发者很大程度上是因为不想管人,也不想被人管。有了合伙人或员工,就得定期开会讨论做了什么、要做什么。
同一篇文章里还有几个明确的不做。免费档的额度不收紧,付费档也不涨价,理由还是 2015 年那句「这件事可以做得更好、更便宜」。企业客户要的采购订单、电汇付款、定制合同、注册供应商门户,他统统不接。他知道这是把钱留在桌上,但对他来说,多出来的钱带不来实质变化,多出来的麻烦却会让他更忙、更烦。功能上也不扩张,不做主动的可用性监控、托管状态页或 APM。他管这个产品叫「霍比特人软件」,管这门生意叫生活方式型生意。
今年 2 月,他取消了所有套餐的团队人数限制。以前免费和 Supporter 档最多 3 人,Business 档 10 人。他在 公告 里解释,多年数据显示,用户升级几乎都是因为监控任务数不够用,因为团队人数升级的很少。他赌的是放开以后大家会拉更多同事进来,长期看口碑带来的价值会超过少收的那点钱。
一个人运营也有代价。About 页直说,数据库故障切换是手动的,运维团队只有一个人,出现几小时甚至几天的故障都有可能。今年 5 月底还有人专门针对它搞了一次短信轰炸,批量注册免费账户,用免费额度往摩洛哥号码发短信,大约 8000 条,给 Twilio 账单加了大约 1800 美元。他把免费账户的短信额度从 5 条改成了 0,Twilio 后来全额退了款。
10 月 13 日最新的 状态更新 里,他提到从 5 月起新注册明显加快,单是 9 月,注册用户就从 11.2 万涨到 14.3 万,一个月涨了 27%。原因是什么,他说自己也不确定。
现在的定价
按 2026 年 10 月 20 日的 定价页,一共四档,按月付:
| 档位 | 月价 | 可监控任务 | 每个任务保留日志 | 短信和 WhatsApp | 电话通知 | 支持 |
|---|---|---|---|---|---|---|
| Hobbyist | $0 | 20 | 100 条 | — | — | — |
| Supporter | $5 | 20 | 100 条 | — | — | — |
| Business | $20 | 100 | 1000 条 | 每月 50 条 | 每月 20 次 | 邮件支持 |
| Business Plus | $80 | 1000 | 1000 条 | 每月 500 条 | 每月 100 次 | 优先邮件支持 |
定价页对这两个低档没有列短信、电话和支持。Supporter 和 Hobbyist 的额度完全一样,页面上的说明是给那些免费档够用、但想出钱支持一下的人。年付打八折,Business 是每年 192 美元,Business Plus 是每年 768 美元。开源项目和非营利组织可以免费用 Business 档,联系他们开通。代码以 BSD 许可证开源,可以自己部署。
在 Spring Boot 里接上它
接入方式就是发 HTTP 请求。任务开始时请求 /start,成功了请求基础地址,失败了请求 /fail,还可以把错误信息放在请求体里。下面这个例子在本地自部署了一个开源版 Healthchecks(GitHub 上 2026 年 10 月 19 日的代码,SQLite 存储),没有调用 hc-ping.com 线上服务。建一个叫 nightly-report 的检查,周期和宽限期都设成 60 秒。
Spring Boot 4.1.1,只引了 spring-boot-starter-restclient。任务每 5 秒跑一次,第 3 次故意抛异常:
@Component
public class NightlyReportJob {
private static final Logger log = LoggerFactory.getLogger(NightlyReportJob.class);
private final RestClient ping;
private int runs = 0;
public NightlyReportJob(RestClient.Builder builder,
@Value("${hc.ping-url}") String pingUrl) {
this.ping = builder.baseUrl(pingUrl).build();
}
@Scheduled(fixedDelay = 5000)
public void run() {
ping.get().uri("/start").retrieve().toBodilessEntity();
try {
runs++;
if (runs == 3) {
throw new IllegalStateException("report query timed out");
}
log.info("run {} finished", runs);
ping.get().retrieve().toBodilessEntity();
} catch (Exception e) {
log.warn("run {} failed: {}", runs, e.getMessage());
ping.post().uri("/fail").body(e.getMessage()).retrieve().toBodilessEntity();
}
}
}
hc.ping-url=http://localhost:8000/ping/41c703a0-e395-4185-ba34-bcfba5ff3819
启动类上加 @EnableScheduling。用 timeout 23 跑了一轮,应用日志如下,省略了启动横幅:
2026-10-20T08:12:34.906+08:00 INFO 364018 --- [ main] com.example.hcdemo.HcDemoApplication : Started HcDemoApplication in 0.934 seconds (process running for 1.267)
2026-10-20T08:12:34.975+08:00 INFO 364018 --- [ scheduling-1] com.example.hcdemo.NightlyReportJob : run 1 finished
2026-10-20T08:12:40.038+08:00 INFO 364018 --- [ scheduling-1] com.example.hcdemo.NightlyReportJob : run 2 finished
2026-10-20T08:12:45.102+08:00 WARN 364018 --- [ scheduling-1] com.example.hcdemo.NightlyReportJob : run 3 failed: report query timed out
2026-10-20T08:12:50.195+08:00 INFO 364018 --- [ scheduling-1] com.example.hcdemo.NightlyReportJob : run 4 finished
2026-10-20T08:12:55.264+08:00 INFO 364018 --- [ scheduling-1] com.example.hcdemo.NightlyReportJob : run 5 finished
Healthchecks 那边的访问日志,时间是 UTC:
[20/Oct/2026 00:12:34] "GET /ping/41c703a0-e395-4185-ba34-bcfba5ff3819/start HTTP/1.1" 200 2
[20/Oct/2026 00:12:34] "GET /ping/41c703a0-e395-4185-ba34-bcfba5ff3819 HTTP/1.1" 200 2
[20/Oct/2026 00:12:40] "GET /ping/41c703a0-e395-4185-ba34-bcfba5ff3819/start HTTP/1.1" 200 2
[20/Oct/2026 00:12:40] "GET /ping/41c703a0-e395-4185-ba34-bcfba5ff3819 HTTP/1.1" 200 2
[20/Oct/2026 00:12:45] "GET /ping/41c703a0-e395-4185-ba34-bcfba5ff3819/start HTTP/1.1" 200 2
[20/Oct/2026 00:12:45] "POST /ping/41c703a0-e395-4185-ba34-bcfba5ff3819/fail HTTP/1.1" 200 2
[20/Oct/2026 00:12:50] "GET /ping/41c703a0-e395-4185-ba34-bcfba5ff3819/start HTTP/1.1" 200 2
[20/Oct/2026 00:12:50] "GET /ping/41c703a0-e395-4185-ba34-bcfba5ff3819 HTTP/1.1" 200 2
[20/Oct/2026 00:12:55] "GET /ping/41c703a0-e395-4185-ba34-bcfba5ff3819/start HTTP/1.1" 200 2
[20/Oct/2026 00:12:55] "GET /ping/41c703a0-e395-4185-ba34-bcfba5ff3819 HTTP/1.1" 200 2
用 Django shell 查这个检查的记录,第一行是名称、状态和 ping 总数,后面每行依次是序号、类型、请求方法、时间(UTC)和耗时。每对 start 和结束信号之间的耗时它会自己算出来:
nightly-report up 10
1 start GET 2026-10-20T00:12:34+00:00 None
2 success GET 2026-10-20T00:12:34+00:00 0:00:00.024080
3 start GET 2026-10-20T00:12:40+00:00 None
4 success GET 2026-10-20T00:12:40+00:00 0:00:00.008808
5 start GET 2026-10-20T00:12:45+00:00 None
6 fail POST 2026-10-20T00:12:45+00:00 0:00:00.034517
7 start GET 2026-10-20T00:12:50+00:00 None
8 success GET 2026-10-20T00:12:50+00:00 0:00:00.007774
9 start GET 2026-10-20T00:12:55+00:00 None
10 success GET 2026-10-20T00:12:55+00:00 0:00:00.008421
第 3 次的失败信号和请求方法都记下来了。状态变化记录显示,这次 fail 让检查在 08:12:45(北京时间)短暂变成 down,08:12:50 下一次成功后又回到 up,跑完最后一次时状态是 up。真正要看的是任务不再报到时会怎样。应用停掉以后,到北京时间 08:15:14 再查,状态变成了 down:
2026-10-20 08:15:14 CST
nightly-report down 2026-10-20T00:12:55+00:00
本地自部署的实例没有配置邮件和其他通知渠道,所以这里只看到状态变化,没有真的发出告警。线上版本里,这一步会按你配置的邮件、Slack、短信等渠道通知。任务在集群里多实例部署时,每台机器都会各报一次到。怎么让它只跑一份,之前那篇讲 Spring Boot 定时任务只执行一次的文章写过。
参考链接
- Healthchecks.io About 页,Open Data 更新于 2026-10-02:https://healthchecks.io/about/
- Healthchecks.io 定价页(2026-10-20 查阅):https://healthchecks.io/pricing/
- Pēteris Caune,My One-person SaaS Side Project Celebrates its Third Birthday(2018-08-24):链接
- Pēteris Caune,Healthchecks Turns 6, Status Update(2021-07-15):链接
- Pēteris Caune,Running One-man SaaS, 9 Years In(2024-07-29):链接
- Pēteris Caune,Unlimited Team Sizes for All(2026-02-18):链接
- Pēteris Caune,About the SMS Pumping Attack on May 26-27(2026-06-10):链接
- Pēteris Caune,Status Updates, October 2026(2026-10-13):链接
- Healthchecks 源码(BSD 许可证):https://github.com/healthchecks/healthchecks
一起交流
分享你的思考,让讨论更进一步。