ntfy
更新于 2026-10-21 16:14:10 · 创建于 2026-09-03 16:05:57
一句话
ntfy(读作 notify)是一个基于 HTTP 的发布/订阅通知服务:一条 curl -d 就能把消息推到手机或桌面,不用注册、不用付费、不用 SDK;不想用公共实例就自己 ntfy serve 起一个。
- 仓库:https://github.com/binwiederhier/ntfy | 官网 / 公共实例:https://ntfy.sh
- 规模:35.0k star / 1.7k fork,Apache-2.0,最新 v2.32.0(2026-10-18)
- 客户端:Android(Play Store / F-Droid)、iOS(App Store)、Web、CLI、桌面
- 形态:Go 单二进制,小到能塞进路由器 / 树莓派
核心心智模型:topic 就是地址
# 发(PUT 或 POST 都行,等价)
curl -d "备份完成" ntfy.sh/my-topic
# 收(命令行订阅,实时流)
curl -s ntfy.sh/my-topic/json
没有"注册应用"、没有"绑定设备"、没有"拿 token 换 device id"。URL 里的那一段就是通道,手机 App 里订阅同一段字符串就能收到。这是它和 gotify 最大的思维差异:gotify 是"应用 → 客户端"的定向投递,ntfy 是"往一个名字上广播"。
调试的爽感也来自这里:浏览器地址栏敲 https://ntfy.sh/my-topic 就是一个网页版收件箱。
消息能带什么
一条消息不是只有正文,头部参数就能干很多事:
| 参数 | 作用 |
|---|---|
title |
标题 |
priority |
1(min)~ 5(max),决定响声、震动、弹出行为 |
tags |
标签,写 emoji 短代码会直接渲染(warning、white_check_mark…) |
click |
点通知打开哪个 URL |
attach |
附件 URL(配合服务端附件存储) |
actions |
通知上的按钮:view(开链接)、broadcast(发 intent)、http(直接打一个 API,可带方法/头/body) |
icon |
自定义图标 |
delay |
定时投递(如 30m、tomorrow 9am) |
email |
同时抄送一封邮件 |
cache=no |
这条不落缓存 |
firebase=no |
这条不走 FCM |
actions 里的 http 是最被低估的:通知上放一个"重启服务"按钮,点一下真的去调你的 API —— 手机端就变成了一个极简运维面板。
订阅的四种姿势
同一个 topic,四种流随便挑:
- JSON stream(
/topic/json)— 每行一条 JSON,最好解析 - SSE(
/topic/sse)— 浏览器EventSource直接用 - WebSocket(
/topic/ws)— 双向,双向的都靠它 - raw(
/topic/raw)— 只要正文,tail -f式的用法
加上 ?since=10m / since=all / since=<message-id>,新订阅者能把错过的消息补回来(前提是服务端开了 message cache)。这一点在"我下班了,第二天早上补看 CI 通知"的场景里是刚需,也是 gotify 目前缺的那块。
自建:一个配置文件的事
ntfy serve # 默认监听 :80,零配置跑起来
认真部署就写 /etc/ntfy/server.yml:
base-url: "https://ntfy.example.com"
listen-http: "127.0.0.1:2586"
behind-proxy: true
cache-file: "/var/cache/ntfy/cache.db"
attachment-cache-dir: "/var/cache/ntfy/attachments"
auth-file: "/var/lib/ntfy/auth.db"
auth-default-access: "deny-all" # 默认拒绝,再按需开白
三条核心配置就够跑:base-url(对外地址)、listen-http(监听)、behind-proxy(在反代后面)。
services:
ntfy:
image: binwiederhier/ntfy
restart: unless-stopped
command: serve
environment:
NTFY_BASE_URL: https://ntfy.example.com
NTFY_BEHIND_PROXY: "true"
NTFY_CACHE_FILE: /var/lib/ntfy/cache.db
NTFY_AUTH_FILE: /var/lib/ntfy/auth.db
NTFY_AUTH_DEFAULT_ACCESS: deny-all
NTFY_ATTACHMENT_CACHE_DIR: /var/lib/ntfy/attachments
volumes:
- ./data:/var/lib/ntfy
ports:
- "2586:80"
配置支持三种写法:server.yml、命令行参数、NTFY_* 环境变量(Docker 场景用后者)。注意:密码里有 $ 要写成 $$,这是 compose 插值的经典坑。
除了 Docker,还有 deb/rpm 包、静态二进制、Helm chart、ntfy-user/systemd 单元。
权限:用户角色 + topic 级 ACL + token
跟"公共实例随便用"的印象相反,自建版权限相当细:
- 用户角色:
admin/user,用户可以在 CLI 或配置里建 - ACL:按 topic 授权,可以给到 read-only 或 write-only
- 典型用法:监控脚本只拿
write-only,手机端只拿read-only
- 典型用法:监控脚本只拿
- access token:给每个脚本/设备单独发,可随时吊销,支持过期时间
- 默认策略
auth-default-access:deny-all/read-only/write-only/read-write
再叠上限流(按 IP、按订阅者)、消息大小上限、附件大小上限、topic 创建限制。这些不是"高级功能",是自建公网实例的必备项——不然你就是在开一个免费的公开推送中继,几天后就会被别人的爬虫和垃圾消息灌满。
附件与缓存:存在哪
- 附件默认落本地
attachment-cache-dir,也可以配 S3(含 MinIO 等 S3 兼容存储),适合无状态容器 - 消息缓存用 SQLite(默认)或 PostgreSQL,集群模式(实验性)下建议 PG
- 附件和缓存都有 TTL 与容量上限,默认不会无限膨胀
iOS 的一个硬限制(自建前必须知道)
这是自建 ntfy 最容易被忽略的一条,官方文档写得很直白:
和 Android 不同,iOS 严格限制后台处理,没有中央服务器就无法实现即时推送。
也就是说,自建服务器想让 iPhone 立刻收到通知,官方做法是配:
upstream-base-url: "https://ntfy.sh"
upstream-access-token: "..." # 可选,被限流或上游受保护时才要
自建服务器把 poll_request 转发给 ntfy.sh,再由 ntfy.sh 交给 APNs。代价是消息元数据会经过官方服务。
不想过第三方,只有两条路:自己编 iOS App 自己签 APNs,或者接受"App 在前台才刷新"。
Android 没这个问题:F-Droid 版本完全不用 Firebase,靠 WebSocket 长连接 + 前台服务做到即时;Play Store 版才走 FCM。这也意味着 F-Droid 版可以在无 GMS 的机器上用。
生态:这个品类里最厚的一家
- UnifiedPush 分发器:ntfy 是官方推荐的 UP 分发器之一,别的 App 可以复用它的连接推消息
- 集成一堆:Grafana 告警、Home Assistant、Uptime Kuma、Watchtower、Node-RED 这类工具的"通知渠道"里基本都有 ntfy 一项
- 官方 CLI(
ntfy publish/ntfy subscribe)、Web App、桌面端(Windows/macOS/Linux 的 native 壳) - 有商业模式:ntfy.sh 上卖 Pro($5/月起),主要买"托管 + 更高额度"。自建版功能不受 tier 限制
和 gotify 怎么选(重点)
| ntfy | gotify | |
|---|---|---|
| 发送 | HTTP PUT/POST,topic 即地址 | REST(token 放 query 或 header) |
| 通道模型 | topic 发布/订阅 | application(发)↔ client(收)分离 |
| 客户端 | Android + iOS + Web + CLI + 桌面 | Android + Web + CLI |
| 消息回放 | ✅ since= 补历史 |
有列表,无 since 语义 |
| 附件 | ✅ 本地 / S3 | ❌(只能给图片 URL) |
| 定时 / 按钮 | ✅ delay / actions |
❌ |
| 权限 | 角色 + topic 级 ACL(读写分离) | 用户 / 应用 / 客户端 |
| 即时推送机制 | Android 走长连接(F-Droid 无 FCM);iOS 需上游中转 | Android 走长连接(无 FCM) |
| MCP | ❌ 得自己包一层 | ✅ 3.x 内置 /mcp |
| 自带 UI | 有(Web App) | 有(更偏"应用管理后台") |
一句话:ntfy 是"推送界的 curl"(一个 URL 一条通道,协议化、能回放),gotify 是"自己的推送 App 后端"(应用/客户端/用户管理更正统,还带 MCP)。
边界和冷水
- 公共实例的 topic 是公开的。
ntfy.sh/xxx只要别人猜到这段字符串就能读。敏感内容一律自建 +deny-all,或者至少配访问控制。名字也别用test、alert这种。 - 自建 = 你会变成一个公网推送中继。不配鉴权 + 限流,几天内就会被滥用(发垃圾、当 C2 通道)。
auth-default-access: deny-all是默认动作,不是可选项。 - iOS 那条上游转发是架构性妥协,不是 bug。合规敏感的团队要么接受、要么自己签 App。
- 自建版的"运维成本"被低估。TLS、反代、缓存目录、附件目录、备份 auth.db、限流、fail2ban —— docker 一行跑起来只是第一天。
- 迭代还在继续(v2.32.0,2026-10),配置项会加,升级前看 release notes。
- 它不是消息队列。没投递确认、没重试语义、没消费组;当"尽力而为的通知"用,别当 Kafka。
适合谁
- ✅ CI/监控/备份脚本要推手机:一条 curl 搞定,比接一堆 SDK 干净
- ✅ 自建 + 多设备 + 要 iOS:这是目前开源方案里少数 iOS 官方客户端齐全的
- ✅ 需要"补看历史通知"(
since=)和附件(截图、报告) - ✅ 想要通知上带按钮、点一下真去调 API 的轻运维场景
- ❌ 只想给 agent 发"任务完成"、且特别在意"token 只能发不能读":gotify 的内置 MCP 更省事
- ❌ 需要投递保证/重试/队列语义的:上正经消息队列
一句话总结
ntfy 用"URL 即通道"把推送这件小事做到了极简:PUT 一条就发,四种流随便订,since= 能补历史,附件/定时/按钮一应俱全,Android + iOS 官方客户端都有,自建还带 topic 级读写分离的 ACL。它是这个品类里生态最厚、也最像"协议"而不是"一个 App"的那一个——代价是 iOS 自建要过一次上游中转,以及自建之后你得自己当一个合格的中继运营者。
更看重"应用/客户端/用户"这套正经模型,或者想要内置 MCP 端点给 agent 发通知的,看同批发布的 gotify/server。