内网环境手机 HTTPS 访问本地前端项目实践指南 —— 为真机调试摄像头采集
更新: 6/12/2026 字数: 0 字 时长: 0 分钟
💡 一句话目标: 让同一 WiFi 下的手机用
https://访问电脑上的本地前端项目,从而在真机上调试摄像头采集。核心方案:mkcert 签发内网受信任证书 + Caddy 反向代理套一层 HTTPS。

一、背景:为什么非要折腾 HTTPS
开发涉及摄像头采集(扫码、人脸、活体检测、AR 等)的前端功能时,本地写完代码必须在手机真机上验证——桌面与手机浏览器的摄像头行为、权限弹窗、分辨率差异很大,很多问题只在真机复现。
而手机访问电脑上的本地项目,绕不开一个硬约束:
⚠️
navigator.mediaDevices.getUserMedia属于「安全上下文」(Secure Context) API,浏览器仅在https://或localhost下放行。
手机通过局域网 IP(如 http://192.168.1.100:8080)访问时既不是 https,也不是 localhost,于是 navigator.mediaDevices 直接为 undefined,摄像头根本调不起来。所以核心目标只有一句话:让手机能用 https 访问到电脑上的本地前端项目。
二、整体方案
整个链路分两层:mkcert 负责生成「内网受信任」证书让手机不报红,Caddy 作为反向代理在前端 dev server(HTTP)前面套一层 HTTPS。
手机浏览器
│ https://192.168.1.x:3000 (加密,证书受信任)
▼
Caddy 反向代理(持有 mkcert 证书,负责 TLS)
│ http://127.0.0.1:8080 (明文,本机回环)
▼
前端 Dev Server(webpack 等,正常 HTTP)✅ 关键设计: TLS 只在 Caddy 这一层做,前端 dev server 保持普通 HTTP。既不用改构建配置,也避免「双层 TLS」错配。
三、实施流程(已验证可用)

第 1 步:用 mkcert 生成本地受信任证书
# 1. 安装 mkcert(macOS)
brew install mkcert nss
# 2. 安装本地根 CA(只需执行一次,会把根证书装进系统信任库)
mkcert -install
# 3. 为你的局域网 IP 签发证书(把 192.168.1.xxx 换成你电脑的实际 IP)
mkcert 192.168.1.xxx localhost执行后会在当前目录生成两个文件:
192.168.1.xxx+1.pem—— 证书192.168.1.xxx+1-key.pem—— 私钥
💡 怎么查局域网 IP: macOS 用
ipconfig getifaddr en0,或在「系统设置 → 网络」里看。填的是192.168.x.x这类内网地址,不是公网 IP。
第 2 步:安装并配置 Caddy
brew install caddy在项目根目录新建一个名为 Caddyfile 的文件(无后缀),写入:
https://192.168.1.xxx:3000 {
tls ./192.168.1.xxx+1.pem ./192.168.1.xxx+1-key.pem
reverse_proxy 127.0.0.1:8080
}| 配置项 | 含义 |
|---|---|
https://192.168.1.xxx:3000 | 手机访问的入口地址(对外端口 3000) |
tls ...pem ...key.pem | 指定上一步生成的证书文件,确保与 Caddyfile 同目录 |
reverse_proxy 127.0.0.1:8080 | 前端项目真实运行地址(按实际端口改) |
第 3 步:启动 Caddy
caddy run # 前台运行
# caddy start # 后台运行
# caddy reload # 改完 Caddyfile 热加载第 4 步:手机访问
确保手机和电脑连同一个 WiFi,手机浏览器打开 https://192.168.1.xxx:3000,此时摄像头 API 即可正常调用。
第 5 步(视情况):手机安装 mkcert 根证书
mkcert 的根 CA 默认只信任在生成证书的那台电脑上。手机是独立设备,若访问时仍提示证书不受信任,需把根证书装到手机:
# 根证书文件为该目录下的 rootCA.pem
mkcert -CAROOT📱 iOS: 传 rootCA.pem 到手机 → 安装描述文件 → 设置 → 通用 → 关于本机 → 证书信任设置 里手动开启完全信任(这一步最易漏!)。
🤖 Android: 设置 → 安全 → 加密与凭据 → 安装证书 → CA 证书。

四、Caddy 反向代理是怎么工作的
- 手机发来的是 HTTPS 加密请求,由 Caddy 用 mkcert 证书完成 TLS 握手与解密;
- Caddy 解密后,用明文 HTTP 把请求转发给本机回环
127.0.0.1:8080上的 dev server; - dev server 的响应原路返回,Caddy 再加密后发回手机。
整个加密只发生在「手机 ↔ Caddy」之间,内网回环 127.0.0.1 这一段是明文,安全且高效。

