想给手机推一条自己发的消息,又不想装 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 就够,浏览器打开服务地址也能在网页里发。
适合谁
适合想自建一条轻量通知通道的人。常见的三种用法:
- 服务器告警,磁盘满了或者进程挂了就推一条
- 脚本跑完提醒自己,不用守着终端
- 家里设备状态变化,比如温湿度超标
反过来,如果你需要消息持久化,或者要开箱即用的手机推送,又或者不想自己管端口和安全,这几点这次没验证或不支持,得另找方案。







