Installer Jellyfin sur un VPS pour lire vos médias
Installez Jellyfin avec Docker sur un VPS : block storage, permissions des médias, direct play, transcodage CPU et accès distant sécurisé, sans GPU.
Ce que vous allez mettre en place
Un serveur multimédia Jellyfin sur un VPS : un conteneur, trois volumes et un disque de block storage contenant vos films et séries, accessibles depuis n’importe quel navigateur ou application Jellyfin. L’installation tient dans un fichier compose de quinze lignes. Les problèmes qui surviennent ensuite viennent de deux causes : les permissions de fichiers empêchent le conteneur de les lire, ou vous demandez à un VPS dépourvu de GPU de transcoder des vidéos qu’il n’a aucune raison de transcoder. Ce guide consacre l’essentiel de sa longueur à ces deux points, car c’est là que se concentrent les demandes d’assistance.
Jellyfin est gratuit et entièrement open source, sans compte, sans fonctionnalités payantes et sans télémétrie. C’est pourquoi il figure dans presque toutes les listes de solutions à auto-héberger en 2026. Il lit les contenus multimédias que vous possédez. Il ne fournit aucun contenu, et ce guide ne traite pas de l’acquisition de contenus.
La réalité du transcodage, avant de louer quoi que ce soit
Lisez ceci en premier, car cela détermine ce que vous devez acheter. Un media server fait l’une de ces deux choses lorsque vous lancez une lecture. Le direct play diffuse le fichier tel quel : le VPS lit les octets sur le disque et les envoie sur le réseau, avec une consommation CPU presque nulle. Le transcoding réencode la vidéo à la volée, pour changer la résolution ou le codec, ou pour incruster des sous-titres. Cette opération utilise entièrement le CPU.
Un VPS classique n’a pas de GPU. Chaque transcodage s’exécute donc sur le CPU avec libx264/libx265, et l’encodage logiciel coûte cher en ressources. Un seul transcodage 1080p en H.264 peut saturer plusieurs vCPU partagés. Un transcodage 4K ou HEVC ne parvient généralement pas à suivre le temps réel. La lecture s’interrompt alors et la mise en mémoire tampon se répète indéfiniment. Le transcodage matériel, qui rend cette opération peu coûteuse sur un serveur domestique équipé d’un iGPU Intel ou d’une carte Nvidia, n’est tout simplement pas disponible, sauf si votre fournisseur loue des instances GPU.
La stratégie sur un VPS est donc la suivante : éviter le transcodage. Conservez votre bibliothèque dans des codecs que vos clients lisent nativement : vidéo H.264, audio AAC ou AC3, dans un conteneur MP4 ou MKV. Choisissez aussi des applications clientes compatibles avec le direct play : les applications Jellyfin natives pour Android TV, iOS et Roku, ainsi qu’Infuse, Kodi et Jellyfin Media Player pour ordinateur. Dans ce cas, le VPS n’exécute jamais ffmpeg et un serveur modeste avec 2 vCPU peut diffuser des contenus à plusieurs personnes simultanément. Si vous prévoyez de transcoder, il vous faut un serveur beaucoup plus puissant et plus coûteux. Même dans ce cas, la 4K reste un mauvais choix.
Faites aussi le calcul de la bande passante, car c’est l’autre surprise. Le direct play envoie le fichier à son propre bitrate. Un fichier 1080p compressé utilise 8-12 Mbps ; un remux Blu-ray 1080p, 20-30 Mbps ; un fichier 4K HDR, 40-80 Mbps. Trois personnes qui lisent directement des fichiers à 10 Mbps génèrent 30 Mbps de trafic sortant continu depuis votre VPS. Vérifiez deux valeurs dans votre offre : le débit du port (peut-il fournir 30 Mbps en upload ?) et le plafond mensuel de transfert. Un film de deux heures à 10 Mbps représente environ 9 GB de trafic sortant. Une offre plafonnée à 1 TB/mois permet donc un peu plus d’une centaine de films de ce type par mois, soit trois ou quatre par jour. Un foyer qui regarde des contenus en 4K, avec un bitrate quatre à huit fois supérieur, épuise cette enveloppe beaucoup plus rapidement. Comptabilisez également tout ce qui génère du trafic sortant depuis le même serveur, notamment un relais RustDesk auto-hébergé, qui transporte l’intégralité d’une session de bureau à distance lorsque deux pairs ne peuvent pas se connecter directement.
Prérequis
- Un VPS KVM Ubuntu 24.04 fraîchement installé, avec un accès root ou sudo, ainsi que Docker et le plugin Compose installés.
- Un volume de stockage en mode bloc pour les médias, dimensionné en fonction de votre bibliothèque (voir le dimensionnement ci-dessous). Le petit disque racine fourni avec un VPS n’est pas destiné à stocker vos films.
- Un nom de domaine si vous voulez un accès HTTPS public, ou un VPN WireGuard sur le même VPS si vous préférez conserver l’ensemble en privé.
- Des médias que vous êtes légalement autorisé à diffuser : vos propres copies, vos propres enregistrements et les fichiers qui vous appartiennent.
Montez d’abord le stockage en mode bloc
Attachez le volume dans le panneau de votre fournisseur, puis identifiez-le et montez-le. Récupérez le nom du périphérique avec lsblk. Il ressemblera à /dev/sdb ou /dev/vdb, mais ne correspondra jamais au disque root.
lsblk
sudo mkfs.ext4 /dev/sdb # ONLY on a new, empty volume — this ERASES it
sudo mkdir -p /mnt/media
sudo blkid /dev/sdb # copy the UUID shown for this deviceMontez-le avec son UUID, et non avec /dev/sdb, car les lettres des périphériques peuvent changer après un redémarrage. Vous pourriez alors formater ou monter le mauvais disque. Ajoutez une ligne à /etc/fstab :
UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx /mnt/media ext4 defaults,nofail 0 2sudo mount -a
df -h /mnt/medianofail est important : sans ce paramètre, si le volume en mode bloc est détaché, la machine refuse de démarrer et bascule vers un shell d’urgence. L’erreur la plus grave consiste à exécuter mkfs.ext4 sur un volume qui contient déjà des données : cette commande les efface. Formatez uniquement les nouveaux volumes. Si le disque contient déjà votre bibliothèque, passez directement à la ligne fstab.
Organiser les médias comme Jellyfin l’attend
Jellyfin associe les métadonnées aux noms des dossiers et des fichiers. Si l’arborescence est incorrecte, les films apparaissent comme des fichiers sans titre ni affiche, ou un épisode est associé à la mauvaise série. Il existe exactement trois règles : chaque film doit se trouver dans son propre dossier Name (Year) et avoir un nom de fichier correspondant ; les dossiers de saison doivent être nommés Season 01, et non S01 ; les fichiers d’épisode utilisent S01E01 ; et les épisodes spéciaux vont dans Season 00.
/mnt/media
├── Movies
│ ├── Blade Runner (1982)
│ │ └── Blade Runner (1982).mkv
│ └── Arrival (2016)
│ └── Arrival (2016).mkv
└── Shows
└── Severance (2022)
├── Season 01
│ ├── Severance - S01E01.mkv
│ └── Severance - S01E02.mkv
└── Season 00
└── Severance - The Lexington Letter.mkvLe (Year) des films n’est pas décoratif : il permet de distinguer les remakes afin que le moteur d’association sélectionne le bon titre. Conservez Movies et Shows dans des dossiers racine distincts, car chacun devient une bibliothèque Jellyfin dédiée à un type de contenu. Les mélanger perturbe le fournisseur de métadonnées. Jellyfin indexera sans problème un troisième dossier contenant des photos, mais l’expérience reste limitée par rapport à un serveur photo spécialisé. Si vos albums sont importants, donnez-leur donc leur propre serveur exécutant PhotoPrism ou Immich et réservez celui-ci aux films et aux séries TV.
Permissions : la première cause des bibliothèques vides
Voici l’erreur qui peut vous faire perdre une soirée. L’image officielle jellyfin/jellyfin ne prend pas en charge les variables d’environnement PUID/PGID. Elles appartiennent à l’image LinuxServer.io (lscr.io/linuxserver/jellyfin). Avec l’image officielle, vous contrôlez l’utilisateur avec la clé user: dans compose. Si vous l’omettez, le conteneur s’exécute avec le compte root. Dans les deux cas, la règle est la même : l’uid/gid utilisé par le conteneur doit pouvoir lire et parcourir tous les répertoires contenant des médias.
Nous utiliserons l’uid/gid 1000, celui du premier utilisateur non-root sur une installation Ubuntu standard. Vérifiez le vôtre et définissez le propriétaire :
id # confirm your user is uid=1000 gid=1000
sudo chown -R 1000:1000 /mnt/media
sudo find /mnt/media -type d -exec chmod 755 {} \;
sudo find /mnt/media -type f -exec chmod 644 {} \;
mkdir -p ~/jellyfin/config ~/jellyfin/cache
sudo chown -R 1000:1000 ~/jellyfinLes répertoires doivent avoir le bit execute — le x dans 755 — et pas seulement le droit de lecture. Sans ce bit, le conteneur ne peut pas entrer dans le répertoire, même s’il peut en afficher le nom. Le piège qui vide toute une bibliothèque est le répertoire parent : si l’uid du conteneur ne peut pas parcourir le point de montage lui-même, il n’atteint jamais /media/Movies ni /media/Shows. Toutes les bibliothèques apparaissent alors vides, et Access to the path ... is denied est écrit dans le journal. Chaque répertoire de médias auquel il ne peut pas accéder est journalisé puis ignoré. Ainsi, un lot de fichiers copié avec root disparaît silencieusement de la bibliothèque. C’est pourquoi nous modifions récursivement le propriétaire et appliquons le bit execute à chaque répertoire, au lieu de corriger un seul dossier.
Le fichier docker-compose
services:
jellyfin:
image: jellyfin/jellyfin:10
container_name: jellyfin
user: "1000:1000"
restart: unless-stopped
ports:
- "127.0.0.1:8096:8096"
volumes:
- ./config:/config
- ./cache:/cache
- /mnt/media:/media:ro
environment:
- JELLYFIN_PublishedServerUrl=https://jellyfin.example.comLigne par ligne : user: "1000:1000" définit réellement les permissions des fichiers, conformément à la propriété définie plus haut. /config contient l’ensemble du serveur, les comptes, les bibliothèques, les métadonnées et l’état de surveillance. Il doit donc être accessible en écriture et c’est le répertoire à sauvegarder. /cache est un espace de travail temporaire. Le montage des médias est :ro (en lecture seule) à dessein : Jellyfin stocke par défaut les illustrations et les métadonnées dans /config. Il n’a donc jamais besoin d’écrire dans votre bibliothèque. Le mode lecture seule protège aussi vos fichiers contre une suppression accidentelle ou un plugin défectueux. Le port est volontairement lié à 127.0.0.1. La page de connexion web de Jellyfin utilise HTTP en clair. Nous n’exposons donc jamais le port 8096 sur Internet. JELLYFIN_PublishedServerUrl est l’adresse que le serveur annonce pour la découverte automatique locale, par diffusion UDP sur le réseau local. Les clients sur Internet ne la voient donc jamais et utilisent simplement l’URL que vous saisissez dans l’application. Définissez cette adresse sur celle qui doit être communiquée aux clients. Sur les appareils distants, vous devrez saisir cette URL manuellement.
Démarrez-le depuis le répertoire compose :
docker compose up -d
docker logs -f jellyfinPremière exécution : assistant de configuration et bibliothèques
Le port étant lié à localhost, ouvrez l’assistant depuis votre ordinateur portable au moyen d’un tunnel SSH au lieu d’ouvrir une brèche dans le firewall :
ssh -L 8096:127.0.0.1:8096 you@your-vps-ipAccédez ensuite à http://localhost:8096. L’assistant vous guide d’abord dans le choix de la langue, puis dans la création d’un compte administrateur avec un mot de passe robuste. Ce compte donne accès à votre serveur : n’utilisez donc pas un mot de passe temporaire ou réutilisé. Ajoutez votre première bibliothèque : choisissez le type de contenu Movies, indiquez /media/Movies (le chemin dans le conteneur, pas le chemin sur l’hôte), puis recommencez avec Shows pour /media/Shows. Terminez l’assistant : Jellyfin lance alors l’indexation. Pour une petite bibliothèque, le résultat attendu est l’affichage des affiches et des titres en une ou deux minutes. Vous pourrez ajouter ou modifier les bibliothèques plus tard dans Dashboard → Libraries, puis forcer une nouvelle analyse avec Scan All Libraries. Ces affiches sont également ce qui rend un frontend alternatif intéressant une fois les bases opérationnelles, puisque Halcyon reconstitue la même bibliothèque sous la forme d’un vidéoclub des années 90 dans lequel vous pouvez vous déplacer à partir des illustrations déjà récupérées par Jellyfin.
Si vous utilisez le transcodage, même occasionnellement, ouvrez Dashboard → Playback → Transcoding et définissez le chemin temporaire du transcodage sur /cache/transcodes afin que les fichiers temporaires soient écrits sur le volume de cache au lieu de remplir /config. Laissez l’accélération matérielle définie sur None : aucun GPU n’est disponible pour l’accélérer.
Accès distant : reverse proxy TLS ou accès via le VPN
Vous avez deux méthodes sûres pour accéder à Jellyfin depuis l’extérieur, et une méthode à éviter. La méthode dangereuse consiste à publier directement le port 8096 sur Internet : l’authentification circule en clair et le port subit des attaques par force brute en quelques heures.
Option A, reverse proxy TLS. Placez Jellyfin derrière un sous-domaine géré par Traefik avec TLS automatique pour vos applications Docker, ou derrière nginx avec un certificat Let's Encrypt délivré par Certbot. Jellyfin utilise des WebSockets pour les mises à jour en temps réel. Le proxy doit donc transmettre les en-têtes de mise à niveau. Traefik le fait automatiquement. Avec nginx, ces en-têtes doivent être indiqués explicitement. La connexion vers l’upstream doit également utiliser HTTP/1.1, sinon la mise à niveau n’a pas lieu :
location / {
proxy_pass http://127.0.0.1:8096;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}Définissez JELLYFIN_PublishedServerUrl sur l’adresse https:// afin que la découverte automatique locale annonce la bonne URL et que les applications distantes utilisent l’adresse que vous leur fournissez. Ajoutez ensuite fail2ban pour ralentir les tentatives de force brute contre l’authentification. Une fois le serveur public, configurez Uptime Kuma avec cette URL afin d’être informé d’une interruption avant vos utilisateurs. Configurez ses notifications vers un serveur ntfy auto-hébergé : un contrôle en échec arrive alors sous forme de notification push sur votre téléphone, au lieu d’un e-mail que vous ne lirez que le lendemain matin. L’ajout d’une authentification sur Internet est également un bon moment pour auditer le reste du serveur. Ne partez pas du principe que seul le port 443 répond. open-kritt exécute cette analyse depuis son propre conteneur sur le même VPS.
Option B, conserver un accès privé via un VPN. Ne publiez pas du tout le port 8096. Accédez à Jellyfin uniquement par un tunnel WireGuard qui se termine sur le même serveur. Pour un usage domestique, c’est le choix sécurisé le plus simple : pas de certificat, aucune exposition publique et aucune surface d’attaque par force brute. Liez le conteneur à l’adresse du tunnel ou à localhost, puis connectez-vous via le VPN. Consultez le guide de configuration d’un VPN WireGuard pour un VPS privé pour configurer le tunnel.
Dimensionnement du stockage et sauvegardes
Dimensionnez selon la qualité, pas selon le nombre de fichiers. Les films 1080p compressés occupent 4-15 GB chacun ; un remux 1080p occupe 20-40 GB ; une saison de séries TV en 1080p occupe 15-40 GB ; en 4K, chaque film occupe 40-100 GB. Une bibliothèque de quelques centaines de films avec quelques séries nécessite un volume de 2-4 TB. Il est moins coûteux de surdimensionner le volume bloc une fois que de migrer les données plus tard.
/config contient l’état complet du serveur. C’est donc le seul élément que vous devez sauvegarder. Prenez un snapshot, ou arrêtez le service puis créez une archive tar, et conservez la copie hors du serveur :
docker compose down
sudo tar czf jellyfin-config-$(date +%F).tgz -C ~/jellyfin config
docker compose up -d/cache et le dossier de transcodage sont jetables. Sauvegardez séparément les médias de /mnt/media, ou acceptez de devoir les réextraire. La plupart des utilisateurs choisissent cette dernière option en raison du volume de données. Les mises à niveau utilisent docker compose pull && docker compose up -d ; la balise :10 ci-dessus reste dans la version majeure 10.x. Le passage à la version majeure suivante nécessite donc de modifier délibérément la balise. Consultez les notes de version de Jellyfin avant cette modification, car les migrations du schéma de bibliothèque ont lieu lors des changements de version majeure. Une balise figée et un répertoire d’état sauvegardé constituent toute la configuration nécessaire pour un conteneur toujours actif. C’est également le principe utilisé pour conserver la mémoire et les tâches planifiées d’un agent auto-hébergé après les redémarrages.
Modes de défaillance et messages affichés
La bibliothèque est vide après une analyse. Le journal accessible dans Dashboard → Logs (ou ~/jellyfin/config/log/log_*.log) affiche :
System.UnauthorizedAccessException: Access to the path '/media/Movies' is denied.L’uid du conteneur ne peut pas lire ce chemin. Cause : les fichiers multimédias appartiennent à root ou à un uid différent de la valeur user:, un répertoire n’a pas le bit d’exécution, ou le point de montage parent n’est pas traversable par cet uid. Correctif : chown -R 1000:1000 /mnt/media, répertoires 755, fichiers 644, puis relancez l’analyse.
La lecture sature le CPU et se met en mémoire tampon. docker stats jellyfin affiche une utilisation du CPU proche de 100% multipliée par le nombre de cœurs, et Dashboard → Playback indique que la session est Transcode avec une vitesse inférieure à 1.0x. Le client n’utilise pas la lecture directe. Le VPS effectue un transcodage CPU plus lent que le temps réel et ne suit plus. Cause : codec ou conteneur non pris en charge, incrustation des sous-titres, ou tone mapping HDR. Correctif : utilisez un client compatible avec la lecture directe, conservez les sources en H.264/AAC, utilisez des sous-titres textuels (SRT) plutôt que des sous-titres image (PGS/VOBSUB), qui imposent une incrustation, et désactivez complètement la lecture 4K HDR sur une machine limitée au CPU.
« Aucun flux compatible n’est disponible. » Le message complet est généralement « Ce client n’est pas compatible avec ce média et le serveur n’envoie pas de format multimédia compatible. » Le client a rejeté la source et le transcodage de secours n’a pas démarré. Cause : commande ffmpeg incorrecte, fichier illisible, ou profil utilisateur qui interdit la conversion vidéo. Correctif : lisez la ligne ffmpeg dans Dashboard → Logs, vérifiez que le fichier peut être lu, contrôlez les permissions de lecture de l’utilisateur si vous dépendez du transcodage, puis essayez un second client pour écarter un problème de codec du navigateur.
Les films n’ont pas d’affiche ou utilisent la mauvaise affiche. Les métadonnées ne correspondent pas. Cause : film qui n’est pas dans son propre répertoire Name (Year), répertoire de saison nommé S01 au lieu de Season 01, épisodes qui ne suivent pas le format S01E01, ou année absente. Correctif : renommez les éléments selon l’organisation indiquée ci-dessus, puis sélectionnez Refresh metadata → Replace all, ou utilisez Identify sur un seul élément pour sélectionner la bonne entrée TMDB/TVDB.
FAQ
Un VPS peut-il transcoder des vidéos sans GPU ?
Oui, mais uniquement sur le CPU, et le coût en ressources est élevé. Un seul transcodage logiciel en 1080p peut saturer plusieurs vCPU, tandis que la 4K ou le HEVC ne permettent généralement pas de suivre le temps réel : la lecture est alors mise en mémoire tampon. La meilleure solution consiste à éviter le transcodage : conservez votre bibliothèque en H.264/AAC et utilisez des applications clientes compatibles avec la lecture directe (« direct play »), afin que le VPS se contente de transmettre les données. Louez une instance avec GPU uniquement si vous avez réellement besoin d’un transcodage à la volée.
Pourquoi ma bibliothèque Jellyfin est-elle vide après une analyse ?
Dans presque tous les cas, le problème vient des permissions. L’image officielle jellyfin/jellyfin s’exécute avec l’utilisateur défini par user: (ou avec root) et, si cet uid ne peut pas lire les fichiers, l’analyse journalise Access to the path ... is denied et les ignore. Corrigez le propriétaire avec chown -R 1000:1000 /mnt/media, ajoutez le bit d’exécution aux répertoires (755), puis relancez l’analyse. Vérifiez également le répertoire parent : si l’uid du conteneur ne peut pas parcourir /mnt/media lui-même, il n’atteint jamais les répertoires de la bibliothèque et celle-ci reste vide. L’autre cause fréquente est une arborescence qui ne respecte pas les attentes de Jellyfin.
Comment accéder à Jellyfin à distance en toute sécurité ?
Deux options sont adaptées. Placez-le derrière un reverse proxy TLS sur un sous-domaine afin de chiffrer l’authentification et le flux, puis ajoutez fail2ban. N’exposez jamais le port 8096 en clair : votre mot de passe y serait transmis sans chiffrement. Vous pouvez aussi le garder entièrement privé et y accéder uniquement via un VPN, ce qui constitue le choix sécurisé le plus simple pour un usage familial. Configurez directement l’adresse publique dans les applications : l’autodiscovery repose sur une diffusion sur le réseau local et n’atteint donc pas les clients qui se connectent depuis Internet.
De quelle capacité disque et de quelle bande passante un VPS Jellyfin a-t-il besoin ?
La capacité disque dépend de la qualité : prévoyez 4-15 GB par film 1080p compressé, 20-40 GB par remux et 40-100 GB pour la 4K. La plupart des bibliothèques nécessitent donc un volume de blocs de 2-4 TB. La bande passante dépend du débit de la lecture directe : 8-12 Mbps par flux 1080p, et bien davantage en 4K. Vérifiez que le débit de votre port supporte le nombre de spectateurs simultanés et surveillez le quota mensuel de transfert. Ajoutez une marge de CPU si vous prévoyez de transcoder ; privilégiez la bande passante plutôt que le nombre de cœurs si vous prévoyez la lecture directe.
Est-il légal d’exécuter Jellyfin sur un VPS ?
Jellyfin est un logiciel libre et open source, et son exécution est parfaitement légale. Ce qui compte, c’est le contenu : diffusez uniquement des médias dont vous êtes propriétaire ou que vous êtes autorisé à détenir, comme vos propres copies de disques, vos enregistrements ou les fichiers pour lesquels vous disposez des droits nécessaires. Jellyfin ne fournit aucun média et aucun moyen d’en obtenir ; il sert de lecteur pour une bibliothèque que vous possédez déjà.