反向代理与域名
生产环境不要把 18080 与 19090 直接暴露给用户。前面挂一层反向代理,拿到 HTTPS、统一域名与可控的超时/缓冲策略。
YiYi Media 有两个对外入口,需要分别代理:
| 入口 | 后端 | 谁访问 | 特点 |
|---|---|---|---|
| 管理台与用户门户 | frontend(nginx)监听 18080 | 管理员与用户浏览器 | 普通 Web 应用,/api/* 与 /mcp 已由容器内 nginx 转给 gateway |
| 播放端点 | play-agent 监听 19090 | 播放客户端(Emby 生态客户端等) | 大文件、HTTP Range 分段、长连接 |
gateway(18086)、config(18085)、user(18082)、media(18083) 不需要也不应该对外代理。
上游地址按部署模式区分
这是两种模式在反代配置上唯一需要区别对待的地方:
| 部署模式 | 播放端点上游 | 说明 |
|---|---|---|
| 单机版 | 默认 127.0.0.1:19090 | Play Agent 是内置节点,监听端口固定为 19090;容器把它发布到宿主机,默认绑 0.0.0.0 |
| 分布式版 | <Play Agent 节点机地址>:19090 | 外部工作节点,反代可以跑在节点机上,也可以指向任意可达的节点地址 |
管理台的上游两种模式都是 127.0.0.1:18080(分布式版为 edge 机器上的 18080)。
单机版的 19090 默认可以直接对外
19090 是播放客户端直连的端口,所以单机版默认把它发布到 0.0.0.0, 不做反代也能用。宿主机上的 nginx / Caddy 直接反代 127.0.0.1:19090 即可。
如果你希望 19090 只对本机反代可见,把 .env 里的 YIYI_PLAY_AGENT_BIND_HOST 改成 127.0.0.1 再 docker compose up -d。 此时反代必须装在同一台宿主机上。
改了对外端口,记得同步节点的「对外地址」
无论是否反代,用户播放线路下发的地址取自节点的对外地址字段。 用自己的域名 + 443 反代时,到「节点管理」把内置播放节点的对外地址改成 https + 你的域名 + 443,否则用户拿到的仍是 19090 直连地址。
哪些端口该对外开放
| 端口 | 单机版 | 分布式版 |
|---|---|---|
18080(网页入口) | 对用户开放(通常经反代走 HTTPS) | 只在 edge 机器开放,同样经反代走 HTTPS |
19090(播放端点) | 默认发布到宿主机,播放客户端直连;也可收窄为回环由本机反代转发 | 节点机开放给客户端,或由节点机上的反代转发 |
18084(storage 节点) | 仅容器内部 | 节点机开放给控制面与客户端 |
5432、6379 | Compose 私有网络,不发布 | 只在私有网络内放行 |
18082、18083、18085、18086 | 仅容器内部 | 只在私有网络内可达 |
18088 | 仅容器内部 | 只绑 127.0.0.1 |
18089 | 不使用 | 只在私有网络内放行 |
这些端口不得开放到公网
5432(数据库)、6379(缓存)、18085(注册中心)、18089(集群许可证同步) 只允许在私网内放行。反代也不应该指向它们。
反代后不需要把域名写回 .env
容器不读 YIYI_PUBLIC_HOST 这类值——它只由 install.sh 做格式校验并用于安装结束时输出访问地址。 挂上反代之后,你只需要在防火墙与安全组里把 18080 限制到代理机来源,不用改 .env。
为什么需要
- HTTPS:浏览器剪贴板 API、Service Worker、Telegram Mini App 都要求安全上下文;后台复制令牌之类的操作在 HTTP 下会降级。
- 域名收敛:用户记
yiyi.example.com与play.example.com,不需要记 IP 与端口。 - 隐藏端口:对外只暴露 443,18080 / 19090 用防火墙限制到代理机来源,减少扫描面。若把
YIYI_PLAY_AGENT_BIND_HOST设为127.0.0.1,19090 天然不可从外部直连。 - 独立的超时与缓冲策略:后台接口与播放流量对超时的要求完全不同,分开两个站点才能各自调。
- 多条播放线路:同一个
play-agent可以被多个域名代理,每个域名在后台登记成一条「手动反代地址」,用于区分流量归属与做用户级授权。
前置条件
| 项目 | 要求 |
|---|---|
| 代理软件 | Caddy 2 或 nginx |
| 部署位置 | 单机版:装在内置 Play Agent 所在的宿主机(反代 127.0.0.1:19090)。分布式版:Play Agent 节点机上,或任意能访问到节点 19090 的机器 |
| 域名 | 至少一个域名解析到代理机;播放端点建议单独一个 |
| 证书 | Caddy 自动签发;nginx 需自备(例如 certbot) |
| 后台登记 | 播放域名要在「节点管理 → 手动反代地址」里登记,并拿到入口令牌 |
| 出站连通 | 应用要能访问你登记的播放域名(它会对该地址的 /health 做心跳检测) |
Caddy 配置
管理台:
# /etc/caddy/Caddyfile
yiyi.example.com {
encode zstd gzip
request_body {
max_size 20MB
}
reverse_proxy 127.0.0.1:18080 {
# SSE 与长响应:立即 flush,不在代理层攒够一块再发
flush_interval -1
}
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
}
}播放端点(独立域名 + 入口令牌):
# /etc/caddy/Caddyfile
play.example.com {
# 播放是 Range 分段流,不要限制请求体、不要缓冲响应
# 单机版写 127.0.0.1:19090(内置 Play Agent 发布在宿主机上)
# 分布式版写 <Play Agent 节点机地址>:19090
reverse_proxy 127.0.0.1:19090 {
header_up X-YiYi-Endpoint-Token <你的入口令牌>
flush_interval -1
}
}Caddy 会自动补 X-Forwarded-For / X-Forwarded-Proto / Host,不需要手写。
caddy validate --config /etc/caddy/Caddyfile
systemctl reload caddynginx 配置
管理台:
server {
listen 80;
listen [::]:80;
server_name yiyi.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name yiyi.example.com;
ssl_certificate /etc/letsencrypt/live/yiyi.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yiyi.example.com/privkey.pem;
# 与镜像内 nginx 保持一致:后台上传限制 20 MB
client_max_body_size 20m;
location / {
proxy_pass http://127.0.0.1:18080;
proxy_http_version 1.1;
proxy_set_header Host $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;
# 大文件上传与 SSE 都要求不在代理层缓冲
proxy_request_buffering off;
proxy_buffering off;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}
}播放端点:
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name play.example.com;
ssl_certificate /etc/letsencrypt/live/play.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/play.example.com/privkey.pem;
# 播放请求体很小,但响应是长流:0 表示不限制
client_max_body_size 0;
location / {
# 单机版写 127.0.0.1:19090;分布式版写 <Play Agent 节点机地址>:19090
proxy_pass http://127.0.0.1:19090;
# 入口令牌:必须注入,否则这条线路的用户级授权不生效
proxy_set_header X-YiYi-Endpoint-Token <你的入口令牌>;
proxy_http_version 1.1;
proxy_set_header Host $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;
proxy_request_buffering off;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}nginx -t && systemctl reload nginx这些参数不是可选项
镜像内 nginx 已经用了 proxy_request_buffering off 与 600 秒读写超时, 外层代理如果开了缓冲或用默认 60 秒超时,会把内层的设置抵消掉: 表现为上传大文件失败、播放拖动后卡住、传输进度条长时间不动。
播放端点独立域名与手动反代地址
把播放流量放到独立域名,好处是可以单独限速、单独换证书、单独下线,也能给不同用户群分发不同线路。步骤:
- 按上面的配置把
play.example.com反代到 Play Agent 的 19090:单机版写127.0.0.1:19090(内置节点),分布式版写节点机地址。改完后在「节点管理」把该节点的对外地址改成https+ 域名 +443。 - 后台 → 节点管理 → 播放代理节点区 → 新增,服务类型选 手动反代地址。这一步在两种部署模式下都可用,也是单机版唯一能新增的项目。
- 填协议
https、域名play.example.com、端口443(填 443 或留空都会得到干净的https://play.example.com)。 - 保存后系统生成入口令牌,并对该地址的
/health做心跳检测。 - 点卡片上的「反代配置」,抽屉里直接给出可复制的两段片段:
proxy_set_header X-YiYi-Endpoint-Token <你的入口令牌>;# /etc/caddy/Caddyfile
header_up X-YiYi-Endpoint-Token <你的入口令牌>nginx 片段放进转发到 play-agent 的 location 块内,Caddy 片段放进 reverse_proxy 的站点块内。
- 到「用户管理 → 节点」把这条线路授权给对应用户。
不注入令牌,这条线路的授权与配额都不生效
系统只认令牌,不再依赖 X-Forwarded-Host 推断入口归属。未注入 X-YiYi-Endpoint-Token 时,经该地址进入的请求会被当作直连处理, 针对这条手动地址的用户级授权不会生效,流量也不会记到这条线路上。
入口令牌不是权限密钥
它是「入口标识」。客户端直连 play-agent 时也能自己构造这个头,所以 节点级授权永远必要,入口令牌只能在节点授权之上加严,不能放宽任何权限。 知道别人线路令牌的人可以把直连流量伪装成那条线路、消耗它的共享额度。 令牌泄漏时在抽屉里点「重新生成令牌」——旧令牌立即失效,反代配置必须同步更新。
手动反代地址不参与节点部署与远程升级,也不占授权配额,它只是一条人工登记的分发入口。 单机版中它是「节点管理」里唯一可以新增的项目。
大文件与长连接参数
| 参数 | 管理台 | 播放端点 | 为什么 |
|---|---|---|---|
| 请求体上限 | 20m / 20MB | 不限制(0) | 后台上传 Logo 等限制 20 MB;播放请求体很小但响应很大 |
| 请求缓冲 | 关 | 关 | 上传与长 POST(例如 /mcp)不能被攒在代理里 |
| 响应缓冲 | 关 | 关 | 播放是 Range 分段流,SSE 是小包长连接,缓冲会让进度与事件延迟到攒满才下发 |
| 读/写超时 | 600s | 3600s | 播放会话可以持续很久,默认 60 秒会在长片播放中途断开 |
| HTTP 版本 | 1.1 | 1.1 | 保持长连接与分块传输 |
| 压缩 | 可开 | 不要开 | 对视频字节流压缩毫无收益,还会破坏 Range 与增加 CPU |
播放链路依赖 HTTP Range:客户端发 Range,play-agent 把 Range 透传给上游并返回 206 Partial Content。反代不要改写或吞掉 Range / Accept-Ranges / Content-Range,也不要把 206 当成异常。
WebSocket 与 SSE
- SSE 有:文件传输进度走
GET /api/storage/mounts/transfer/events(经gateway)。前端带Accept: text/event-stream,用fetch流式读取响应体而不是EventSource(因为要带鉴权头)。gateway对 event-stream 响应会设置X-Accel-Buffering: no并逐块 flush,但这只保证它自己那一跳——外层代理仍必须关闭缓冲(nginxproxy_buffering off、Caddyflush_interval -1),否则事件被攒在代理里,前端读不到增量,会退回前台 3 秒轮询:进度更新变粗、请求量上升。 - WebSocket 没有:YiYi Media 自身没有 WebSocket 服务端实现。Emby 协议响应里的
WebSocketPortNumber只是协议字段,不代表有 WS 端点。所以不需要Upgrade/Connection头的特殊处理;加上也无害。 /mcp是 Streamable HTTP:请求与响应都可能长时间挂着,同样要求关闭缓冲、放宽超时。它由镜像内 nginx 的location = /mcp转给gateway,外层代理只要按管理台的配置整体转发到 18080 就覆盖了。注意 MCP 用的是Authorization: Bearer头,与后台管理接口的X-Admin-Token不同。
自定义请求头必须原样透传
链路上有三类自定义头承担着实际语义:后台鉴权 X-Admin-Token、存储节点选择 X-Storage-Node-Id、播放入口归属 X-YiYi-Endpoint-Token。nginx 与 Caddy 默认都会 透传带连字符的头,但不要在 location 里把它们显式清空或覆盖;前面还挂了 CDN 时, 确认 CDN 没有剥离未知请求头,否则表现为「本机能用、走域名就鉴权失败或线路归属错乱」。
单机版只有一个内置 Storage,前端不会出现节点切换菜单,X-Storage-Node-Id 由前端固定指向 node-local-storage,你不需要关心它;分布式版有多个文件管理节点时才需要它参与。
验证
# 证书与跳转
curl -sSI https://yiyi.example.com/ | head -5
# 接口经代理可达(未激活时返回状态 JSON,这是正常的)
curl -fsS https://yiyi.example.com/api/license/status
# 播放端点健康
curl -fsS https://play.example.com/health
# 播放域名不应该被压缩:响应头里不该出现 Content-Encoding
curl -sS -D - -o /dev/null https://play.example.com/emby/System/Info/Public \
| grep -i 'content-encoding\|HTTP/'
# SSE 是否实时下发(后台管理接口用 X-Admin-Token;分布式版注册了多个文件管理节点时才需要 X-Storage-Node-Id)
curl -N -sS -H 'Accept: text/event-stream' \
-H 'X-Admin-Token: <你的管理员令牌>' \
https://yiyi.example.com/api/storage/mounts/transfer/events | head -3HTTP Range 的实际验证要走真实播放:用播放客户端打开一部影片并拖动进度条,能立即续播说明 206 与 Content-Range 被完整透传。SSE 也可以直接在浏览器里看——登录后台打开传输队列,开发者工具 Network 里那条 transfer/events 请求应该持续有事件进来,而不是一直停在「等待响应」。
后台确认两处:节点管理里那条手动反代地址状态是在线(说明应用出站能访问到它的 /health);用该线路实际播放一次,确认针对这条地址的用户级授权生效、流量记到了这条线路上。
常见问题排查
| 现象 | 原因 | 处理 |
|---|---|---|
| 页面能开,接口 502 | 代理指向的 18080 不在同一台机器,或 frontend 与 gateway 不同机 | 反代目标必须是 frontend 所在机器的 18080;frontend 与 gateway 必须同机 |
| 上传大文件 413 | 外层 client_max_body_size 小于内层的 20m | 外层设 20m 或更大 |
| 播放几分钟就断 | 读超时用了默认 60 秒 | 播放站点设 proxy_read_timeout 3600s(Caddy 用 transport http { read_timeout 3600s }) |
| 拖动进度条后一直转圈 | 响应缓冲开着,或压缩改写了分段响应 | proxy_buffering off,播放站点关掉 gzip / encode |
| 传输进度条不动,页面靠轮询 | SSE 被外层代理缓冲 | 关缓冲;Caddy 加 flush_interval -1 |
| 手动反代地址显示离线 | 应用出站访问不到该域名,或 /health 被反代拦了 | 在本机 curl https://<你的播放域名>/health |
| 某条线路的用户级授权不生效 | 反代没注入 X-YiYi-Endpoint-Token,或令牌轮换后没更新配置 | 从抽屉复制最新片段并 reload 代理 |
| 播放域名 404 / 走到后台页面 | 两个站点配成了同一个 server_name,或播放域名反代到了 18080 | 播放域名必须反代到 play-agent 的 19090 |
| 单机版播放域名连不上 19090 | 反代与内置 Play Agent 不在同一台宿主机,或 YIYI_PLAY_AGENT_BIND_HOST 被设成了别的地址 | 把反代装到 YiYi-media-standalone 所在宿主机;默认绑 0.0.0.0:19090,回环地址可直接反代 |
反代配好了,客户端仍连到 19090 | 节点的「对外地址」没改 | 到「节点管理」把内置播放节点的对外地址改成反代域名与 443 |
| 客户端拿到的是内网地址 | 分布式版节点记录里登记的对外地址不可达 | 把节点的对外地址改成客户端可达的域名或公网 IP,协议与端口同步改 |