SparkBox/Guides/Jellyfin unhealthy

Jellyfin Shows "Unhealthy" But Playback Works Fine — Here's Why

You run docker ps and the Jellyfin container sits there marked unhealthy, over and over, no matter how many times you restart it — yet you can open the web UI and stream a movie without a hitch. Nine times out of ten this is a Docker health check (a small script Docker runs on a timer to decide if a container is "working") that expects a plain HTTP 200 response on the root path, and Jellyfin's root path doesn't send one back that way.

Jellyfin home screen on a SparkBox server
Jellyfin home screen on a SparkBox server

Rather not hand-edit health check configs at all? SparkBox runs Jellyfin (and the rest of a media stack) with correct defaults out of the box, so this class of false-alarm "unhealthy" status doesn't show up in the first place. See the walkthrough →

The 10-second version: Find whatever is running a custom health check against Jellyfin's / path expecting a 200 status, and either remove it (letting the image's own built-in health check take over) or point it at something that actually returns 200. Then recreate the container.

Why this happens

Docker containers can define a HEALTHCHECK — a command that runs periodically inside the container to report back "healthy" or "unhealthy" to whatever is watching (Docker itself, Portainer, a monitoring dashboard, Kubernetes, etc). The official Jellyfin image already ships its own built-in health check that's written to work correctly against Jellyfin's actual endpoints.

Portainer container management on a SparkBox server
Portainer container management on a SparkBox server

The problem starts when a docker-compose file, a copied-and-pasted setup guide, or a reverse proxy adds an additional health check on top of that — usually a generic one that just does the equivalent of "curl the root path and expect a 200 back." That's a completely reasonable default for a lot of web apps. It's the wrong assumption for Jellyfin, because hitting / on a Jellyfin server returns a redirect to the web interface rather than a bare 200. A check that only accepts "exactly 200" will read that redirect as a failure — and since the redirect never stops happening, the container never stops being marked unhealthy, even though nothing is actually wrong with it.

Gotcha: "Unhealthy" by itself usually doesn't stop or restart your container — it's just a status label. But if you're using something like depends_on: condition: service_healthy in a compose stack, or an auto-healing tool that restarts anything marked unhealthy, this false positive can cause real disruption: dependent services refusing to start, or Jellyfin getting restarted in a loop for no reason.

Fix it: find and fix the offending health check

1. Confirm this is actually the cause

Check what Docker's health check has been logging for the container:

docker inspect --format='{{json .State.Health}}' <container_name>

Look at the recent log entries in the output. If you see a failure tied to an HTTP status that isn't 200, or a check hitting the root path (/), that confirms the mismatch described above.

2. Look for a custom healthcheck block

Open your docker-compose file (or whatever run command/template you used to create the container) and search for a healthcheck: section. If you find one that's checking the root path and expecting a 200, that's the culprit. Some setups add this out of habit, copying it from a template meant for a different app.

3. Remove the override, or fix the target

You have two reasonable options:

4. Recreate the container

Health check configuration is baked in when a container is created — a plain restart won't pick up your edit. Recreate it:

docker compose up -d --force-recreate

5. Give it a minute, then check again

Health checks run on an interval with a retry count before Docker updates the status, so don't panic if it still says "starting" or "unhealthy" for the first minute or two. Recheck with:

docker ps

You're looking for the status to settle on healthy.

If a reverse proxy is the one flagging it

If you're running Jellyfin behind nginx, Traefik, Caddy, or a load balancer, that layer can have its own independent upstream health probe, separate from Docker's own status. If your dashboard, load balancer, or proxy shows Jellyfin as "down" while the container itself says healthy (or vice versa), check that proxy's health check configuration the same way — look for a probe against / expecting a flat 200, and either loosen the accepted status codes to include redirects or point it at a path you know returns 200 directly.

Gotcha: Fixing Docker's health check and fixing your reverse proxy's health check are two separate jobs. It's common to fix one, see the container itself go "healthy," and still see a dashboard or proxy reporting it as down because it's running its own, unrelated probe.

Frequently asked

Does "unhealthy" mean Jellyfin is actually broken?

Not necessarily. "Unhealthy" is just the result of a health check script Docker (or a reverse proxy) runs on a timer. If that check is written wrong, it can flag a perfectly working Jellyfin server as unhealthy forever, with no impact on actual streaming.

Why does Jellyfin fail a check that expects 200 on the root path?

Jellyfin's root path (/) issues a redirect to the web UI rather than returning a plain 200 response. A generic health check that only accepts a 200 status code will treat that redirect as a failure, even though the server is responding normally.

Is it safe to remove a custom healthcheck from my compose file?

Yes, in most cases. The official Jellyfin image already ships its own built-in health check instruction that probes the correct place. Removing a custom override lets Docker fall back to that built-in check instead of a naive one.

My reverse proxy also marks Jellyfin down. Same issue?

Usually yes. Reverse proxies and load balancers can run their own upstream health probes independently of Docker. If one of those is configured to expect a 200 on /, it hits the same redirect problem separately from whatever status Docker itself reports.

Skip tuning health checks by hand

SparkBox deploys Jellyfin with sane defaults already applied — no leftover "expect 200 on /" checks copied from a template that doesn't fit. Add movie requests, users, and other apps around it without touching a compose file.

Get SparkBox → Or read the media-server walkthrough →

About this guide: Written and tested by the SparkBox team on a UGREEN DXP4800 Plus and a $7/month Hostinger VPS, both running SparkBox 1.6.763. The causes above are the real ones we've diagnosed in d/sparkbox. If something doesn't match, tell us on YouTube.