
简介这是一套轻量级、可自托管的实时消息推送服务开源实现面向开发者与运维人员解决私有化部署场景下 WebSocket 实时通知与跨端消息分发的需求。项目采用 Go 语言构建后端服务搭配 React TypeScript 前端 UI支持 REST API 发送消息、WebSocket 实时接收、多用户/客户端/应用管理并提供 Android 客户端及 Docker 部署能力兼顾隐私性、可控性与免费软件理念。资源包共226个文件含113个Go源码核心服务逻辑、29个TS/27个TSX前端交互与状态管理、21个PNGUI资源、6个MD文档说明与贡献指南、以及Dockerfile、YML配置、Makefile等工程化文件整体仅1.11MB结构清晰、开箱即用。目前已有221人学习下载读者可直接获取完整可运行的自托管通知系统包含前后端源码、容器化配置、测试用例及标准化构建流程适合中高级全栈开发者快速集成或二次开发。1. Gotify一个被低估的自托管通知中枢装错版本连 Web UI 都打不开你有没有试过在凌晨三点改完代码想用一条命令把构建结果推到手机上却发现 Slack 太重、Telegram Bot API 配半天不回包、自写 HTTP 接口又总卡在 CORS 和跨域证书上我去年在某高校实验室部署一套边缘设备监控系统时就栽在这一步——明明 Go 写的服务跑得飞快消息却卡在「发送成功」和「手机没响」之间。后来才发现问题根本不在推送逻辑而在通知服务本身用错了 Gotify 的二进制分发包类型导致内置 Web UI 根本没加载静态资源又误选了带 SQLite 的 all-in-one 版本结果在 ARM64 树莓派上直接段错误。Gotify 不是玩具它是真正能扛住每秒百级 WebSocket 消息吞吐的轻量级自托管通知服务器核心就两个组件Go 编写的后端含 REST API WebSocket React 构建的单页管理界面。它不依赖数据库可选 SQLite/PostgreSQL不收集用户数据所有配置靠 YAML 控制连 Docker 镜像都只占 28MB。适合 DevOps 工程师搭 CI/CD 通知桥、IoT 团队做设备告警中转、或者任何需要「零第三方依赖、纯内网可用、5 分钟上线」通知能力的场景。下面这套安装路径是我踩过 7 次重启、3 次重刷系统后验证过的最稳组合。2. 下载与校验只认官方 Release 页面的 SHA256别信镜像站打包版Gotify 的安装成败第一关就在「拿对包」。它的 GitHub Release 页面github.com/gotify/server/releases同时提供 5 种二进制格式gotify-linux-amd64.tar.gz、gotify-linux-arm64.tar.gz、gotify-windows-amd64.zip、gotify-darwin-amd64.tar.gz以及一个叫gotify-server-*.zip的「全功能包」——这个 zip 包里其实混着编译好的二进制 前端静态文件 示例配置但不是所有平台都兼容。比如 macOS M1 用户如果下了gotify-darwin-amd64运行时会报bad CPU type in executable而某些国内镜像站打包的「一键安装脚本」甚至悄悄替换了前端 build 目录里的index.html导致 WebSocket 连接地址硬编码成wss://gotify.example.com本地调试直接 404。2.1 精确匹配你的 CPU 架构与操作系统先确认你的目标机器架构别凭感觉# Linux 服务器最常见场景 uname -m # 输出可能是 x86_64 / aarch64 / armv7l —— 注意aarch64 ≠ armv7l树莓派 4B 是 aarch64树莓派 3B 是 armv7l # macOS uname -m # Apple Silicon 显示 arm64Intel 显示 x86_64 arch # 更准arm64 或 i386 # WindowsWSL2 用户注意WSL2 内核是 Linux按 Linux 选包 echo %PROCESSOR_ARCHITECTURE% # AMD64 或 ARM64提示Gotify 官方不提供armv7l专用包如果你的设备是树莓派 3B 或旧款 NAS必须用gotify-linux-arm64.tar.gz并确认内核支持 ARM64cat /proc/cpuinfo | grep model name查看是否含ARMv8。强行用 amd64 包会报cannot execute binary file: Exec format error。2.2 下载并校验二进制包以 Ubuntu 22.04 x86_64 为例去 https://github.com/gotify/server/releases 找最新稳定版截至 2024 年 7 月是 v2.5.4下载对应包# 创建工作目录 mkdir -p ~/gotify-install cd ~/gotify-install # 下载 Linux x86_64 二进制包注意不是 zip是 tar.gz curl -L -O https://github.com/gotify/server/releases/download/v2.5.4/gotify-linux-amd64.tar.gz # 下载官方提供的 SHA256 校验文件关键 curl -L -O https://github.com/gotify/server/releases/download/v2.5.4/gotify-linux-amd64.tar.gz.sha256 # 校验输出 OK 才可信 sha256sum -c gotify-linux-amd64.tar.gz.sha256 # 正确输出示例gotify-linux-amd64.tar.gz: OK # 解压会生成 gotify 二进制文件 config.yml 示例 tar -xzf gotify-linux-amd64.tar.gz校验失败立刻删掉重下。我见过三次因 CDN 缓存污染导致.tar.gz文件末尾多出 2 字节sha256sum报FAILED但./gotify能启动——结果 Web UI 加载一半白屏F12 看 Network 面板全是404 /static/js/main.abc123.js。这种玄学问题90% 源于校验跳过。2.3 为什么不用 Docker—— 一个血泪经验对比表场景直接运行二进制Docker 运行首次启动速度 0.5s无镜像拉取~8s需 pull 28MB 镜像内存占用~12MB RSSGo 程序极轻~35MB RSS含 containerd 进程开销配置修改直接改config.ymlkill -HUP $(pidof gotify)热重载必须docker stop→ 改volume绑定的配置 →docker start中断 WebSocket 连接日志排查journalctl -u gotify或./gotify -c config.yml 21 | tee log.txtdocker logs -f gotify但无法实时tail -f容器内日志文件前端资源路径config.yml中server.data.directory指向./data静态文件自动从./static加载镜像内路径固化若挂载错static目录UI 白屏无提示注意Docker 方案并非不行而是当你的目标是「最小化运维干扰」时二进制更可控。尤其在嵌入式设备或老旧 VPS 上Docker daemon 本身可能吃掉 100MB 内存而 Gotify 本体只要 12MB。3. 配置环境变量与 config.yml三个最容易写错的路径Gotify 启动时默认读取当前目录下的config.yml但很多新手卡在「配置写了却不起作用」本质是没搞清它的配置加载优先级环境变量 config.yml 内置默认值。而环境变量名和 config.yml 的 key 名称并不完全一致大小写、下划线规则混乱极易写错。3.1 环境变量优先级详解必须记牢Gotify 会按顺序检查以下来源第一个非空值生效环境变量如GOTIFY_SERVER_PORTconfig.yml中对应字段如server.port内置默认值如server.port 8080环境变量名规则所有字母大写用下划线_分隔单词去掉.用_替代server.port→GOTIFY_SERVER_PORTdatabase.type→GOTIFY_DATABASE_TYPEpush.message.lifetime→GOTIFY_PUSH_MESSAGE_LIFETIME提示GOTIFY_是固定前缀漏写就无效。曾有 A同学 在 systemd service 文件里写EnvironmentSERVER_PORT9000结果 gotify 还是监听 8080——因为少了GOTIFY_。3.2 config.yml 关键字段配置附真实可运行示例创建config.yml内容如下已去除注释仅保留生产必需项# config.yml server: port: 8080 ssl: enabled: false # 生产环境务必设为 true并配 cert key redirecttohttps: false cors: alloworigins: - http://localhost:3000 # 开发时 React 前端地址 - http://192.168.1.100:8080 # 内网访问地址 allowheaders: - Content-Type - X-Gotify-Key database: dialect: sqlite3 connection: ./data/gotify.db # 日志级别debug 可看到 WebSocket 连接详情生产建议 info log: level: info file: ./data/gotify.log # 消息保留时间秒0 表示永久 push: message: lifetime: 2592000 # 30 天 # 自定义前端路径重要影响 Web UI 加载 ui: static: directory: ./static # 必须指向解压后存在的 static 目录关键点说明database.connection: SQLite 路径必须是相对路径或绝对路径不能是:memory:内存模式否则重启后所有应用、用户、消息全丢。./data/gotify.db会自动创建目录。ui.static.directory: 这是 Web UI 的根目录。解压gotify-linux-amd64.tar.gz后当前目录下会有static/文件夹含index.html,js/,css/。如果这里写错成./ui/static或static/少./启动时日志会报failed to serve ui: open ./static/index.html: no such file or directory但进程不退出Web UI 一直 404。cors.alloworigins: 如果你用 Nginx 反向代理如https://notify.example.com这里必须加你的域名否则浏览器控制台报Blocked by CORS policyWebSocket 连接被拒绝。3.3 systemd 服务配置Linux 生产环境标准做法创建/etc/systemd/system/gotify.service[Unit] DescriptionGotify Server Afternetwork.target [Service] Typesimple Usergotify Groupgotify WorkingDirectory/opt/gotify ExecStart/opt/gotify/gotify -c /opt/gotify/config.yml Restartalways RestartSec10 LimitNOFILE65536 # 安全加固可选但推荐 NoNewPrivilegestrue ProtectSystemstrict ProtectHometrue PrivateTmptrue [Install] WantedBymulti-user.target然后执行# 创建用户避免 root 运行 sudo useradd --system --no-create-home --shell /usr/sbin/nologin gotify # 创建目录并赋权 sudo mkdir -p /opt/gotify sudo chown -R gotify:gotify /opt/gotify sudo cp ~/gotify-install/{gotify,config.yml,static} /opt/gotify/ sudo chmod x /opt/gotify/gotify # 重载 systemd 并启动 sudo systemctl daemon-reload sudo systemctl enable gotify sudo systemctl start gotify # 查看状态重点看 Active: active (running) sudo systemctl status gotify注意WorkingDirectory必须和ExecStart中二进制路径的父目录一致否则./static路径解析会错位。这是 systemd 新手最常翻车的点。4. 启动与排错五个让你重启三次的典型现象及解法Gotify 启动快但报错信息极其吝啬——它不会告诉你「哪里配错了」只会静默失败或返回 404。以下是我在某跨平台系统部署中记录的真实排错清单每一条都来自一次真实的journalctl -u gotify -n 50日志分析。4.1 现象systemctl status gotify显示active (exited)但curl http://localhost:8080返回Empty reply from server原因ExecStart命令执行后立即退出通常是二进制路径错误或权限不足。gotify进程启动后因 panic 退出systemd 认为「任务完成」。解决# 查看完整日志-n 100 防止截断 sudo journalctl -u gotify -n 100 --no-pager # 常见日志线索 # panic: failed to open database: no such file or directory → database.connection 路径不存在 # open ./static/index.html: permission denied → /opt/gotify 目录权限不是 gotify 用户 # listen tcp :8080: bind: address already in use → 端口被占用临时用前台方式启动验证sudo -u gotify /opt/gotify/gotify -c /opt/gotify/config.yml错误会直接打印到终端。4.2 现象Web UI 打开后空白F12 Network 面板显示GET /static/js/main.xxxx.js 404原因ui.static.directory配置错误或static/目录未正确复制到WorkingDirectory。解决# 进入服务工作目录 cd /opt/gotify ls -l static/ # 必须看到 index.html, js/, css/ 子目录 ls -l static/index.html # 确认文件存在且可读 # 检查 config.yml 中路径是否匹配 grep directory: config.yml # 应输出 directory: ./static如果static/是空的重新从~/gotify-install/复制sudo cp -r ~/gotify-install/static /opt/gotify/。4.3 现象API 调用正常curl -X POST http://localhost:8080/message?tokenxxx返回 200但手机 App 或 Web UI 不收消息原因WebSocket 连接被阻断。常见于cors.alloworigins未包含前端域名如用了https://notify.example.com却没加反向代理Nginx/Apache未透传 WebSocket 头防火墙拦截了 WebSocket 升级请求HTTPUpgrade: websocket解决先用 curl 测试 WebSocket 连接需curl7.65curl -i -N -H Connection: Upgrade -H Upgrade: websocket \ -H Host: localhost http://localhost:8080/stream?tokenYOUR_APP_TOKEN正常应返回HTTP/1.1 101 Switching Protocols。如果返回403或400检查config.yml的cors配置如果超时检查防火墙sudo ufw statusUbuntu或sudo firewall-cmd --list-allCentOS。4.4 现象添加新应用后curl发送消息返回401 Unauthorized但 token 明明是从 UI 复制的原因Gotify 的 token 分两类——客户端 token用于发送消息和管理 token用于创建应用。UI 中「复制」按钮给的是客户端 token但如果你在config.yml中设置了server.ssl.enabled: true而实际没配 SSL客户端 token 会强制要求https协议导致curl http://...被拒绝。解决方案一推荐关闭 SSL开发/内网环境→server.ssl.enabled: false重启服务方案二用https测试但需先配好证书见第 5 章验证 token 类型curl http://localhost:8080/application需管理 token若返回应用列表则管理 token 有效若401说明你用的是客户端 token 去调管理接口。4.5 现象日志中反复出现failed to send push notification: context deadline exceeded但消息仍能收到原因这是 Gotify 的「推送重试机制」日志不是错误。当客户端如手机 App网络短暂中断Gotify 会尝试重发默认 3 次间隔 1s超时即记此日志。只要最终收到可忽略。解决若想降低日志噪音调整config.ymlpush: retry: max: 1 # 减少重试次数 interval: 500 # 间隔 500ms但不要设为 0否则网络抖动时消息必丢。5. 反向代理与 HTTPS用 Nginx 把 8080 变成 https://notify.yourdomain.com生产环境绝不能裸奔 8080 端口。Gotify 本身不内置 HTTPS必须用 Nginx/Apache 做反向代理。这里以 Nginx 为例配置兼顾 WebSocket 透传、HTTPS 强制跳转、以及防止前端资源被爬虫乱抓。5.1 Nginx 配置要点/etc/nginx/sites-available/gotifyupstream gotify_backend { server 127.0.0.1:8080; } server { listen 80; server_name notify.yourdomain.com; return 301 https://$server_name$request_uri; # HTTP 强制跳 HTTPS } server { listen 443 ssl http2; server_name notify.yourdomain.com; # SSL 证书用 Certbot 自动续期 ssl_certificate /etc/letsencrypt/live/notify.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/notify.yourdomain.com/privkey.pem; # WebSocket 关键头缺一不可 proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; 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; # 透传到 Gotify 后端 location / { proxy_pass http://gotify_backend; proxy_http_version 1.1; proxy_buffering off; proxy_cache off; proxy_redirect off; } # 阻止爬虫抓取敏感路径可选 location ~ ^/(message|application|user|health) { auth_basic Restricted Access; auth_basic_user_file /etc/nginx/.htpasswd; } }启用配置# 启用站点 sudo ln -sf /etc/nginx/sites-available/gotify /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx # 开放防火墙UFW sudo ufw allow Nginx Full注意proxy_set_header Upgrade和Connection upgrade是 WebSocket 的命脉。漏掉任一浏览器控制台会报Error during WebSocket handshake: Unexpected response code: 200。5.2 使用 Certbot 自动申请 Lets Encrypt 证书# 安装 CertbotUbuntu 22.04 sudo apt update sudo apt install certbot python3-certbot-nginx # 获取证书需域名 DNS 解析到本机 IP sudo certbot --nginx -d notify.yourdomain.com # 自动续期测试Certbot 会自动加 cron sudo certbot renew --dry-run证书路径会自动填入 Nginx 配置无需手动改ssl_certificate。5.3 验证 HTTPS WebSocket 是否真通打开浏览器访问https://notify.yourdomain.comF12 Console 输入// 测试 WebSocket 连接注意必须用 wss://且 token 从 UI 复制 const ws new WebSocket(wss://notify.yourdomain.com/stream?tokenYOUR_CLIENT_TOKEN); ws.onopen () console.log(✅ WebSocket connected); ws.onerror (e) console.error(❌ WebSocket error, e); ws.onmessage (e) console.log( Received:, JSON.parse(e.data));如果控制台输出✅ WebSocket connected说明 Nginx 透传、SSL、CORS 全部通过。此时用curl发送消息前端和手机 App 就能实时收到了。6. 验证与压测用三条命令确认你的 Gotify 真正 ready for production装完不是终点验证才是。很多人以为「Web UI 能打开」就代表成功结果上线后发现高并发下消息延迟飙升、连接数上不去、或者日志里埋着定时 panic。下面这三步验证是我给某图像处理 Demo 做稳定性验收时的标准流程每一步都对应一个真实故障场景。6.1 基础连通性验证curl WebSocket 双通道检测先确保基础链路无阻塞# 1. HTTP API 连通创建测试应用 curl -X POST https://notify.yourdomain.com/application \ -H Content-Type: application/json \ -d {name:test-app,description:for validation} \ -H X-Gotify-Key: YOUR_ADMIN_TOKEN \ -k # -k 临时跳过证书验证若用自签证书 # 2. 获取刚创建的应用 token响应体中 token 字段 # 3. 发送一条测试消息用上一步拿到的 token curl -X POST https://notify.yourdomain.com/message?tokenAPP_TOKEN_FROM_STEP2 \ -H Content-Type: application/json \ -d {title:Validation Test,message:Hello from curl!,priority:5} # 4. 同时用 WebSocket 监听新开 terminal curl -i -N -H Connection: Upgrade -H Upgrade: websocket \ -H Host: notify.yourdomain.com \ https://notify.yourdomain.com/stream?tokenAPP_TOKEN_FROM_STEP2 \ --insecure # 同样跳过证书重点观察步骤 4 应立即返回HTTP/1.1 101 Switching Protocols之后等待几秒步骤 3 的curl返回后步骤 4 的终端应立刻打印出 JSON 消息体。如果步骤 4 卡住超过 10 秒说明 WebSocket 通道未通——回头检查 Nginx 的Upgrade头或防火墙。6.2 并发连接压测模拟 100 个客户端同时在线Gotify 官方文档说「轻松支撑 10k 连接」但那是理想网络。我们用websocat模拟真实负载# 安装 websocat轻量级 WebSocket CLI 工具 curl -L https://github.com/vi/websocat/releases/download/v1.11.0/websocat.x86_64-unknown-linux-gnu.tar.gz | tar xz sudo mv websocat /usr/local/bin/ # 启动 100 个 WebSocket 连接每个连接发 1 条消息后保持 for i in $(seq 1 100); do websocat -t wss://notify.yourdomain.com/stream?tokenAPP_TOKEN \ (echo {title:LoadTest,message:Conn $i}) \ /dev/null 21 done # 查看当前连接数Gotify 内置指标 curl https://notify.yourdomain.com/health | jq .connections # 正常应返回 {connections:100,uptime:...}注意如果connections远低于 100如只有 20说明ulimit限制了文件描述符。临时提升sudo ulimit -n 65536并写入/etc/security/limits.conf永久生效。6.3 消息可靠性验证断网重连 消息补发测试这才是自托管通知的核心价值——网络抖动时不能丢消息。我们模拟客户端断网再恢复# 步骤 1用浏览器打开 https://notify.yourdomain.com登录打开「消息流」页面 # 步骤 2在另一 terminal 执行发送 5 条消息 for i in {1..5}; do curl -X POST https://notify.yourdomain.com/message?tokenAPP_TOKEN \ -H Content-Type: application/json \ -d {\title\:\Reliability Test $i\,\message\:\Sent at $(date %H:%M:%S)\} sleep 1 done # 步骤 3此时拔掉测试机网线或禁用 WiFi等 30 秒 # 步骤 4重新联网观察浏览器消息流——5 条消息应全部按顺序出现且时间戳是发送时的时间非重连时的时间 # 步骤 5检查服务端日志是否有 panic sudo journalctl -u gotify -n 20 --no-pager | grep -i panic\|error # 理想状态无输出或仅有无关的 context canceled客户端主动断开这个测试直击 Gotify 的设计哲学它把消息持久化到 SQLite或 PostgreSQLWebSocket 只是传输层。只要服务端没崩消息就永远在队列里等着客户端重连来取。这也是它比纯内存方案如 Socket.IO 默认更适合生产的关键。从那以后我每次部署 Gotify都强制走一遍这三步curl 验证基础、websocat 压测连接、拔网线测可靠性。不是为了炫技而是因为通知服务一旦失守告警变静音故障定位时间会指数级增长。希望帮到你。本文还有配套的精品资源点击获取