网络协议

Nginx + Emby:配置 HTTPS 反向代理与 WebSocket

用 Nginx 为 Emby 配置 HTTPS、WebSocket 和流式转码,并在访问日志中脱敏 URL 查询参数里的 API key。

BINBHO · 图文教程

用 nginx 给 Emby 加上 HTTPS 反向代理:WebSocket(播放进度、远程控制要用)、关闭缓冲(边转码边播放)、上传大小限制,另外还有一个容易被忽略的安全点:访问日志会原样记下 URL 里的 api_key。这份配置在写日志前先把密钥打码。

先看清成品

在 box 上用 Docker 实测了 nginx:alpine + Emby 4.10.2.0:

  • nginx -t 显示 successful。
  • 通过 HTTPS 访问:用 api_key 查询参数返回 200,用 X-Emby-Token 请求头返回 200;WebSocket 握手返回 101。
  • 打码后的日志: 只出现 api_key=***,完整密钥 0 次。
  • 默认格式的日志: 换成默认日志格式再请求一次,完整密钥出现了 1 次(见调试示例)。
  • 证书用的是 openssl 生成的自签证书;公网证书没有测过。

注意:媒体库里只放你有权使用的内容,比如自己拍的家庭视频、自己购买的影片的合法备份、开放授权的作品。不要安装盗版插件,不要分享资源。硬件加速转码属于 Emby Premiere 的功能(官方 Premiere 页面写明“Hardware Accelerated Transcoding”),请按官方方式订阅,价格以官网为准。

做完之后你会得到什么?

成果 说明
emby.conf HTTPS、HTTP/2、WebSocket、关闭缓冲、给 api_key 打码的日志格式
docker-compose.yml Emby + nginx 放在同一个网络里

需要准备哪些环境?

项目 要求 说明
证书 内网 CA 或自签证书;有公网域名就用 Let's Encrypt 文件放在 ./certs/
Docker 带 Compose 插件
访问控制 只有自己人用的话,配合 Tailscale 见 38-jellyfin-C

文件结构

39-emby-C-src/
├── emby.conf
└── docker-compose.yml

第 1 步:nginx 配置

emby.conf:

# Emby 反向代理(nginx)。证书换成你自己的:家里用自签证书或内网 CA,有公网域名就用 Let's Encrypt
# 把 URL 里的 api_key 打码后再写访问日志:Emby 的部分客户端会把密钥放在查询参数里
map $request_uri $safe_uri {
    "~^(?<pre>.*[?&]api_key=)[^&]*(?<post>.*)$" "${pre}***${post}";
    default $request_uri;
}
log_format safe '$remote_addr [$time_local] "$request_method $safe_uri" $status $body_bytes_sent';

map $http_upgrade $connection_upgrade { default upgrade; '' close; }

server {
    listen 443 ssl;
    http2 on;
    server_name emby.home.arpa;
    ssl_certificate     /etc/nginx/certs/emby.crt;
    ssl_certificate_key /etc/nginx/certs/emby.key;
    access_log /var/log/nginx/emby.access.log safe;
    client_max_body_size 100m;           # 上传字幕、封面用

    location / {
        proxy_pass http://embyserver:8096;
        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_set_header Upgrade $http_upgrade;          # WebSocket:播放进度、远程控制要用
        proxy_set_header Connection $connection_upgrade;
        proxy_buffering off;                             # 边转码边播放,不要让 nginx 缓冲
    }
}

docker-compose.yml:

# Emby + nginx(HTTPS 和 WebSocket)。证书文件放在 ./certs 目录
services:
  embyserver:
    image: emby/embyserver
    environment: [UID=1000, GID=1000, GIDLIST=1000]
    volumes:
      - ./programdata:/config
      - /srv/media:/mnt/media:ro
    networks: [media]
    restart: on-failure
  nginx:
    image: nginx:alpine
    ports: ["443:443"]
    volumes:
      - ./emby.conf:/etc/nginx/conf.d/emby.conf:ro
      - ./certs:/etc/nginx/certs:ro
      - ./logs:/var/log/nginx
    networks: [media]
    restart: unless-stopped
networks:
  media: {}

第 2 步:验证

$ docker exec nginx nginx -t
nginx: configuration file /etc/nginx/nginx.conf test is successful
$ curl --cacert certs/emby.crt https://emby.home.arpa/emby/System/Info/Public
{"LocalAddresses":[],"RemoteAddresses":[],"ServerName":"…","Version":"4.10.2.0",…}
$ curl -o /dev/null -w '%{http_code}' --http1.1 -H "Connection: Upgrade" -H "Upgrade: websocket" \
       -H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
       "https://emby.home.arpa/embywebsocket?api_key=…&deviceId=ngtest"
101
$ cat logs/emby.access.log
172.25.0.1 [21/Oct/2026:08:06:57 +0000] "GET /emby/System/Info/Public" 200 131
172.25.0.1 [21/Oct/2026:08:06:57 +0000] "GET /emby/System/Info?api_key=***" 200 893
172.25.0.1 [21/Oct/2026:08:06:57 +0000] "GET /emby/System/Info" 200 893

检查点:WebSocket 返回 101,日志里只有 ***。(摘录的是测试时那几条普通请求的日志;WebSocket 那一条没有摘录。)

注:示例里把 compose 的端口映射写成 443:443;我在 box 上测试时映射到 18444,域名用的是 hosts 文件里临时加的一条记录。在手机 App 里填 https://你的域名 能正常登录和播放(这一步没有在真机上测)。

调试示例:默认日志格式泄露了密钥

我临时把日志格式换回 nginx 默认的 combined,再请求一次带 api_key 的 URL,日志里出现了:

172.25.0.1 - - [21/Oct/2026:08:07:11 +0000] "GET /emby/System/Info?api_key=<完整API密钥> HTTP/2.0" 200 893 "-" "curl/8.14.1"
完整密钥出现次数:1

能读到日志的人,就能用这个密钥访问你的媒体库。修改方法:用 map 把 $request_uri 里 api_key= 后面的值换成 ***,再用自定义的 log_format safe 写日志。改完以后,同样的请求在日志里完整密钥出现 0 次。自己写的脚本则一律用 X-Emby-Token 请求头(见 39-emby-A)。

常见问题

现象 原因 处理
播放进度不同步,远程控制没反应 WebSocket 没有升级成功 检查 Upgrade / Connection 头,测试应该返回 101
转码播放时一卡一卡 nginx 在缓冲响应 proxy_buffering off
上传字幕失败(413) 请求体太大 调大 client_max_body_size
日志里出现了密钥 用的是默认日志格式 用 safe 格式,并且换掉已经泄露的密钥

模板规则

  • 反向代理要同时处理 HTTPS、WebSocket 和日志打码。
  • 发现密钥进过日志,就立刻换掉。
  • 公网访问配合访问控制;家庭自用优先选 Tailscale。

项目资源

用途 地址
nginx WebSocket 代理 https://nginx.org/en/docs/http/websocket.html
nginx map 模块 https://nginx.org/en/docs/http/ngx_http_map_module.html
API 密钥认证(官方) https://dev.emby.media/doc/restapi/API-Key-Authentication.html
Emby Premiere 功能列表 https://emby.media/premiere.html

反向代理配好以后,最容易被忽略的是日志。密钥一旦进了日志,就会跟着日志文件被备份、复制到各处。打码只需要几行配置,在一开始就加上。