Jellyfin sur un VPS : diffusez vos médias
Faites tourner Jellyfin dans Docker sur un VPS pour diffuser votre bibliothèque : stockage, permissions, direct play ou transcodage CPU, accès distant sûr.
Ce que vous allez construire
Un serveur multimédia Jellyfin sur un VPS : un conteneur, trois volumes et un disque de stockage en bloc qui héberge vos films et vos séries, accessible depuis n'importe quel navigateur ou application Jellyfin. L'installation tient dans un fichier compose de quinze lignes. Tout ce qui se casse ensuite vient de deux endroits : les permissions de fichiers que le conteneur ne peut pas lire, et le fait de demander à un VPS sans GPU de transcoder une vidéo qu'il n'a aucune raison de transcoder. Ce guide consacre l'essentiel de sa longueur à ces deux points, parce que c'est là que se trouvent les tickets de support.
Jellyfin est gratuit et entièrement open source, sans compte, sans fonctionnalités payantes et sans télémétrie, ce qui explique qu'il figure sur presque toutes les listes de choses qui valent la peine d'être auto-hébergées en 2026. Il lit les médias que vous possédez. Il ne fournit aucun contenu, et ce guide ne parle pas d'en obtenir.
La réalité du transcodage, avant de louer quoi que ce soit
Lisez ceci en premier, parce que cela change ce que vous achetez. Un serveur multimédia fait l'une de deux choses quand vous appuyez sur lecture. Le direct play diffuse le fichier tel quel : le VPS lit les octets sur le disque et les envoie sur le réseau, pour un coût CPU presque nul. Le transcodage réencode la vidéo à la volée (nouvelle résolution, nouveau codec, ou sous-titres incrustés), et c'est un travail purement CPU.
Un VPS classique n'a pas de GPU. Chaque transcodage tourne donc sur le CPU avec libx264/libx265, et l'encodage logiciel coûte cher. Un seul transcodage 1080p H.264 peut saturer plusieurs vCPU partagés ; un transcodage 4K ou HEVC n'arrive en général pas du tout à suivre le temps réel, si bien que la lecture se bloque et met en mémoire tampon sans fin. Le transcodage matériel, ce qui rend l'opération peu coûteuse sur une machine domestique avec un iGPU Intel ou une carte Nvidia, ne vous est tout simplement pas accessible sauf si votre fournisseur loue des instances GPU.
Toute la stratégie sur un VPS consiste donc à éviter le transcodage. Gardez 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) et choisissez des applications clientes qui font du direct play : les applications Jellyfin natives pour Android TV, iOS et Roku, ainsi qu'Infuse, Kodi et le Jellyfin Media Player de bureau. Faites cela et le VPS ne touche jamais à ffmpeg, et une modeste machine à 2 vCPU diffuse vers plusieurs personnes à la fois. Prévoyez de transcoder et il vous faut une machine bien plus grosse et plus chère, et même là, la 4K est un mauvais pari.
Faites aussi le calcul de la bande passante, parce que c'est l'autre surprise. Le direct play envoie le fichier à son propre débit. Un fichier 1080p compressé tourne à 8-12 Mbps ; un remux Blu-ray 1080p à 20-30 Mbps ; de la 4K HDR à 40-80 Mbps. Trois personnes qui lisent en direct play des fichiers à 10 Mbps, cela fait 30 Mbps d'upload soutenu depuis votre VPS. Vérifiez deux chiffres sur votre offre : la vitesse du port (peut-il pousser 30 Mbps en montée ?) et le plafond de transfert mensuel. Un film de deux heures à 10 Mbps représente environ 9 Go en sortie, donc un forfait mesuré à 1 To/mois correspond à un peu plus d'une centaine de films de ce type par mois, trois ou quatre par jour, et un foyer qui regarde de la 4K, à quatre à huit fois le débit, l'épuise bien plus vite.
Prérequis
- Un VPS Ubuntu 24.04 KVM tout neuf avec root ou sudo, et Docker plus le plugin Compose installés.
- Un volume de stockage en bloc pour les médias, dimensionné pour votre bibliothèque (voir le dimensionnement plus bas). Le petit disque root livré avec un VPS n'est pas l'endroit où vont 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 garder l'ensemble privé.
- Des médias que vous avez légalement le droit de diffuser : vos propres rips, vos propres enregistrements, des fichiers que vous possédez.
Montez d'abord le stockage en bloc
Attachez le volume dans le panneau de votre fournisseur, puis repérez-le et montez-le. Récupérez le nom du périphérique avec lsblk : ce sera quelque chose comme /dev/sdb ou /dev/vdb, jamais le 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 par UUID, pas par /dev/sdb, parce que les lettres de périphérique changent d'ordre entre les redémarrages et vous pourriez finir par 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 a son importance : sans lui, si le volume en bloc est un jour détaché, la machine refuse de démarrer et bascule sur un shell d'urgence. La plus grosse erreur ici est de lancer mkfs.ext4 sur un volume qui contient déjà des données, ce qui l'efface. Ne formatez que les volumes neufs ; si le disque contient déjà votre bibliothèque, passez directement à la ligne fstab.
Organisez les médias comme Jellyfin l'attend
Jellyfin fait correspondre les métadonnées par noms de dossiers et de fichiers. Si l'organisation est mauvaise, les films arrivent sous forme de fichiers sans titre et sans affiche, ou un épisode est associé à la mauvaise série. Il y a exactement trois règles : chaque film vit dans son propre dossier Name (Year) avec un nom de fichier assorti ; les dossiers de saison s'appellent Season 01, pas 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) sur les films n'est pas décoratif : il lève l'ambiguïté sur les remakes pour que l'outil de correspondance récupère le bon titre. Gardez Movies et Shows comme dossiers séparés de premier niveau, parce que chacun devient une bibliothèque Jellyfin d'un type de contenu précis, et les mélanger perturbe le fournisseur de métadonnées.
Permissions : la première cause de bibliothèques vides
Voici l'idée fausse qui coûte une soirée aux gens. L'image officielle jellyfin/jellyfin ne respecte pas les variables d'environnement PUID/PGID : celles-ci appartiennent à l'image LinuxServer.io (lscr.io/linuxserver/jellyfin). Sur l'image officielle, vous contrôlez l'utilisateur avec la clé user: dans compose, et si vous l'omettez, le conteneur tourne en tant que root. Quelle que soit celle que vous utilisez, la règle est la même : l'uid/gid sous lequel tourne le conteneur doit pouvoir lire et traverser chaque répertoire de médias.
Nous tournerons en uid/gid 1000, le premier utilisateur non-root sur une machine Ubuntu standard. Confirmez le vôtre et définissez la propriété :
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 ont besoin du bit execute (le x de 755), pas seulement de lecture : sans lui, le conteneur ne peut pas entrer dans le dossier même s'il peut en lister le nom. Le piège qui vide toute une bibliothèque est le parent : si l'uid du conteneur ne peut pas traverser le point de montage lui-même, il n'atteint jamais /media/Movies ou /media/Shows, et toutes les bibliothèques deviennent vides d'un coup avec Access to the path ... is denied dans le journal. Tout dossier de médias qu'il ne peut pas lire est journalisé et ignoré, si bien qu'un lot de fichiers copiés en tant que root disparaît silencieusement de la bibliothèque. C'est pourquoi nous faisons un chown récursif et mettons le bit execute sur chaque répertoire plutôt que 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\" est ce qui fixe réellement les permissions de fichiers, en accord avec la propriété ci-dessus. /config contient tout le serveur (comptes, bibliothèques, métadonnées, état de visionnage), il doit donc être accessible en écriture et c'est ce que vous sauvegardez. /cache est un espace de travail jetable. Le montage des médias est en :ro (lecture seule) à dessein : Jellyfin stocke par défaut les visuels et les métadonnées sous /config, il n'a donc jamais besoin d'écrire dans votre bibliothèque, et la lecture seule protège vos fichiers d'une suppression accidentelle ou d'un plugin défectueux. Le port est lié à 127.0.0.1 délibérément : la connexion web de Jellyfin est en HTTP simple, on ne publie donc jamais 8096 sur l'internet public. JELLYFIN_PublishedServerUrl est l'adresse que le serveur annonce pour l'autodécouverte locale, une diffusion UDP sur le LAN, si bien que les clients sur internet ne la voient jamais et utilisent simplement l'URL que vous tapez dans l'application. Réglez-la sur l'adresse à communiquer aux clients, et attendez-vous à saisir cette URL à la main sur les appareils distants.
Lancez-le depuis le répertoire compose :
docker compose up -d
docker logs -f jellyfinPremier lancement : l'assistant de configuration et vos bibliothèques
Comme le port est lié à localhost, atteignez l'assistant via un tunnel SSH depuis votre ordinateur portable plutôt que d'ouvrir un trou dans le pare-feu :
ssh -L 8096:127.0.0.1:8096 you@your-vps-ipMaintenant, ouvrez http://localhost:8096. L'assistant vous guide pour la langue, puis la création d'un utilisateur admin avec un mot de passe fort : ce compte est votre serveur, ne réutilisez donc pas un mot de passe jetable. Ajoutez votre première bibliothèque : choisissez le type de contenu Movies, pointez-la vers /media/Movies (le chemin à l'intérieur du conteneur, pas le chemin de l'hôte), et recommencez avec Shows vers /media/Shows. Terminez, et Jellyfin lance l'analyse. Un résultat correct, ce sont les affiches et les titres qui se remplissent en une minute ou deux pour une petite bibliothèque. Ajoutez ou modifiez les bibliothèques plus tard dans Dashboard → Libraries, et forcez une nouvelle analyse avec Scan All Libraries.
Si vous dépendez du moindre transcodage, ouvrez Dashboard → Playback → Transcoding et réglez le chemin temporaire de transcodage sur /cache/transcodes pour que l'activité atterrisse sur le volume cache au lieu de gonfler /config. Laissez l'accélération matérielle sur None : il n'y a pas de GPU pour accélérer.
Accès distant : proxy inverse TLS, ou tout garder sur le VPN
Vous avez deux façons sûres d'atteindre Jellyfin depuis l'extérieur, et une façon dangereuse à éviter. La façon dangereuse consiste à publier le port 8096 directement sur internet : la connexion voyage en clair et le port se fait attaquer par force brute en quelques heures.
Option A : proxy inverse TLS. Placez Jellyfin sur un sous-domaine derrière Traefik avec TLS automatique pour vos applications Docker, ou derrière nginx avec un certificat Let's Encrypt émis par Certbot. Jellyfin utilise les WebSockets pour les mises à jour en temps réel, le proxy doit donc transmettre les en-têtes d'upgrade. Traefik le fait automatiquement ; nginx a besoin qu'on les précise, et a besoin de HTTP/1.1 vers l'amont sinon l'upgrade n'a jamais 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";
}Réglez JELLYFIN_PublishedServerUrl sur l'adresse https:// pour que toute autodécouverte locale annonce la bonne URL (les applications distantes utilisent l'adresse que vous leur donnez), et ajoutez Fail2ban pour ralentir les tentatives de force brute contre la connexion. Une fois le serveur public, pointez Uptime Kuma sur l'URL pour être au courant des pannes avant vos spectateurs.
Option B : gardez-le privé sur un VPN. Ne publiez pas du tout 8096 ; atteignez Jellyfin uniquement via un tunnel WireGuard qui se termine sur la même machine. Pour un foyer, c'est le choix sûr le plus simple : pas de certificat, pas d'exposition publique, pas de surface de force brute. Liez le conteneur à l'adresse du tunnel ou à localhost et connectez-vous par le VPN. Voir la configuration du VPN WireGuard pour un VPS privé pour le tunnel lui-même.
Dimensionnement du stockage et sauvegardes
Budgétez par qualité, pas par nombre de fichiers. Les films 1080p compressés font 4-15 Go chacun ; un remux 1080p 20-40 Go ; une saison de série 1080p 15-40 Go ; n'importe quoi en 4K fait 40-100 Go par film. Une bibliothèque de quelques centaines de films plus quelques séries réclame un volume de 2-4 To, et il revient moins cher de surdimensionner le volume en bloc une fois que de migrer plus tard.
/config est l'intégralité de l'état du serveur, c'est donc la seule chose que vous devez sauvegarder. Faites-en un instantané, ou arrêtez et archivez-le avec tar, et gardez la copie hors de la machine :
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. Les médias sur /mnt/media, vous les sauvegardez séparément ou vous les acceptez comme réextractibles ; la plupart des gens choisissent la seconde option vu la taille. Les mises à jour se font avec docker compose pull && docker compose up -d ; le tag :10 ci-dessus reste dans la version majeure 10.x, donc passer à la majeure suivante est une modification de tag délibérée : parcourez les notes de version de Jellyfin avant de la faire, parce que les migrations de schéma de bibliothèque se produisent sur les versions majeures.
Modes de défaillance, avec les messages que vous verrez
La bibliothèque est vide après une analyse. Le journal 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 : des médias appartenant à root ou à un uid autre que votre valeur user:, un répertoire sans son bit execute, ou le point de montage parent lui-même non traversable par cet uid. Solution : chown -R 1000:1000 /mnt/media, répertoires 755, fichiers 644, puis relancez l'analyse.
La lecture sature le CPU et met en mémoire tampon. docker stats jellyfin montre un CPU proche de 100 % multiplié par votre nombre de cœurs, et Dashboard → Playback liste la session comme Transcode avec une vitesse inférieure à 1.0x. Le client ne fait pas de direct play, le VPS transcode donc sur CPU plus lentement que le temps réel et perd la course. Cause : un codec ou conteneur non pris en charge, une incrustation de sous-titres, ou un tone-mapping HDR. Solution : passez à un client en direct play, gardez les sources en H.264/AAC, utilisez des sous-titres texte (SRT) plutôt que des sous-titres image (PGS/VOBSUB) qui forcent une incrustation, et gardez la 4K HDR complètement à l'écart d'une machine sans GPU.
« No compatible streams are available. » Le message complet est en général « This client isn't compatible with the media and the server isn't sending a compatible media format. » Le client a rejeté la source et le transcodage de repli n'a pas non plus réussi à démarrer. Cause : une commande ffmpeg cassée, un fichier illisible, ou le profil de l'utilisateur qui bloque la conversion vidéo. Solution : lisez la ligne ffmpeg dans Dashboard → Logs, confirmez que le fichier se lit tout court, vérifiez les permissions de lecture de l'utilisateur si vous dépendez du transcodage, et essayez un second client pour écarter les caprices de codec du navigateur.
Les films n'ont pas d'affiche, ou la mauvaise. Les métadonnées n'ont pas correspondu. Cause : un film qui n'est pas dans son propre dossier Name (Year), un dossier de saison nommé S01 au lieu de Season 01, des épisodes qui ne sont pas au format S01E01, ou une année manquante. Solution : renommez selon l'organisation ci-dessus, puis Refresh metadata → Replace all, ou utilisez Identify sur un seul élément pour fixer la bonne entrée TMDB/TVDB.
FAQ
Un VPS peut-il transcoder de la vidéo sans GPU ?
Oui, mais sur le CPU uniquement, et cela coûte cher. Un seul transcodage logiciel 1080p peut saturer plusieurs vCPU, et la 4K ou le HEVC n'arrivent en général pas à suivre le temps réel, donc la lecture met en mémoire tampon. Le bon réflexe est d'éviter le transcodage : gardez votre bibliothèque en H.264/AAC et utilisez des applications clientes qui font du direct play, pour que le VPS ne fasse que diffuser des octets. Ne louez une instance GPU que si vous avez réellement besoin de transcodage à la volée.
Pourquoi ma bibliothèque Jellyfin est-elle vide après une analyse ?
Presque toujours les permissions. L'image officielle jellyfin/jellyfin tourne sous le user: que vous définissez (ou root), et si les fichiers ne sont pas lisibles par cet uid, l'analyse journalise Access to the path ... is denied et les ignore. Corrigez la propriété avec chown -R 1000:1000 /mnt/media, donnez aux répertoires le bit execute (755), et relancez l'analyse ; et vérifiez aussi le parent, parce que si l'uid du conteneur ne peut pas traverser /mnt/media lui-même, il n'atteint jamais les dossiers de bibliothèque et tout devient vide. La deuxième cause la plus fréquente est une organisation de dossiers qui ne correspond pas à ce qu'attend Jellyfin.
Comment accéder à Jellyfin à distance et en toute sécurité ?
Deux bonnes options. Placez-le derrière un proxy inverse TLS sur un sous-domaine pour que la connexion et le flux soient chiffrés, et ajoutez Fail2ban ; n'exposez jamais le port 8096 en clair, qui envoie votre mot de passe en clair. Ou gardez-le entièrement privé et atteignez-le uniquement via un VPN, le choix sûr le plus simple pour un foyer. Donnez directement l'adresse publique aux applications : l'autodécouverte est une diffusion sur le réseau local, elle n'atteint donc pas les clients qui arrivent par internet.
De combien de disque et de bande passante un VPS Jellyfin a-t-il besoin ?
Le disque dépend de la qualité : comptez 4-15 Go par film 1080p compressé, 20-40 Go par remux, et 40-100 Go pour la 4K, donc la plupart des bibliothèques réclament un volume en bloc de 2-4 To. La bande passante est fixée par le débit du direct play (8-12 Mbps par flux 1080p, bien plus pour la 4K), confirmez donc que la vitesse de votre port gère le nombre de spectateurs simultanés et surveillez le plafond de transfert mensuel. Ajoutez de la marge CPU si vous prévoyez de transcoder ; privilégiez la bande passante sur les cœurs si vous prévoyez de faire du direct play.
Est-il légal de faire tourner Jellyfin sur un VPS ?
Jellyfin lui-même est un logiciel gratuit et open source, et le faire tourner est tout à fait légal. Ce qui compte, c'est le contenu : ne diffusez que des médias que vous possédez ou que vous êtes autorisé à détenir, vos propres rips de disques, enregistrements, ou fichiers auxquels vous avez droit. Jellyfin ne fournit aucun média et ne donne aucun moyen d'en obtenir ; c'est un lecteur pour une bibliothèque que vous possédez déjà.