五、踩坑实录与排查(重点)
坑 1:connection reset by peer(连上即被重置)
❌ 现象: Caddy 日志报
aborting with incomplete response/read: connection reset by peer,duration仅 1 毫秒。
原因: 协议错配。上游 dev server 其实跑的是 HTTPS,而 Caddy 的 reverse_proxy 默认用明文 HTTP 去连,TLS 服务收到明文请求无法解析直接 RST。
解决: 推荐让 dev server 退回普通 HTTP,TLS 全交给 Caddy(即本文标准方案);若 dev server 必须保持 HTTPS,则让 Caddy 用 TLS 连上游:
reverse_proxy https://127.0.0.1:8001 {
transport http {
tls_insecure_skip_verify
}
}坑 2:Connection refused(端口压根没人监听)
❌ 现象:
curl http://127.0.0.1:8001直接Connection refused,但浏览器打开127.0.0.1:8001却正常。
原因: dev server 没监听 IPv4 回环。常见两种:只监听了 IPv6 的 ::1(macOS 上 localhost 常解析到 IPv6),而 curl 127.0.0.1 走 IPv4 连不上;或只绑定了某张网卡 IP,没绑回环。
# macOS
lsof -iTCP:8001 -sTCP:LISTEN -n -P
# Linux
ss -tlnp | grep 8001解决: 把 dev server 监听地址改成 0.0.0.0(IPv4/IPv6 都听),从根上消除回环歧义:
devServer: {
host: '0.0.0.0',
port: 8001,
allowedHosts: 'all', // 防止 Invalid Host header
}或者让 Caddy 显式指向 IPv6:reverse_proxy [::1]:8001。
坑 3:端口冲突 —— Caddy 对外端口和上游端口设成同一个
如果 Caddy 监听 192.168.1.x:8001,上游 dev server 也用 8001,一旦 dev server 绑的是 0.0.0.0:8001,两者就会抢同一个端口,出现诡异的自我代理或绑定失败。
✅ 最佳实践: 对外端口和上游端口永远分开。比如 Caddy 用 3000/8443 对外,上游保持 8080/8001。本文标准方案(外 3000、内 8080)就是这么设计的。
坑 4:手机证书报红 / 摄像头仍调不起来
- 证书报红 → 手机没装或没信任 mkcert 根证书;iOS 特别注意漏了「证书信任设置」里的开关。
- 摄像头
getUserMedia为undefined→ 确认地址栏是https://而非http://。 - iOS Safari 调不起摄像头 → 需要用户手势触发,即用户点击按钮后再调
getUserMedia,不能页面一加载就自动调。
坑 5:Invalid Host header
dev server 收到非预期 Host 时拒绝服务。webpack-dev-server v4/v5 设 allowedHosts: 'all',v3 设 disableHostCheck: true。
六、排查速查表
| 现象 | 根因 | 处理 |
|---|---|---|
| connection reset,1ms 断开 | 上游是 HTTPS,Caddy 用 HTTP 连 | dev server 改回 HTTP,或 Caddy 用 https + tls_insecure_skip_verify |
| curl 127.0.0.1 refused 但浏览器能开 | dev server 只听 ::1 (IPv6) | dev server 改 host: '0.0.0.0',或 Caddy 用 [::1]:port |
| 端口绑定失败 / 自我代理 | 对外端口 = 上游端口 | 两个端口分开(外 3000,内 8080) |
| 手机证书报红 | 未装 / 未信任 mkcert 根证书 | 装 rootCA.pem,iOS 开「证书信任设置」 |
| getUserMedia 为 undefined | 用的是 http 而非 https | 改用 https 访问 |
| iOS 摄像头调不起 | 缺用户手势 | 点击按钮后再调用 |
| Invalid Host header | dev server Host 校验 | allowedHosts: 'all'(v3 用 disableHostCheck: true) |
| 手机连不上 | 防火墙 / 不同网段 | 同一 WiFi + 放行对应端口 |
七、最佳实践小结
- TLS 只做一层:交给 Caddy,dev server 保持 HTTP,最省心也最不容易协议错配。
- 端口分离:对外端口 ≠ 上游端口,避免端口冲突。
- dev server 监听 0.0.0.0:根除 ::1 / 127.0.0.1 回环歧义,并加
allowedHosts: 'all'。 - 证书私钥不出内网:mkcert 方案全程在局域网内完成,比内网穿透 / 公网隧道安全,适合企业内网调试。
- 手机端只需一次性配置:装一次 mkcert 根证书并信任,之后长期复用。
- 证书文件加入 .gitignore:
*.pem不要提交到仓库。
🏁 按这套流程,手机就能稳定通过 HTTPS 访问本地前端项目,摄像头采集调试再也不用反复打包发测试环境了。