个人项目[VentoSiteProxy]:基于 Pingora 的反代网关,替代 Nginx Proxy Manager
🚀 VentoSiteProxy - 一个单二进制反代网关,替代 Nginx Proxy Manager
用 Nginx Proxy Manager(NPM)做统一入口这些年,我反复被同一批问题卡住:SSE 被 proxy_buffering 卡住、上传超过 1M 就断、长连接 60 秒被掐,每解决一个就要多写一层 override,而我想做的"按站点登录认证"它压根没这个能力。与其继续跟 nginx 默认配置打架,索性自己写了一个——VentoSiteProxy,基于 Pingora(Cloudflare 开源的代理框架)的 Rust 单二进制网关,把 账号登录、站点配置、启停管理、操作日志、热重载 做成开箱即用,后来还长出了一套零信任认证(LDAP + TOTP + passkey + 站点级 ACL)。
以下截图来自本地演示环境,站点、域名与日志均为虚构演示数据(IP 使用 RFC 5737 文档保留段),敏感信息已打码。
🖼️ 预览

💡 项目简介
VentoSiteProxy 面向个人与小团队的定位很明确:一个二进制的 Nginx Proxy Manager 替代品。管理端是 Web 界面,网关本身不依赖 nginx、不需要 Docker、没有 web 面板进程,拷一个静态二进制到目标机就能跑(musl 静态编译,不挑目标机 glibc 版本)。
为什么不直接改 nginx?因为 NPM 的痛点全部来自 nginx 默认配置与直觉预期的差异,而 Pingora 里这些行为是原生的,不需要任何 override:
| NPM 问题 | VentoSiteProxy |
|---|---|
proxy_buffering 卡 SSE |
默认流式转发,无缓冲 |
client_max_body_size 1m 限制上传 |
无 body 大小限制 |
| 长连接 60s 断开 | 超时按站点可配,0 = 不限 |
| advanced config 复杂难维护 | 配置模型即能力,管理端可视化 |
✨ 核心特性
站点反向代理
- 名称 / 备注 / 多域名(含
*.wildcard泛域名)/ 上游 http(s) - SNI、证书校验开关、连接·读·写超时均可按站点配置(单位秒,
0= 不限) - location 覆盖:按序匹配 path,命中后指向不同上游——比如
/mqtt*单独走一个 broker 并配 7 天长超时 - URL 黑白名单:按序匹配、首个命中生效。黑名单模式(默认
url_default: allow)把/api/v1/db*的 deny 写在前面,/api/v1/db、/api/v1/db/health一律 403,其余放行;白名单模式(url_default: deny)则只放行显式列出的路径

访问日志
- 按站点开关:日志按站点、按天归档为
logs/access/site-{id}-{slug}-YYYYMMDD.log,跨天自动换新文件 - 自动清理:默认开启、保留 180 天,后台任务每天按归档日期删除超期文件;「设置」页可开关或改保留天数,改完即时生效

默认 404
请求 Host 未命中任何启用站点时,按 default_404 响应,管理端「设置」页可改、即时生效:
status(默认):只返回 404 状态码 + 最小文本page:内置通用 404 页面(不含 Host / 服务名 / 版本,不暴露信息)custom:用户目录custom_404_dir里的404.html(支持 css / 图片引用,防目录穿越,状态码恒 404)
TCP 数据流
非 HTTP 场景(MQTT 之类)走 TCP 透传中继,启用/停用约 1 秒生效,和 HTTP 站点统一在同一个管理端里管理。
管理端、热重载与零停机发布
- 管理端独立端口,argon2id 密码 + JWT 登录;登录和全部增删改启停都写 JSONL 操作日志
- 热重载:管理端保存 = 内存
ArcSwap即时生效 + 原子写回sites.yaml;手工改文件则 2 秒内自动生效,解析失败时保留旧配置,服务不会挂 - 零停机发布:
--upgrade参数让新进程经 upgrade socket 接管旧进程的文件描述符,升级不中断流量

