Skip to content

反向代理与域名 ​

生产环境不要把 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:19090Play 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、6379Compose 私有网络,不发布只在私有网络内放行
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 配置 ​

管理台:

txt
# /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"
    }
}

播放端点(独立域名 + 入口令牌):

txt
# /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,不需要手写。

bash
caddy validate --config /etc/caddy/Caddyfile
systemctl reload caddy

nginx 配置 ​

管理台:

nginx
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;
    }
}

播放端点:

nginx
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;
    }
}
bash
nginx -t && systemctl reload nginx

这些参数不是可选项

镜像内 nginx 已经用了 proxy_request_buffering off 与 600 秒读写超时, 外层代理如果开了缓冲或用默认 60 秒超时,会把内层的设置抵消掉: 表现为上传大文件失败、播放拖动后卡住、传输进度条长时间不动。

播放端点独立域名与手动反代地址 ​

把播放流量放到独立域名,好处是可以单独限速、单独换证书、单独下线,也能给不同用户群分发不同线路。步骤:

  1. 按上面的配置把 play.example.com 反代到 Play Agent 的 19090:单机版写 127.0.0.1:19090(内置节点),分布式版写节点机地址。改完后在「节点管理」把该节点的对外地址改成 https + 域名 + 443。
  2. 后台 → 节点管理 → 播放代理节点区 → 新增,服务类型选 手动反代地址。这一步在两种部署模式下都可用,也是单机版唯一能新增的项目。
  3. 填协议 https、域名 play.example.com、端口 443(填 443 或留空都会得到干净的 https://play.example.com)。
  4. 保存后系统生成入口令牌,并对该地址的 /health 做心跳检测。
  5. 点卡片上的「反代配置」,抽屉里直接给出可复制的两段片段:
nginx
proxy_set_header X-YiYi-Endpoint-Token <你的入口令牌>;
txt
# /etc/caddy/Caddyfile
header_up X-YiYi-Endpoint-Token <你的入口令牌>

nginx 片段放进转发到 play-agent 的 location 块内,Caddy 片段放进 reverse_proxy 的站点块内。

  1. 到「用户管理 → 节点」把这条线路授权给对应用户。

不注入令牌,这条线路的授权与配额都不生效

系统只认令牌,不再依赖 X-Forwarded-Host 推断入口归属。未注入 X-YiYi-Endpoint-Token 时,经该地址进入的请求会被当作直连处理, 针对这条手动地址的用户级授权不会生效,流量也不会记到这条线路上。

入口令牌不是权限密钥

它是「入口标识」。客户端直连 play-agent 时也能自己构造这个头,所以 节点级授权永远必要,入口令牌只能在节点授权之上加严,不能放宽任何权限。 知道别人线路令牌的人可以把直连流量伪装成那条线路、消耗它的共享额度。 令牌泄漏时在抽屉里点「重新生成令牌」——旧令牌立即失效,反代配置必须同步更新。

手动反代地址不参与节点部署与远程升级,也不占授权配额,它只是一条人工登记的分发入口。 单机版中它是「节点管理」里唯一可以新增的项目。

大文件与长连接参数 ​

参数管理台播放端点为什么
请求体上限20m / 20MB不限制(0)后台上传 Logo 等限制 20 MB;播放请求体很小但响应很大
请求缓冲关关上传与长 POST(例如 /mcp)不能被攒在代理里
响应缓冲关关播放是 Range 分段流,SSE 是小包长连接,缓冲会让进度与事件延迟到攒满才下发
读/写超时600s3600s播放会话可以持续很久,默认 60 秒会在长片播放中途断开
HTTP 版本1.11.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,但这只保证它自己那一跳——外层代理仍必须关闭缓冲(nginx proxy_buffering off、Caddy flush_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,你不需要关心它;分布式版有多个文件管理节点时才需要它参与。

验证 ​

bash
# 证书与跳转
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 -3

HTTP 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,协议与端口同步改

相关文档 ​

自托管媒体管理系统(单机版 / 分布式版) · 想先体验或有问题,加 Telegram 群:t.me/yiyi_media_group