---
title: Jellyfin
description: Install Jellyfin, a free and open-source media server, to stream your movies and TV shows from anywhere without a paid subscription.
---


:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
# Jellyfin

::note
🎯 __Objectives:__

- Install Jellyfin
- Access your media from outside your network
::

[Jellyfin](https://jellyfin.org) is a free, open-source alternative to Plex and Emby. Unlike Plex, it has no paid tiers, no telemetry, and doesn't require an online account to use or manage your server: everything runs locally and stays yours.

It covers the same basics: a media library with metadata and artwork, transcoding (including hardware acceleration), and apps for TV, Android, iOS, Windows, and macOS.

As always, we'll use the [linuxserver.io image](https://docs.linuxserver.io/images/docker-jellyfin).

::note

Unlike Plex, Jellyfin has no cloud relay: to access your server outside your local network, you must expose it yourself (see below), or use a VPN like [Wireguard](/serveex/security/wireguard).
::

## Install Jellyfin
Folder structure:
```text [Directory tree]
root
├── docker
│   └── jellyfin
│       ├── compose.yaml
│       ├── .env
│       └── config
└── media
    ├── tvseries
    ├── movies
    └── library
```

Create the `movies`, `tvseries`, and `library` folders in `/media`:

```bash [Terminal]
mkdir -p /media/movies /media/library /media/tvseries
```

Open Dockge, click `compose`, name the stack `jellyfin`, and add the following config:

```yaml [compose.yaml]
---
services:
  jellyfin:
    image: lscr.io/linuxserver/jellyfin:latest
    container_name: jellyfin
    environment:

      - PUID=${PUID}
      - PGID=${GUID}
      - TZ=Europe/Paris
    volumes:

      - /docker/jellyfin/config:/config
      - /media:/media
    restart: unless-stopped
    devices:

      - /dev/dri:/dev/dri
    ports:

      - 8096:8096
```

::tip
✨ Add the Watchtower label to automate updates:

```yaml [compose.yaml]
services:
  jellyfin:
    #...
    labels:

      - com.centurylinklabs.watchtower.enable=true
```
::

Find your PUID and GUID by running:

```bash [Terminal]
id username
```

Fill in your `.env` file with the retrieved values, for example:

```properties [.env]
PUID=1000
GUID=1000
```

Deploy the stack. The local interface is available at `http://yourserverip:8096`.

::note

The `/dev/dri` device is only needed for hardware-accelerated transcoding on Intel/AMD GPUs. Remove it if your server doesn't have one, or adapt it for an NVIDIA GPU following [linuxserver.io's documentation](https://docs.linuxserver.io/images/docker-jellyfin/#hardware-acceleration).
::

## Configure Jellyfin
On first visit, Jellyfin walks you through a setup wizard:

- Choose a display language and create your admin account.
- Add a media library, pointing to `/media/movies` for movies and `/media/tvseries` for TV shows.
- (Optional) Enable hardware acceleration in _Dashboard > Playback_ if you have a compatible GPU.

And that's it! Add your media to `/media/movies` and `/media/tvseries` on your server, then install the Jellyfin app on your devices to watch locally or remotely.

::note

If your media is stored on a network disk (e.g. NAS or external hard drive over the network), refer to the [Samba mount guide](/general/networking/samba) so Jellyfin can access it.
::

## Expose Jellyfin with Swag
To access Jellyfin outside your local network, we'll expose it through Swag.

::note

We assume you have the subdomain `jellyfin.mydomain.com` with a `CNAME` pointing to `mydomain.com` in your [DNS zone](/general/networking/dns). And of course, [unless you use Cloudflare Zero Trust](/serveex/security/cloudflare), your box's port `443` must be forwarded to your server's port `443` in [NAT rules](/general/networking/nat).
::

Go to Dockge and edit SWAG's compose file by adding Jellyfin's network:

```yaml [compose.yaml]
services:
  swag:
     container_name: # ...
      # ... 
     networks:              # Attach container to custom network 
      # ...           

      - jellyfin            # Name of the declared network

networks:                   # Define the custom network
  # ...
  jellyfin:                 # Declared network name
    name: jellyfin_default  # Actual external network name
    external: true          # Marks it as externally defined
```

Redeploy the stack and wait for SWAG to be fully operational.

::note

Here we assume the Jellyfin network name is `jellyfin_default`. You can check the connection by visiting SWAG's dashboard at `http://yourserverip:81`.
::

In the Swag folders, create the file `jellyfin.subdomain.conf`:

::tip{icon=""}
✨ __Tip:__ Use [File Browser](/serveex/files/file-browser) to navigate and edit files instead of using terminal commands.
::

```bash [Terminal]
sudo nano /docker/swag/config/nginx/proxy-confs/jellyfin.subdomain.conf
```

Paste the following configuration:

```nginx [jellyfin.subdomain.conf]
## Version 2023/12/19

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name jellyfin.*;

    include /config/nginx/ssl.conf;

    client_max_body_size 0;

    # enable for ldap auth (requires ldap-location.conf in the location block)
    #include /config/nginx/ldap-server.conf;

    # enable for Authelia (requires authelia-location.conf in the location block)
    #include /config/nginx/authelia-server.conf;

    # enable for Authentik (requires authentik-location.conf in the location block)
    #include /config/nginx/authentik-server.conf;

    location / {
        # enable the next two lines for http auth
        #auth_basic "Restricted";
        #auth_basic_user_file /config/nginx/.htpasswd;

        # enable for ldap auth (requires ldap-server.conf in the server block)
        #include /config/nginx/ldap-location.conf;

        # enable for Authelia (requires authelia-server.conf in the server block)
        #include /config/nginx/authelia-location.conf;

        # enable for Authentik (requires authentik-server.conf in the server block)
        #include /config/nginx/authentik-location.conf;

        include /config/nginx/proxy.conf;
        include /config/nginx/resolver.conf;
        set $upstream_app jellyfin;
        set $upstream_port 8096;
        set $upstream_proto http;
        proxy_pass $upstream_proto://$upstream_app:$upstream_port;
    }
}
```

Press :kbd{value="Ctrl+O"}, then :kbd{value="Enter"} to save, and :kbd{value="Ctrl+X"} to exit.

Wait a few minutes, then open `https://jellyfin.mydomain.com` in your browser.

::caution

__If it fails:__ check your firewall rules.
::

::tip{icon=""}
✨ __Tip:__ You can protect this app with [TinyAuth](/serveex/security/tinyauth) and [Pocket ID](/serveex/security/pocket-id) using the reverse-proxy pattern from the TinyAuth guide, or with Authentik by opening `jellyfin.subdomain.conf` and uncommenting `include /config/nginx/authentik-server.conf;`{lang=nginx} and `include /config/nginx/authentik-location.conf;`{lang=nginx}. Don't forget to [create an application and provider in Authentik](/serveex/advanced/authentik#protecting-an-app-via-reverse-proxy).
::

And you're done!