零信任认证(可选,按站点启用)
这是后面迭代的重点,默认关闭,在站点编辑弹窗的「认证」区按站点打开:
- 身份源:本地用户(SQLite 存储)或 LDAP(对接公司目录,服务账号 simple-bind + 用户绑定 + 组反向搜索,结果 TTL 缓存,故障可配
stale或deny) - 双因子:TOTP 动态码或 passkey(WebAuthn) 二选一通过即签发会话
- 会话制 SSO:一次登录,所有启用认证的站点免登(
vsp_sessionCookie,HttpOnly) - 站点级 ACL:
allowed_users/allowed_groups白名单,须命中其一;两者皆空 = 谁都不允许 - 豁免路径
exempt_paths:登录页静态资源、健康检查、PWA manifest、favicon 这类浏览器自动请求的公开资源,配成 glob 后完全不经过认证直接透传 - 持续评估:用户被禁用、因子被删除、策略变更,对应会话即时失效
- 空闲免密重验证:会话空闲超过阈值(
reauth_idle_secs,默认 1800 秒,0= 关闭)后,下次请求只需重过二次因子,不用重输密码 - 上游 Host/Origin 改写:部分上游按 Host 鉴权,或隧道场景内外地址不一致时,配置
upstream_host转发时改写Host(有Origin时同步改写),避免上游 401/403

🛠️ 技术栈
- Rust + Pingora 0.9(
proxy+rustls) tokio多线程运行时;arc-swap做配置的原子热切换rusqlite(bundled 编译,仅需 C 编译器)存本地用户与 MFA 因子ldap3(纯 Rust、rustls)、totp-rs、webauthn-rs(纯 Rust,musl 静态友好)、psl(WebAuthnrp_id推导 eTLD+1)- Web UI 是单文件
index.html,编译期include_str!内嵌进二进制——没有前端构建流程,没有静态文件服务 - release profile:
opt-level=3, lto=true, codegen-units=1, strip=symbols
📝 安装与使用
1 | # 1. 构建(x86_64 Linux;部署到 glibc 偏老的机器可用 musl 静态编译) |
systemd 单元文件在仓库 deploy/ventositeproxy.service,systemctl enable --now ventositeproxy 即可。默认代理端口 8880、管理端 8890(我的环境里外发经 frp 切端口指向 8880)。
部署目录一目了然:
1 | /opt/ventositeproxy/ |
🔧 配置详解
核心配置就是 sites.yaml,一个站点一条记录:
1 | sites: |
零信任的全局配置在 auth.yaml:本地用户开关、LDAP 服务账号、会话策略(空闲重验证时长)、限流参数等。
⚠️ 注意:
init生成的随机管理员密码存在admin.yaml里,不要提交到 git,需要时再用set-password修改。
📚 常见问题
Q:为什么选 Pingora 而不是继续魔改 nginx?
A:上面表格里的每一项,在 nginx 侧都是"补丁",而且站点越多补丁越难维护;nginx 的配置模型也不适合做按站点鉴权,再往上叠一个 auth 服务就变成两层维护。Pingora 把这些行为做成了原生,配置模型和管理端共享同一份数据结构。
Q:手机上注册了 passkey,为什么电脑上用不了?
A:passkey 是设备本地凭据,私钥存在注册设备的安全芯片里,A 设备注册的在 B 设备天然不可用。想给电脑加 passkey,得先用别的方式(手机 passkey 或 TOTP)登录,再从安全设置里添加。登录页故意不放注册入口,就是防止拿到低权账号的人在未验证状态给自己注册因子。
Q:手工改 sites.yaml 改错了怎么办?
A:解析失败会保留旧配置继续服务,不会把网关打挂;管理端的保存路径和手工文件是同一份,改完 2 秒内自动生效。
🔗 项目地址
GitHub: https://github.com/Ventorium/VentoSiteProxy
🌟 结语
VentoSiteProxy 解决的就是"想要 NPM 的易用性,但受不住 nginx 默认行为限制,还想要按站点认证"这个问题:流式转发、无 body 限制、按站点超时是原生行为,配置模型即能力,零信任认证(LDAP + TOTP + passkey + 站点 ACL + 空闲重验证)也已经落地并在跑。项目目前还在 0.1.0 阶段,属于"自用先跑起来、边用边磨"的状态,后续迭代看需求。如果你也在 NPM 的坑里挣扎,或者想给自己搭一个"登录才能进"的网关,欢迎看看。
个人项目[VentoSiteProxy]:基于 Pingora 的反代网关,替代 Nginx Proxy Manager