Jellyfin as a media server is fine. It does what it says. But the moment you want to expose it to the internet—even just to family outside the house—you hit the authentication problem. Traefik is excellent at routing. Less excellent at understanding Jellyfin’s session handling. I spent three evenings debugging why my auth was leaking before I found the right combination of headers and middleware.

Why this matters at all
Running Jellyfin on your homelab network is safe enough. The moment you add Traefik as a reverse proxy with the intent to reach it from outside, you introduce a new layer. Traefik can forward requests and terminate SSL, but it doesn’t automatically know how to preserve Jellyfin’s authentication state. Jellyfin uses session cookies. Traefik, by default, can strip or mishandle those. You end up with a media server that works fine on your LAN but logs you out every time you switch devices or your cookie gets rewritten by a proxy header.
The integration itself isn’t complicated. But the details matter, and Jellyfin’s documentation doesn’t really address the Traefik case specifically.
The docker-compose setup that works
I run both Jellyfin and Traefik in containers. Here’s what ended up in my homelab:
version: '3.8'
services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
volumes:
- /mnt/media/jellyfin/config:/config
- /mnt/media:/media:ro
environment:
- JELLYFIN_PublishedServerUrl=https://media.example.com
networks:
- traefik-net
labels:
- "traefik.enable=true"
- "traefik.http.routers.jellyfin.rule=Host(`media.example.com`)"
- "traefik.http.routers.jellyfin.entrypoints=websecure"
- "traefik.http.routers.jellyfin.tls.certresolver=letsencrypt"
- "traefik.http.routers.jellyfin.middlewares=jellyfin-headers"
- "traefik.http.services.jellyfin.loadbalancer.server.port=8096"
- "traefik.http.middlewares.jellyfin-headers.headers.customrequestheaders.X-Real-IP=192.168.1.0/24"
- "traefik.http.middlewares.jellyfin-headers.headers.customresponseheaders.Cache-Control=no-cache"
restart: unless-stopped
traefik:
image: traefik:v2.10
container_name: traefik
command:
- "--api.insecure=false"
- "--providers.docker=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
- "[email protected]"
- "--certificatesresolvers.letsencrypt.acme.storage=/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./acme.json:/acme.json
networks:
- traefik-net
restart: unless-stopped
networks:
traefik-net:
driver: bridge
The key part is that environment variable: JELLYFIN_PublishedServerUrl=https://media.example.com. Without it, Jellyfin thinks it’s running on localhost:8096 and generates URLs pointing there, which breaks everything downstream of the proxy. I missed that the first time.
The headers that actually matter
The middleware section is where most people get stuck. I initially thought I needed to add X-Forwarded-For, X-Forwarded-Proto, and half a dozen other headers. Turns out Traefik v2 forwards those automatically. What Jellyfin actually needs is clean handling of caching and a stable request path.
That Cache-Control: no-cache header prevents your browser from caching auth responses. The X-Real-IP header with your subnet ensures Jellyfin sees internal requests as local (which matters for some plugins). Neither of these is obvious from reading Traefik docs.
The part that surprised me: I originally added way more headers thinking I was solving a problem. Turns out the session leak was happening because I’d misconfigured the PublishedServerUrl. Once that was right, removing the extra headers actually made things more stable. Less is sometimes genuinely better.
Testing the auth actually holds
After spinning up the containers, I hit the dashboard from three different clients: Chrome on my desktop, Firefox on my phone, and VLC on a Raspberry Pi. The first login worked. The second device picked up the session. Switching between them for an hour didn’t force a re-login.
What I didn’t test initially: accessing it from a different domain name. If you have both media.example.com and 192.168.1.50 pointing at your Jellyfin instance, the cookies won’t transfer between them. That’s not a Traefik problem, that’s just how cookies work. Pick one address and stick with it.
The one thing that still feels fragile
Jellyfin’s plugin system doesn’t always play nice with proxied requests. The AI subtitle generation plugin (the one using Whisper) works fine. But the recommendation plugin sometimes fails to fetch metadata when routed through Traefik. I haven’t root-caused it yet. Might be the caching headers, might be something in the plugin itself. It’s worth testing your specific plugins after deployment rather than assuming they’ll all work out of the box.
Jellyfin + Traefik is a solid pairing for a homelab media setup. The config above will get you there. But spend fifteen minutes actually testing login behavior from different devices before declaring victory. That’s where the real problems show up.
Explore Jellyfin in our AI Homelab Toolkit.
Recommended Hardware & Hosting
Build your homelab with hardware tested and used by our team.
Affiliate links — we may earn a small commission at no extra cost to you.