个人项目[VentoSiteProxy]:基于 Pingora 的反代网关,替代 Nginx Proxy Manager

个人项目[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_session Cookie,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(WebAuthn rp_id 推导 eTLD+1)
  • Web UI 是单文件 index.html,编译期 include_str! 内嵌进二进制——没有前端构建流程,没有静态文件服务
  • release profile:opt-level=3, lto=true, codegen-units=1, strip=symbols

📝 安装与使用

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 构建(x86_64 Linux;部署到 glibc 偏老的机器可用 musl 静态编译)
cargo build --release

# 2. 初始化管理员(生成随机密码)
./target/release/ventositeproxy init -f admin.yaml

# 3. (可选)从 NPM 一键导入现有站点
./target/release/ventositeproxy migrate \
-d /opt/nginx-proxy-manager/data/database.sqlite -o sites.yaml

# 4. 启动
./target/release/ventositeproxy run -c ventsitemproxy.yaml

systemd 单元文件在仓库 deploy/ventositeproxy.service,systemctl enable --now ventositeproxy 即可。默认代理端口 8880、管理端 8890(我的环境里外发经 frp 切端口指向 8880)。

部署目录一目了然:

1
2
3
4
5
6
7
8
9
/opt/ventositeproxy/
ventsitemproxy.yaml 主配置
sites.yaml 站点路由表(管理端自动同步)
admin.yaml 管理员认证
auth.yaml / auth.db 零信任认证配置与数据(SQLite,600 权限)
upgrade.sock / *.pid
logs/access/site-*.log 访问日志(按站点、按天归档)
logs/audit.jsonl 操作日志(管理端操作)
logs/auth.jsonl 认证门禁判定日志

🔧 配置详解

核心配置就是 sites.yaml,一个站点一条记录:

1
2
3
4
5
6
7
8
9
10
11
12
13
sites:
- name: demo-web
domains: ["www.example.com", "example.com"]
upstream: "http://203.0.113.10:8080"
read_timeout_secs: 3600
url_rules:
- pattern: "/api/v1/db*"
action: deny # 黑名单规则,命中即 403
auth:
enabled: false # 该站点的零信任开关
exempt_paths: ["/healthz", "/manifest.webmanifest"]
allowed_users: []
allowed_groups: [] # 两个都为空 = 谁都不允许

零信任的全局配置在 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

https://blog.erguotou.me/my-project-ventositeproxy.html

作者

二锅头

发布于

2026-11-11

许可协议

CC BY-NC-SA 4.0

评论

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×