想给手机推一条自己发的消息,又不想装 App、不想调第三方接口,ntfy 是个省事的选择:解压一个 76MB 的 exe,一条 curl 就能发出去。我在 Windows 上跑了 2.32.0,从下载校验到读取消息走了一遍,撞到两个必须知道的坑。

ntfy 是 Go 写的开源推送服务,客户端往主题(topic)POST 一条消息,订阅了该主题的浏览器、手机或命令行就能收到。默认只在本机跑,不开公网。

怎么拿到包,怎么确认没坏?

包体 32362142 字节。本地网络直连 GitHub release 失败,curl 报 CONNECT tunnel failed 和 HTTP 502,改从一台能访问 GitHub 的服务器下载再传回来。

下载完先核对校验值:

certutil -hashfile ntfy_2.32.0_windows_amd64.zip SHA256

输出 f03eacd89d065a693d9a219d62dca4cbbe9ee7d06377db91be725fa789e57a63,和官方 checksums.txt 里那一行逐字符一致。

解压出来是 ntfy.exe(76194816 字节),另外还有 LICENSE 和 README.md,以及一个 client 目录。ntfy.exe --version 输出 ntfy version 2.32.0。

怎么起服务,怎么确认它活着?

直接跑就起来了,Windows 上没有 /etc/ntfy/ 目录,服务用内置默认值,不读任何配置文件。

ntfy.exe serve --listen-http :25867

启动日志打出 INFO Listening on :25867[http],跟着是版本号和日志级别。

健康检查走 /v1/health,返回 {"healthy":true,"role":"active"}。看到这个就可以开始发了。

发一条消息,返回什么?

curl -d "库存补货了,价格 ¥199" http://127.0.0.1:25867/shiceku-test

HTTP 200,耗时 2.06 ms,响应体带回了完整字段:

{"id":"7vIsSTA4ZIPM","time":1792482373,"expires":1792525573,
 "event":"message","topic":"shiceku-test","message":"库存补货了,价格 ¥199"}

中文原样保存,用 ?poll=1 读回来没有乱码。

中文标题为什么会让请求卡死?

这是最容易踩的坑。把标题直接写成中文:

curl -H "Title: 库存提醒" -d "正文" http://127.0.0.1:25867/shiceku-test

请求卡住不返回,8 秒后超时,HTTP 000,消息没进队列。

加 -v 看,客户端把 Title: 库存提醒 原样送出去了,没有任何报错;服务端那边也没有日志,连请求记录都没打,但进程还活着。紧接着发一条英文 Title: Stock,立刻返回 200。

解法:中文标题要按 RFC2047 编码。改成 Title: =?UTF-8?B?5bqT5a2Y5o+Q6YaS?= 之后返回 200、1.9 ms,读回来的 title 正确显示「库存提醒」。

也就是说,正文中文没问题,只有 Title 头会卡。用 curl 发中文标题,一定要先编码。

优先级和标签怎么加?

加 Priority: high,消息 JSON 里会多出 "priority":4。

加 Tags: warning,多出 "tags":["warning"]。

这两项只是给客户端看的提示,不影响投递。

订阅是怎么推的?

走 SSE,端点连上先推两条:event: open,然后一条 {"id":"V173T5EQBj8Z","event":"open","topic":"shiceku-test"}。之后连接保持挂着,等新消息。

浏览器端就是靠这个实时刷新的。

重启之后消息去哪了?

重启前 ?poll=1 能读到 6 条消息,重启后同一条命令返回 0 条。

程序目录里只多了一个 serve.log,没有 cache.db 之类的数据文件。

默认是纯内存存储,进程一停消息全没。要留存得改用文件或数据库后端,这一步本次没有验证。

常用参数

参数 / 头作用
--listen-http :25867监听端口,例子用 25867
-d "内容"curl 发消息时的正文
Title:消息标题,中文必须 RFC2047 编码
Priority:优先级,high 对应 JSON 里的 priority 4
Tags:标签,对应 JSON 里的 tags 数组
?poll=1一次性拉取该主题的历史消息

常见问题

能用手机接收吗?

能,但要把服务开放到手机能访问的地址。这次只测了 127.0.0.1 本机,没做局域网和公网暴露,端口和安全配置不在验证范围。

消息能永久保存吗?

默认不行。实测重启后消息数从 6 变 0,只留一个日志文件。要持久化得换存储后端。

为什么用 curl 发中文标题会一直不返回?

服务端不接受未编码的非 ASCII 头值,但也不报错、不写日志,表现就是卡住直到超时。改成 RFC2047 编码即可。

要装客户端才能发消息吗?

不用。一条 curl 就够,浏览器打开服务地址也能在网页里发。

适合谁

适合想自建一条轻量通知通道的人。常见的三种用法:

  • 服务器告警,磁盘满了或者进程挂了就推一条
  • 脚本跑完提醒自己,不用守着终端
  • 家里设备状态变化,比如温湿度超标

反过来,如果你需要消息持久化,或者要开箱即用的手机推送,又或者不想自己管端口和安全,这几点这次没验证或不支持,得另找方案。