Créer un serveur média maison avec Jellyfin et Proxmox
Ma famille et moi payions trois services de streaming différents chaque mois. Entre Netflix, Disney+ et Prime Video, la facture approchait les 40 € — et bizarrement, la moitié des films qu’on voulait vraiment regarder n’étaient sur aucun d’entre eux.
Voici l’histoire de comment un vieux disque dur, une soirée de bricolage et un logiciel open-source appelé Jellyfin sont devenus notre propre Netflix familial.
Pourquoi Jellyfin ?
J’ai d’abord regardé Plex — c’est le nom que tout le monde connaît. Mais Plex enferme progressivement des fonctionnalités derrière un paywall et pousse du contenu financé par la pub dans votre bibliothèque. Jellyfin est entièrement gratuit, complètement open-source, et fait exactement ce qu’on attend de lui : servir vos fichiers média sur n’importe quel appareil avec une interface propre, sans conditions cachées.
Pas d’abonnement. Pas de télémétrie qui téléphone à la maison mère. Pas de suggestions « continuer à regarder » pour du contenu que je n’ai jamais demandé.
Le matériel
Le serveur tourne sur mon homelab Proxmox — un hyperviseur que j’utilise pour tout, du DNS à la supervision. Le conteneur média reçoit 2 vCPU et 2 Go de RAM, ce qui est largement suffisant pour du streaming en lecture directe (pas de transcodage nécessaire pour la plupart des appareils modernes).
La vraie star, c’est un disque dur de 4 To que j’utilisais comme backup froid. Ce n’est pas un SSD, mais pour de la lecture séquentielle de fichiers média, il n’en a pas besoin. Le disque est connecté physiquement à l’hôte Proxmox en SATA, formaté en ext4, et monté sur /mnt/toshiba/MOVIES/ sur l’hôte. Plutôt que d’utiliser NFS ou virtio-9p (qui ajoutent tous deux de la latence réseau ou d’émulation), j’ai opté pour un simple bind mount — une ligne dans la configuration du conteneur qui mappe le dossier hôte directement dans le système de fichiers du conteneur :
mp0: /mnt/toshiba/MOVIES/,mp=/mnt/media-storage
C’est l’équivalent LXC Proxmox de mount --bind. Le noyau mappe le dossier hôte directement dans l’espace de noms de montage du conteneur sans aucune surcharge — pas de pile réseau, pas de couche FUSE, pas de démon userspace. Le conteneur le voit comme un dossier local dans /mnt/media-storage/, Jellyfin et qBittorrent lisent et écrivent directement dedans, et les performances sont identiques à un disque natif.
Hôte Proxmox
└── /mnt/toshiba/MOVIES/ (disque physique 4 To, ext4, SATA)
monté par bind dans le conteneur (mp0)
└── media-srv:/mnt/media-storage/
├── Films/ (dossiers par genre : action, scifi, comédie...)
├── Series/ (séries par genre et saison)
└── Animation/ (films d'animation)
La stack
Le conteneur fait tourner deux services, tous deux gérés par systemd :
Jellyfin (port 8096) est l’interface utilisateur — il scanne les dossiers média, télécharge les métadonnées et les pochettes depuis TMDB, et propose une interface web élégante qui fonctionne sur téléphone, tablette, smart TV et navigateur. Il gère plusieurs profils utilisateurs, donc chaque membre de la famille a son propre historique et ses favoris. Jellyfin stocke sa configuration et l’état de ses bibliothèques dans une base SQLite située dans /var/lib/jellyfin/data/jellyfin.db — chaque chemin de bibliothèque, profil utilisateur et position de lecture se trouve dans ce fichier unique.
qBittorrent-nox (port 8080) gère le téléchargement. Il tourne en mode headless avec son API Web exposée sur le port 8080, ce qui permet d’ajouter des torrents à distance, de définir des chemins de sauvegarde par genre, et de suivre la progression des téléchargements. L’API est simple — ajouter un torrent avec un chemin de sauvegarde spécifique se fait en un seul appel curl :
curl -s -b /tmp/qb_cookies.txt \
'http://localhost:8080/api/v2/torrents/add' \
--data-urlencode "urls=magnet:?xt=urn:btih:..." \
--data-urlencode "savepath=/mnt/media-storage/Films/scifi"
Quand un téléchargement se termine, qBittorrent laisse le fichier dans le dossier cible et Jellyfin le détecte au prochain scan de la bibliothèque — aucune intervention manuelle nécessaire.
Les deux services tournent dans un seul conteneur LXC non privilégié, isolé de l’hôte mais partageant le même noyau. Le conteneur est configuré avec le nesting activé (features: nesting=1), 2 Go de swap comme soupape de sécurité, et un ordre de démarrage de 2 pour qu’il se lance après les conteneurs d’infrastructure comme le DNS et le reverse proxy.
Chemin réseau : d’Internet à votre écran
Voici comment une requête distante atteint Jellyfin et sert un film :
- La requête externe arrive sur
media.example.comsur le port 443 - Le reverse proxy NGINX (un conteneur séparé sur le bridge interne
vmbr1) termine le TLS avec un certificat Let’s Encrypt et proxifie la requête vershttp://media-srv:8096 - Jellyfin reçoit la requête, vérifie le token d’authentification de l’utilisateur dans
jellyfin.db, récupère les métadonnées du média demandé, et diffuse le fichier directement depuis/mnt/media-storage/ - Sur le réseau local, les appareils contournent complètement le reverse proxy — l’application Jellyfin se connecte directement à l’IP interne du conteneur sur le port 8096
Le conteneur est sur un réseau bridge interne (vmbr1, 10.0.0.0/24) avec une réservation DHCP garantissant une IP stable. Pas de redirection de port sur le routeur, pas de bricolage DNS dynamique — juste le reverse proxy qui gère la terminaison TLS et le routage.
Les entrailles de Jellyfin : comment fonctionnent les bibliothèques
Le système de bibliothèques de Jellyfin mérite d’être compris car il détermine la façon dont vos fichiers apparaissent dans l’interface. Chaque bibliothèque est définie par une ligne CollectionFolder dans la base SQLite avec un champ JSON Data qui contient :
PhysicalLocationsList— tableau de chemins absolus du système de fichiers à scannerPhysicalFolderIds— UUIDs des éléments Folder de supportCollectionType—movies,tvshows,musicoumixed
Quand vous déclenchez un scan (depuis l’interface Web ou via l’API), Jellyfin parcourt chaque chemin de PhysicalLocationsList, vérifie les fichiers nouveaux ou modifiés, et met à jour son index interne. L’API de scan est un simple POST :
curl -X POST "http://localhost:8096/Library/Refresh" \
-H "X-Emby-Authorization: MediaBrowser Client=\"script\", Device=\"cli\", DeviceId=\"auto-scan\", Version=\"10.11.8\", Token=\"$TOKEN\""
Retourne HTTP 204 en cas de succès. Le scan s’exécute de manière asynchrone — vous pouvez le déclencher et obtenir une réponse immédiate pendant que Jellyfin parcourt les dossiers en arrière-plan.
Sur mon installation, les bibliothèques sont organisées ainsi :
- Films — pointe vers
/mnt/media-storage/Films/avecCollectionType: movies - Séries TV — pointe vers
/mnt/media-storage/Series/avecCollectionType: tvshows - Animation — pointe vers
/mnt/media-storage/Animation/avecCollectionType: movies
Chaque dossier de genre sous Films/ est un sous-dossier que Jellyfin découvre automatiquement pendant le scan — pas besoin de configurer une bibliothèque par genre.
Un exemple concret
Voilà à quoi ressemble le processus quand quelqu’un demande « on peut regarder Dune ? »
- Je cherche le torrent, je l’ajoute à qBittorrent avec le chemin de sauvegarde
/mnt/media-storage/Films/scifi/ - qBittorrent télécharge le fichier directement dans le bon dossier de genre
- Je déclenche un scan de bibliothèque Jellyfin — un simple
curlPOST vers l’API - Deux minutes plus tard, Dune apparaît dans la bibliothèque Jellyfin avec la pochette, le casting et les sous-titres
Tout le processus prend moins de cinq minutes, et une fois dans la bibliothèque, le film y reste pour toujours. Pas d’accords de licence qui expirent. Pas de bannières « bientôt retiré du catalogue ».
Accès familial
Jellyfin gère les profils utilisateurs avec contrôle parental, donc j’ai créé des comptes pour chaque membre de la famille. Les comptes des enfants ne voient que le contenu adapté à leur âge. Chacun a son propre historique, ses favoris, et sa ligne « continuer à regarder ».
L’accès depuis l’extérieur passe par un reverse proxy avec SSL Let’s Encrypt. Sur le réseau local, c’est direct — on ouvre l’appli, on choisit un film, on lance la lecture.
Organisation du stockage
Le disque de 4 To est organisé par genre au niveau racine, avec un sous-dossier Series/ pour les séries TV qui suit la convention Series/Genre/Nom de la série/Saison XX/. La configuration des bibliothèques Jellyfin pointe vers ces dossiers et détecte automatiquement films vs séries selon la structure des répertoires.
Avec environ 90 % d’occupation, plus de 70 films et plusieurs séries complètes, j’ai peut-être un an avant de devoir penser à ajouter un autre disque. Mais avec des disques de 4 To qui ne coûtent pas grand-chose, c’est un problème que j’accepte volontiers.
Sécurité
Le conteneur est non privilégié — une fonctionnalité de sécurité Proxmox qui mappe l’UID 0 à l’intérieur du conteneur vers l’UID 100000 sur l’hôte, donc même si un attaquant s’échappe du conteneur, il atterrit en tant qu’utilisateur non privilégié. Le conteneur tourne avec un minimum de paquets installés (pas de X11, pas de bureau, pas de compilateurs) et n’a aucune exposition directe à Internet — tout accès extérieur passe par le reverse proxy en HTTPS. Le client torrent tourne sous son propre utilisateur système (qbittorrent-nox), séparé de l’utilisateur du service Jellyfin, et le bind mount est en lecture seule pour Jellyfin (il n’a besoin que d’un accès en lecture pour le streaming) tandis que qBittorrent écrit les fichiers téléchargés.
L’ensemble est protégé par mon système de prévention d’intrusion CrowdSec et mon pipeline de centralisation des logs, donc toute activité suspecte est signalée immédiatement.
Est-ce que ça valait le coup ?
Absolument. L’investissement en temps initial a été de peut-être deux soirées — dont la majeure partie passée sur l’organisation des fichiers, pas sur l’installation du logiciel. Jellyfin s’est installé en quelques minutes via apt. La maintenance continue est quasi nulle : un apt upgrade de temps en temps, et déplacer des fichiers dans les bons dossiers après un téléchargement.
Pour le prix de l’électricité et d’un disque dur que je possédais déjà, ma famille dispose d’une bibliothèque média qui fonctionne sur tous les écrans de la maison, n’affiche jamais de pub, et ne retire jamais de contenu parce qu’un accord de licence a expiré.
Si vous avez une machine qui dort et un disque dur qui traîne, essayez Jellyfin. Votre portefeuille — et vos soirées cinéma en famille — vous remercieront.