效率工具

让家里的 Jellyfin 在外面也能看:Tailscale Serve 与 Caddy 配置

通过 Tailscale Serve 或 Caddy 为 Jellyfin 配置安全的远程访问,并实测 HTTPS、Known Proxies 与客户端 IP 识别。

BINBHO · 图文教程

让家里的 Jellyfin 在外面也能看,同时尽量少开端口。这里有两条路:只给自己和家人用的话,用 Tailscale Serve,只有你 tailnet 里的设备能访问,不需要开放任何端口;需要用域名访问时,在前面加一个 Caddy 反向代理负责 HTTPS,并且在 Jellyfin 里配置 Known Proxies,否则 Jellyfin 看不到客户端的真实 IP。

先看清成品

  • Caddy: 在 box 上用 Docker 实测了 Caddy + Jellyfin:
    • caddy validate 显示 Valid configuration。
    • 用 tls internal 的自签证书,带上 Caddy 根证书访问返回 200;不带根证书时,curl 报证书错误(退出码 60)。
  • Known Proxies: 没配置时,Jellyfin 看到的客户端 IP 是 172.25.0.3(Caddy 自己)。配置并重启后,变成了 172.25.0.1(真实的来源地址)。
  • Tailscale: 命令按官方文档整理,没有实测(box 上没有 Tailscale 账号)。
  • 公网域名和 Let's Encrypt 证书也没有测过。

注意:Jellyfin 是免费开源的软件,硬件转码也不收费。媒体库里只放你有权使用的内容;不要安装或分享盗版插件和资源,不要把服务器开放给家庭以外的人共享内容。本文的测试素材都是用 ffmpeg 生成的测试图样和正弦波。

安全提醒:不要把 8096 端口直接映射到公网。家庭用途优先选 Tailscale Serve;Funnel 会把服务公开到整个互联网,一般不需要。

做完之后你会得到什么?

成果 说明
Caddyfile tls internal 的局域网版本;换成域名就能自动申请证书
docker-compose.yml Jellyfin + Caddy,给 Caddy 固定 IP,Known Proxies 才不会失效
proxy_check.py 检查 Jellyfin 看到的客户端 IP,并且可以修改 Known Proxies
tailscale.sh Serve 的开启、查看、关闭命令

需要准备哪些环境?

项目 要求 说明
方案一 Tailscale 账号,服务器和手机都装上客户端 不需要开放端口
方案二 自己的域名,80/443 端口能从公网访问 Caddy 会自动申请证书
Python 3.8 以上 pip install requests

文件结构

38-jellyfin-C-src/
├── Caddyfile
├── docker-compose.yml
├── proxy_check.py
└── tailscale.sh

方案一:Tailscale Serve(推荐给家庭使用)

tailscale.sh:

# 不开放端口的远程访问:Tailscale Serve(只有自己 tailnet 里的设备能访问)
# 在运行 Jellyfin 的机器上,安装 Tailscale 并登录以后:
tailscale serve --bg 8096          # 在后台运行,https://<机器名>.<tailnet>.ts.net 会转发到本机 8096
tailscale serve status             # 查看当前在提供哪些服务
tailscale serve reset              # 关闭并清空全部 serve 配置(也可以在原来的命令末尾加 off,只关掉那一条)
# Funnel 会把服务公开到互联网(只能用 443、8443、10000 端口)。家庭媒体库一般不需要,不建议打开

官方文档说明:--bg 表示在后台运行;Funnel 只能监听 443、8443、10000 三个端口,而且只能用 tailnet 自己的 ts.net 域名。

方案二:Caddy 反向代理

Caddyfile:

# 局域网 / Tailscale 内使用:Caddy 自签证书(tls internal),客户端需要信任 Caddy 的根证书,或者在测试时用 curl -k
# 有公网域名时,把站点名换成你的域名,并删掉 tls internal,Caddy 会自动申请证书(需要 80/443 端口可以从公网访问)
jellyfin.home.arpa {
	tls internal
	reverse_proxy jellyfin:8096
}

docker-compose.yml:

# Jellyfin + Caddy。给 Caddy 固定 IP,Known Proxies 才不会因为容器重启、IP 变化而失效
services:
  jellyfin:
    image: jellyfin/jellyfin
    user: "1000:1000"
    volumes:
      - ./config:/config
      - ./cache:/cache
      - /srv/media:/media:ro
    networks: [media]
    restart: unless-stopped
  caddy:
    image: caddy:2
    ports: ["443:443", "80:80"]
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./caddy-data:/data
    networks:
      media: { ipv4_address: 172.25.0.3 }
    restart: unless-stopped
networks:
  media:
    ipam: { config: [{ subnet: 172.25.0.0/24 }] }
$ docker run --rm -v $PWD/Caddyfile:/etc/caddy/Caddyfile:ro caddy:2 caddy validate --config /etc/caddy/Caddyfile
Valid configuration
$ docker exec caddy cat /data/caddy/pki/authorities/local/root.crt > caddy-root.crt
$ curl --cacert caddy-root.crt https://jellyfin.home.arpa/System/Info/Public
{"LocalAddress":"http://172.25.0.2:8096","ServerName":"…","Version":"12.2.0",…}

