在 NAS 上通过 Docker 部署了一系列服务后,使用 Nginx(或 OpenResty)做统一的反向代理是常规操作。然而,在接入 Home Assistant(HA) 时,很多人都会迎头撞上一个极其顽固的错误:网页端无法加载,控制台疯狂报错 400 Bad Request。
更让人崩溃的是,当你为了解决 HA 的问题修改了 Nginx 配置后,可能会发现 NAS 上的其他服务(比如为知笔记 Wiznote)又双叒叕连不上了!本文将复盘这一系列"按下葫芦浮起瓢"的踩坑过程,并给出能完美兼容所有服务的终极 Nginx 配置。
症状描述
- 通过内网 IP + 端口访问 HA 一切正常。
- 通过 Nginx 反向代理域名(如
https://nas.example.com:10461)访问 HA 时,页面卡在登录或白屏。 - 按下 F12 打开浏览器控制台,会看到以下报错:
Manifest fetch from .../manifest.json failed, code 400
WebSocket connection to 'wss://.../api/websocket' failed
两大核心根因
这次 400 错误并不是单纯的一个问题,而是两个安全机制叠加产生的"连环套":
根因一:HA 的代理白名单机制
Home Assistant 对安全性要求极高。默认情况下,它会拒绝所有来自未知反向代理服务器的请求,以防止 IP 欺骗。如果 Nginx 部署在 Docker 中,或者走的是 IPv6 回环地址(::1),HA 识别到的源 IP 就会发生变化。只要这个 IP 不在 HA 的白名单里,一律无情 400 拦截。
根因二:Nginx 的 WebSocket 全局污染
HA 极度依赖 WebSocket 进行实时通信。为了让代理支持 WebSocket,很多人在 Nginx 全局 http 块中直接加上:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
这就是灾难的开始。 这会导致 Nginx 把所有发往 HA 的普通 HTTP 请求(如获取图片或 manifest.json)都强行伪装成 WebSocket 升级请求,HA 底层的 Web 服务器发现格式不对,同样报 400。
此外,这种全局配置还会波及同服务器上的其他服务,比如为知笔记(Wiznote)。为知笔记依赖 HTTP 长连接(Keep-Alive)来加载数据,收到强制的 upgrade 或 close 指令后,直接罢工断连。
附加根因:端口透传丢失
如果使用了 proxy_set_header Host $host;,会丢失用户请求的原始端口号。对于内部依赖端口号进行数据交互的服务(如为知笔记网页端),这会导致界面能打开但数据无限转圈。
终极解决方案
第一步:设置 HA 的"全内网"白名单
最稳妥的方法是把所有安全的私有网段(涵盖局域网、Docker 网桥、IPv6 本地地址)全部加进 HA 的配置文件。
打开 HA 映射目录下的 configuration.yaml,添加或修改以下内容:
http:
use_x_forwarded_for: true
trusted_proxies:
- 127.0.0.0/8 # 本机 IPv4
- ::1 # 本机 IPv6 (极其重要,很多本地代理会走这个)
- 10.0.0.0/8 # A 类私有网络
- 172.16.0.0/12 # Docker 默认的虚拟网络网段
- 192.168.0.0/16 # C 类私有网络(涵盖大部分家用路由器网段)
- fe80::/10 # 本地链路 IPv6
⚠️ 修改完成后,必须完全重启 Home Assistant 容器,配置才能生效:
docker restart homeassistant
第二步:配置动态 WebSocket + 端口透传(Nginx)
核心思路是动态判断客户端是否真的在请求 WebSocket。同时,使用 $http_host 完美透传原始端口,确保其他服务不被干扰。
打开 Nginx(或 OpenResty)配置文件 nginx.conf:
http {
# ... 其他基础配置 ...
# 1. map 块:动态识别 WebSocket 请求
# 收到 Upgrade 头才升级,否则保持普通长连接(拯救为知笔记等服务)
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# 2. 全局代理参数优化
# 使用 $http_host 完美透传域名和端口
proxy_set_header Host $http_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;
# 3. 统一开启动态 WebSocket 支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# --- 代理 Home Assistant ---
server {
listen 10461 ssl;
server_name nas.yourdomain.com;
location / {
proxy_pass http://192.168.31.3:8123;
# 无需再重复写 proxy_set_header,完美继承全局变量
}
}
# --- 代理为知笔记(完全不受干扰,顺滑加载) ---
server {
listen 10449 ssl;
server_name nas.yourdomain.com;
location / {
proxy_pass http://192.168.31.3:9000;
}
}
}
第三步:重载生效
sudo nginx -t
sudo nginx -s reload
总结
| 问题 | 解决方案 |
|---|---|
| HA 400 + Docker/IPv6 源 IP 变化 | 在 configuration.yaml 加全网段 trusted_proxies |
全局 upgrade 污染其他服务 |
用 map $http_upgrade $connection_upgrade 动态判断 |
$host 丢失原始端口 |
改用 $http_host 透传 |
| 改完 HA 不生效 | docker restart homeassistant 重启容器 |
在复杂的 NAS 容器网络中遇到代理报 400 Bad Request 时,查阅目标服务的日志永远是第一步。改用 $http_host 和 map 动态升级方案后,不仅仅是 Home Assistant 和 Wiznote,以后在这台服务器上部署任何奇奇怪怪的 Web 服务,这套基础网络架构都能稳稳支撑!