Files
Havline/docs/scenario-a-multi-app-https-entry.md
T
mrlee 2a0740a60f
CI / verify (push) Canceled after 0s
Docker Release / build-and-push (amd64, linux/amd64) (push) Canceled after 0s
Docker Release / build-and-push (arm64, linux/arm64) (push) Canceled after 0s
GitHub Release / release (push) Canceled after 0s
Docker Release / create-manifest (push) Canceled after 0s
chore(release): 发布 v1.9.2
- 「公网服务端」新增 Nginx 启停控制(Agent 0.15.2,停止带二次确认)
- frps 启动失败不再误报成功,并增强启动诊断
- frps vhost 端口禁止 80/443,避免与 VPS 上 Nginx 冲突
- 旧服务端配置的 tcp_mux / login_fail_exit 默认值补齐
- 新增安全审查、页面布局方案与场景操作文档
2026-10-07 19:26:02 +08:00

212 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 场景 A 可行性分析与操作步骤:多 Web 应用统一入口
> 日期:2026-10-07
> 场景:家庭宽带 NAS,多个 Web 应用统一 443 对外,VPS 做公网入口中转,TLS 在 NAS 本地终止
> 性质:只做分析与步骤说明,不含代码改动
> 结论:**整体可行**,但场景描述里有 3 处前提需要修正,否则其中一步会走不通
## 一、目标链路
```
用户 → https://app.example.com:443
→ DNS 解析到 VPS IP
→ VPS:443(frps 监听,由 tcp 规则提供)
→ FRP TCP 隧道(remotePort 443)
→ NAS frpc → 127.0.0.1:443
→ Havline 本地 Nginx(TLS 在此终止,按 server_name 分流)
→ 内网应用
```
这条链路的关键特征:**TLS 全程到 NAS 才解开**,VPS 只做 TCP 转发,看不到明文。相比「VPS 上终止 TLS」的方案,这是更安全的一种,值得保留。
## 二、先修正场景描述里的三处前提
### 前提 1:`http://` 跳转 `https://` 这一步走不通
原文第 6 步写「按域名建规则(HTTPS + 强制跳转 + 选证书)」。问题在于:浏览器访问 `http://app.example.com` 走的是 **80 端口**,而本场景只把 **443** 通过隧道引到 NAS,80 的流量根本到不了本地 Nginx。
再看 Havline 的实现,`http_redirect` 的语义是「在**同一个监听端口**上额外生成一个 301 server 块」:
```go
// internal/nginx/generate.go
if rule.HTTPRedirect && useHTTPS {
b.WriteString(" return 301 https://$host:$server_port$request_uri;\n")
}
```
它服务于「同一端口既能收明文又能收 TLS」的场合,无法覆盖 80 端口。如果为了跳转把规则的监听端口设成 80 并开启 HTTPS,生成的会是 `listen 80 ssl;`,属于错误配置。
**处理方式(三选一)**:
1. **推荐**:在 VPS 层做跳转——VPS 上已有 Nginx 时,加一个 `listen 80; server_name app.example.com; return 301 https://$host$request_uri;`。这与隧道无关,最简单。
2. 额外开一条 tcp 隧道(remotePort 80 → 本地 80),再用自定义全局配置在本地 Nginx 手工加跳转。
3. 接受「只有 https 可访问」,不做跳转。
因此在第 6 步里,`HTTP 跳转 HTTPS` 开关不要打开。
### 前提 2:443 只能作为 TCP 规则的远程端口,不能填进 frps 的 vhost 端口
场景第 1 步写「放行 bindPort 与 443」,这是对的;但要避免一个常见混淆:Havline 在生成 frps.toml 时**明确禁止** `vhostHTTPPort` / `vhostHTTPSPort` 使用 80/443,会直接报错:
> frps vhost 回源端口不能使用 80/443(通常由 VPS 上的 Nginx 占用);请在服务端表单改为 8080/8443 后重新生成配置
原因:vhost 端口是给「http/https 类型规则 + VPS Nginx 反代」用的,属于另一条技术路线。**本场景用的是 `tcp` 类型规则**,443 是规则的 remotePort,两者互不冲突。
### 前提 3:DDNS 解析到家庭宽带 IP 并不总是可行
原文最后一段写「域名也可解析到家庭宽带 IP(DDNS 留空自动检测),VPS 仅做隧道;两种都可行」。这条要加前提:
1. 家宽必须是**真正的公网 IPv4**(非 CGNAT 大内网),否则外部无法直连到家庭 IP;
2. 国内运营商普遍**封锁家宽的 80/443 入站**,即使有公网 IP,443 也可能不通。
只要这两条有一条不满足,「解析到家庭 IP」就等于解析到一个不可用的地址,链路会静默失败。相比之下,**解析到 VPS IP 是更稳的选择**,也是本场景的推荐形态。
## 三、项目里的功能支撑(已逐项核对)
| 场景需要 | 项目实现 | 位置 |
| --- | --- | --- |
| 生成/下发 frps.toml | `FRPSServerConfig`(脱敏预览)+ `PushFRPSConfigToAgent`(下发) | [frps_template.go](F:/Fonu-main/havline/internal/frp/frps_template.go)、服务端「详情 → frps.toml」 |
| DDNS 自定义 IP(指向 VPS) | 自定义 IPv4/IPv6,留空则自动检测本机公网 IP | [DdnsView.vue](F:/Fonu-main/havline/web/src/views/DdnsView.vue:451) |
| 通配符证书 DNS-01 | lego `dns01`,支持 Cloudflare / DNSPod / 阿里云 / 腾讯云 / 火山 | [dns_provider.go](F:/Fonu-main/havline/internal/acme/dns_provider.go:16) |
| DNS-01 凭证复用 DDNS | **自动复用**,无需重填 | [service.go](F:/Fonu-main/havline/internal/acme/service.go:126) `ddnsSvc.ConfigForDNSZone` |
| FRP TCP 规则 | 类型 tcp + 远程端口 + 内网目标 | FrpView 规则表单 |
| 反代规则监听 443 + HTTPS + 出口 | 监听端口 / 出口多选 / 启用 HTTPS | [ProxyView.vue](F:/Fonu-main/havline/web/src/views/ProxyView.vue:459) |
| 证书自动绑定规则 | 按域名自动匹配,**通配符优先于精确域名** | [generate.go](F:/Fonu-main/havline/internal/nginx/generate.go:249) `findCertificate` |
| 校验并应用 | frpc `verify` 通过才落盘 | [manager.go](F:/Fonu-main/havline/internal/frp/manager.go:41) |
两点值得注意的细节:
1. **证书不需要在反代规则里逐个选**。生成器按域名找证书,顺序是先试 `*.example.com` 再试 `example.com`。所以只要申请了通配符证书,之后新增子域名规则会自动套用。
2. **DNS-01 凭证确实复用 DDNS**:`prepareApplyByZone` 先取 DDNS 配置,再由 `dnsCredentialsForDomain` 拿到 `ddns.Credentials` 交给 `newDNS01Provider`。前提是申请的域名必须落在 DDNS 已托管的 zone 内,否则会触发 `DomainsUnderZones` 校验失败。
## 四、操作步骤
### 步骤 1:VPS 侧准备
1. 确认 443 空闲:`ss -lntp | grep ':443 '`。若 VPS 上已有 Nginx 占用 443,需要先停掉或改端口,否则 frps 的 tcp 规则绑定失败。
2. 放行端口:`bindPort`(默认 7000)与 **443**。
3. 如果 frps.toml 配了 `allowPorts` 白名单,务必把 443 加进去。
4. 生成 frps 配置:Havline →「公网服务端」→ 选中该 VPS →「frps.toml」查看同源配置,用「下发 frps 配置」写入 VPS,或在 VPS 上手工放置。
5. 启动 frps,确认控制端口可连。
### 步骤 2:DDNS 指向 VPS
1. 「域名解析」页 → 新建任务 → 服务商选你的 DNS 服务商,填入凭证。
2. 记录填写完整域名:`example.com`、`*.example.com`(通配符便于后续新增子域)。
3. 点该任务的「自定义 IP」→ IPv4 填 **VPS 的 IP**(这一步很关键,不填的话 Havline 会去检测 NAS 自己的公网 IP)。
4. 立即同步一次,确认解析生效:`nslookup app.example.com` 返回 VPS IP。
### 步骤 3:申请通配符证书
1. 「证书」页 → 申请 → CA 选 Let's Encrypt → 校验方式为 **DNS-01**。
2. 域名填 `example.com` 与 `*.example.com`(一起提交,避免重复申请)。
3. 凭证无需填写,会自动复用步骤 2 里 DDNS 的凭证。
4. 申请成功后确认通配符证书落在 `wildcard.example.com` 目录下。
### 步骤 4:添加 FRP 服务端
1. 「公网服务端」→ 添加服务端。
2. 主机地址填 VPS IP,端口 7000,Token 与 frps 一致。
3. 若要用本页的「安装 Agent / 下发 frps 配置」能力,同时填好 Agent 地址与 SSH 信息。
### 步骤 5:添加 FRP TCP 规则
1. 「公网服务端」→ 选中该服务端 → 新建规则。
2. 类型 `tcp`;内网地址 `127.0.0.1`;内网端口 `443`;公网远程端口 `443`。
3. 命名建议用真实用途(例如 `nas-https-entry`),不要沿用 `ssh-tcp` 这类与实际不符的名字。
4. 启动 frpc,日志应出现该规则的 `start proxy success`。
### 步骤 6:建立反向代理规则
**入口更正**:这一步在「**反向代理**」页(路由 `/proxies`),不是「公网反代」页。「公网反代」(`/public-proxy`)管理的是部署到 **VPS 上的 Nginx 反代**,属于「VPS 出口」路线(TLS 在 VPS 终止);本场景要的是 NAS 本地 Nginx,必须用「反向代理」页。
页面右上角「新建规则」→ 弹窗有三个页签:**基础配置 / 安全设置 / Nginx**。
#### 基础配置页签逐字段填写
| 字段 | 填写值 | 说明 |
| --- | --- | --- |
| 名称 | `NAS 统一入口` | 选填,建议填,便于列表识别 |
| 前端域名 | `app.example.com` | 必填;每行一个;**不要带端口** |
| 监听端口 | `443` | 必填;**默认值是 80,必须改成 443** |
| 监听协议 | 只勾 `IPv4` | 默认即 IPv4;除非 VPS 侧走 IPv6 否则不必勾 IPv6 |
| 目标地址 | `http://127.0.0.1:8080` | 换成实际应用地址 |
| 出口 | 只勾「本机 Nginx」 | **不要勾 Cloudflare 隧道** |
| 启用 HTTPS | 打开 | 默认已开 |
| HTTP 跳转 HTTPS | **关闭** | **默认是打开的,必须手动关**,原因见前提 1 |
| 启用规则 | 打开 | 默认已开 |
证书不需要选,生成器按域名自动匹配步骤 3 的通配符证书(优先 `*.example.com`)。
#### 两个默认值陷阱
1. `监听端口` 默认是 **80**,不改的话会以 root 权限在 NAS 上监听 80,与预期不符。
2. `HTTP 跳转 HTTPS` 默认是 **开**。打开后会在 443 端口上额外生成一个明文 301 server 块——对公网访问没有实际作用,反而让配置语义混乱,因此要关掉。
#### 安全设置页签(可选)
1. 「一键推荐」会填入:HTTPS + 请求限流(10 r/s,burst 20)+ 安全响应头。**注意它会把 `HTTP 跳转 HTTPS` 重新打开**,点完要回基础配置再关掉。
2. **不要开启「仅中国大陆 IP」**:TCP 隧道下客户端真实 IP 已丢失(见 5.1),该策略会失效或误判。
3. 需要额外保护时可开 Basic Auth;「高级 → 仅 TLS 1.3」按需。
#### Nginx 页签
保持默认的生成模式即可,不需要手工编辑。
#### 保存与生效
1. 点保存。保存前会做「域名 + 端口」冲突检查,若提示 `域名 x:443 已被规则「y」使用`,说明该端口已被占用。
2. 保存成功后后端会自动同步 Nginx 配置并重载;若返回 `sync_warning`,表示规则已写入但 Nginx 同步失败,需要到日志页排查。
3. 若 NAS 本地 443 已被其他服务占用,重载会失败——先用 `ss -lntp | grep ':443 '` 确认。
### 步骤 7:验证
1. frpc 日志:`start proxy success`,无 `start error`。
2. VPS 上确认 443 已被 frps 监听:`ss -lntp | grep ':443 '`。
3. 外网探测 TCP:
```powershell
1..5 | % { (Measure-Command { $c = New-Object Net.Sockets.TcpClient; $c.Connect('VPS_IP', 443); $c.Close() }).TotalMilliseconds }
```
4. 端到端验证(这一步才说明链路通):
```powershell
curl.exe -v --resolve app.example.com:443:VPS_IP https://app.example.com/
```
5. 检查返回的证书是否为通配符证书(`curl -v` 输出中 subject 应为 `*.example.com`)。
6. Havline「公网服务端 → 详情」看诊断与可用率;日志页看该服务端运行日志。
## 五、已知限制
### 5.1 客户端真实 IP 会丢失
TCP 隧道下,NAS 本地 Nginx 看到的 `$remote_addr` 是 frpc 来源地址(回环),不是访客真实 IP。影响两处:
1. Nginx 访问日志里的 IP 全部失真;
2. Havline 的「中国 IP 段」访问控制(按来源 IP 放行/拦截)会失效——所有请求看起来都来自内网。
若要保留真实 IP,需要 FRP 规则开启 `transport.proxyProtocolVersion = "v2"`,同时本地 Nginx 的监听加 `proxy_protocol` 并配合 realip 模块。这属于可选增强,需要两端同时改。
### 5.2 NAS 本地 443 的占用
Havline 要在 NAS 上监听 443。如果 fnOS 系统面板或其他容器已经占用 443,需要先让出端口。可用 `ss -lntp | grep ':443 '` 确认。
### 5.3 「本机出口」与「公网反代(VPS 出口)」是两条不同路线
1. 本场景用的是**本机出口**:配置生成在 NAS 本地 Nginx,TLS 在 NAS 终止。
2. 「公网反代」里另一种形态是 **VPS 出口**:frps 开 vhost 端口(8080/8443),VPS 上 Nginx 反代回源,TLS 在 VPS 终止。
两者不要混用同一条规则,否则会出现「证书在 NAS、TLS 却在 VPS 终止」的错配。判断方法:看规则表单里「出口」勾了哪些。
## 六、扩展性
这套结构的好处正如场景描述所说:**新增应用只改反代,不动 FRP**。
1. 新建反代规则,监听端口仍填 443,域名换 `app2.example.com`,上游指向新应用;
2. 生成器自动套用同一张通配符证书;
3. 不需要新增 FRP 规则、不需要改 frps、不需要动 DNS(通配符记录已覆盖)。
唯一需要留意的是:所有子域名共用同一条 443 隧道,因此 **NAS 本机 Nginx 是单点**——它挂了所有应用一起不可用。这是该架构的固有取舍,不是配置错误。