配置 Known Proxies

proxy_check.py:

"""检查反向代理后面的 Jellyfin 能不能看到客户端的真实 IP(Known Proxies 设置有没有生效)。
Jellyfin 只信任“已知代理”发来的 X-Forwarded-For;没配置时,所有访问都会显示成代理自己的 IP,
“只允许局域网访问”之类基于 IP 的限制也就跟着失效了。
用法:JF_PUBLIC=https://你的域名 JF_PASSWORD=... python proxy_check.py [--ca 根证书.pem]
      python proxy_check.py --set-proxy 172.25.0.3   (需要管理员身份,修改 Known Proxies)"""
import os, sys
import requests

BASE = os.environ.get("JF_PUBLIC", "https://jellyfin.home.arpa").rstrip("/")
CA = sys.argv[sys.argv.index("--ca") + 1] if "--ca" in sys.argv else True
DEV = "proxy-check-1"
AUTH = f'MediaBrowser Client="proxy_check", Device="script", DeviceId="{DEV}", Version="1.0"'

def login():
    r = requests.post(BASE + "/Users/AuthenticateByName", json={"Username": os.environ.get("JF_ADMIN", "admin"), "Pw": os.environ["JF_PASSWORD"]},
                      headers={"Authorization": AUTH}, verify=CA, timeout=15)
    r.raise_for_status(); return r.json()["AccessToken"]

def h(tok): return {"Authorization": AUTH + f', Token="{tok}"'}

if __name__ == "__main__":
    tok = login()
    if "--set-proxy" in sys.argv:
        ip = sys.argv[sys.argv.index("--set-proxy") + 1]
        c = requests.get(BASE + "/System/Configuration/network", headers=h(tok), verify=CA, timeout=15).json()
        c["KnownProxies"] = [ip]
        requests.post(BASE + "/System/Configuration/network", json=c, headers=h(tok), verify=CA, timeout=15).raise_for_status()
        print(f"✓ Known Proxies = {[ip]}(本教程实测时重启了 Jellyfin 再检查;改完不确定是否生效时,就重启一次)")
    else:
        s = [x for x in requests.get(BASE + "/Sessions", headers=h(tok), verify=CA, timeout=15).json() if x.get("DeviceId") == DEV]
        print("Jellyfin 看到的客户端 IP:", s[0].get("RemoteEndPoint") if s else "(没有找到会话)")
$ python proxy_check.py --ca caddy-root.crt
Jellyfin 看到的客户端 IP: 172.25.0.3
$ python proxy_check.py --ca caddy-root.crt --set-proxy 172.25.0.3
✓ Known Proxies = ['172.25.0.3'](本教程实测时重启了 Jellyfin 再检查;改完不确定是否生效时,就重启一次)
$ docker restart jellyfin && python proxy_check.py --ca caddy-root.crt
Jellyfin 看到的客户端 IP: 172.25.0.1

检查点:配置之后,Jellyfin 里显示的客户端 IP 是真实地址,不再是代理的 IP。官方文档说明:如果没有配置,基于 IP 的“仅限本地网络访问”这类限制就会失效。

调试示例:所有访问都“来自”代理

刚接上 Caddy 的时候,proxy_check.py 显示所有访问都来自 172.25.0.3,也就是 Caddy 容器自己的 IP。官方文档解释得很清楚:Jellyfin 只信任 Known Proxies 列表里的代理发来的 X-Forwarded-For,其余的一律丢弃。把 Caddy 的 IP 加进 Known Proxies 并重启以后,显示变成了 172.25.0.1。第二个坑是:容器每次重启,IP 可能会变,所以 compose 里给 Caddy 固定了 ipv4_address。

常见问题

现象 原因 处理
浏览器提示证书不受信任 用的是 tls internal 自签证书 在设备上导入 Caddy 根证书,或者改用公网域名
客户端 IP 全是代理的 没有配置 Known Proxies 按上面的步骤配置
用了 Funnel 之后被陌生人访问 Funnel 是公开到互联网的 改回 Serve
手机 App 连不上 没有填 https 地址 填完整的 https:// 地址

模板规则

  • 能用 Tailscale Serve,就不要开放公网端口。
  • 用反向代理时一定配置 Known Proxies,并且给代理固定 IP。
  • 证书交给 Caddy 自动管理,不要把 DNS 服务商的 API 密钥写进 Caddyfile(官方文档也提醒了这一点)。

项目资源

用途 地址
反向代理(官方) https://jellyfin.org/docs/general/post-install/networking/reverse-proxy/
Caddy 配置(Jellyfin 官方) https://jellyfin.org/docs/general/post-install/networking/reverse-proxy/caddy/
Tailscale Serve https://tailscale.com/kb/1312/serve
tailscale serve 命令 https://tailscale.com/kb/1242/tailscale-serve
Tailscale Funnel https://tailscale.com/kb/1223/funnel
Caddy reverse_proxy https://caddyserver.com/docs/caddyfile/directives/reverse_proxy
Jellyfin 文档首页 https://jellyfin.org/docs/

远程访问要做到“能用”又“不裸奔”:家人自己用就走 Tailscale;要用域名就让 Caddy 负责 HTTPS,并且把 Known Proxies 配好。