SSD Nodes Learn Hosting plans →
Guide Matt ConnorDi Matt Connor

Jellyfin su VPS: streaming della tua libreria

Installa Jellyfin in Docker su un VPS: configura block storage e permessi, capisci Direct Play e transcodifica CPU, e proteggi l'accesso remoto.

Cosa stai configurando

Un media server Jellyfin su un VPS: un container, tre volumi e un disco di block storage che contiene film e serie, accessibile da qualsiasi browser o app Jellyfin. L'installazione richiede un file compose di quindici righe. Quasi tutti i problemi successivi dipendono da due cause: permessi sui file che impediscono al container di leggerli e richieste di transcodifica video a un VPS privo di GPU. Questo tutorial dedica la maggior parte dello spazio a questi due aspetti, perché è qui che si concentrano le richieste di supporto.

Jellyfin è gratuito e completamente open source. Non richiede un account, non include funzionalità a pagamento e non raccoglie dati di telemetria. Per questo compare in quasi tutti gli elenchi di progetti che vale la pena ospitare in autonomia nel 2026. Riproduce i contenuti multimediali che possiedi. Non fornisce alcun contenuto e questo tutorial non tratta alcun metodo per procurarsene.

La realtà della transcodifica, prima di noleggiare qualsiasi cosa

Leggete prima questa sezione, perché cambia ciò che dovete acquistare. Un media server esegue una di due operazioni quando avviate la riproduzione. La riproduzione diretta trasmette il file così com'è: il VPS legge i byte dal disco e li invia sulla rete, usando una quantità minima di CPU. La transcodifica ricodifica il video in tempo reale, ad esempio cambiando risoluzione o codec oppure incorporando i sottotitoli nell'immagine, e richiede esclusivamente lavoro della CPU.

Un VPS tipico non dispone di una GPU. Ogni transcodifica video viene quindi eseguita sulla CPU con libx264/libx265, e la codifica software è onerosa. Una singola transcodifica 1080p H.264 può saturare diversi vCPU condivisi; una transcodifica 4K o HEVC in genere non riesce nemmeno a mantenere la velocità di riproduzione in tempo reale, quindi la riproduzione si interrompe e il buffering continua senza fine. La transcodifica hardware, che rende economica questa operazione su un computer domestico con una iGPU Intel o una scheda Nvidia, non è semplicemente disponibile, a meno che il provider non offra istanze GPU.

Su un VPS, quindi, la strategia consiste nell'evitare la transcodifica. Conservate la libreria in codec riprodotti nativamente dai client: video H.264, audio AAC o AC3, in un contenitore MP4 o MKV. Scegliete applicazioni client che supportano la riproduzione diretta: le applicazioni Jellyfin native per Android TV, iOS e Roku, oltre a Infuse, Kodi e Jellyfin Media Player per desktop. In questo modo il VPS non deve mai eseguire una transcodifica ffmpeg durante la riproduzione e un server con 2 vCPU può trasmettere contenuti a più persone contemporaneamente. Se prevedete di eseguire transcodifiche, vi serve un server molto più potente e costoso; anche in quel caso, il 4K rimane una scelta poco realistica.

Calcolate anche la larghezza di banda, perché è l'altra sorpresa. La riproduzione diretta invia il file alla sua velocità in bit. Un file 1080p compresso utilizza 8-12 Mbps; un remux Blu-ray 1080p utilizza 20-30 Mbps; un contenuto 4K HDR utilizza 40-80 Mbps. Tre persone che riproducono direttamente file a 10 Mbps generano 30 Mbps di traffico in uscita continuo dal VPS. Verificate due valori nel vostro piano: la velocità della porta, cioè se può trasmettere 30 Mbps in upstream, e il limite mensile di trasferimento. Un film di due ore a 10 Mbps genera circa 9 GB di traffico in uscita, quindi una disponibilità a consumo di 1 TB/mese consente poco più di cento film di questo tipo al mese, vale a dire tre o quattro al giorno. Un'intera famiglia che guarda contenuti 4K, con un bitrate da quattro a otto volte superiore, esaurisce il limite molto più rapidamente. Considerate nello stesso budget anche tutto il traffico in uscita generato da altri servizi sullo stesso server, incluso un relay RustDesk self-hosted, che trasporta un'intera sessione desktop remota quando due peer non riescono a connettersi direttamente.

Prerequisiti

  • Un VPS KVM Ubuntu 24.04 nuovo, con accesso root o sudo, Docker e il plugin Compose installati.
  • Un volume di block storage per i contenuti multimediali, dimensionato in base alla libreria (vedere la sezione sul dimensionamento). Il piccolo disco root fornito con il VPS non è destinato ai film.
  • Un nome di dominio se si desidera l'accesso HTTPS pubblico, oppure una VPN WireGuard sullo stesso VPS se si preferisce mantenere tutto privato.
  • Contenuti multimediali che si è legalmente autorizzati a trasmettere in streaming: proprie copie, proprie registrazioni e file di cui si detiene la proprietà.

Montare prima lo storage a blocchi

Collega il volume dal pannello del provider, quindi individualo e montalo. Ricava il nome del dispositivo da lsblk. Sarà simile a /dev/sdb o /dev/vdb, mai al disco 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 device

Montalo tramite UUID, non tramite /dev/sdb, perché le lettere dei dispositivi possono cambiare dopo i riavvii e potresti formattare o montare il disco errato. Aggiungi una riga a /etc/fstab:

UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx  /mnt/media  ext4  defaults,nofail  0  2
sudo mount -a
df -h /mnt/media

nofail è importante: senza questa opzione, se il volume a blocchi viene scollegato, il server non riesce ad avviarsi e passa a una shell di emergenza. L'errore più grave consiste nell'eseguire mkfs.ext4 su un volume che contiene già dati: il comando li cancella. Formatta solo i volumi nuovi. Se il disco contiene già la tua libreria, passa direttamente alla riga di fstab.

Organizzare i contenuti nel formato previsto da Jellyfin

Jellyfin associa i metadati ai nomi di cartelle e file. Se la struttura è errata, i film vengono visualizzati come file senza titolo e senza locandina oppure un episodio viene associato alla serie sbagliata. Quattro convenzioni coprono quasi tutti i casi: ogni film deve trovarsi nella propria cartella, con un nome file corrispondente, denominato Name (Year) (Jellyfin considera l'anno facoltativo, ma inserirlo rende l'associazione molto più affidabile); le cartelle delle stagioni devono essere denominate Season 01, non S01; i file degli episodi devono usare S01E01; gli speciali devono trovarsi in 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.mkv

Il (Year) dei film non è decorativo: distingue i remake e consente al sistema di associazione di selezionare il titolo corretto. Mantieni Movies e Shows come cartelle di primo livello separate, perché ciascuna diventa una libreria Jellyfin dedicata a un tipo specifico di contenuti; mescolarle confonde il provider dei metadati. Jellyfin indicizza senza problemi anche una terza cartella di foto, ma l'esperienza è limitata rispetto a quella offerta da un server fotografico dedicato. Se i tuoi album sono importanti, assegna loro un server separato con PhotoPrism o Immich e usa questo server solo per film e serie TV.

Permessi: il motivo principale per cui le librerie risultano vuote

Questo è l'equivoco che può costare un'intera serata. L'immagine ufficiale jellyfin/jellyfin non riconosce le variabili d'ambiente PUID/PGID, che appartengono all'immagine LinuxServer.io (lscr.io/linuxserver/jellyfin). Nell'immagine ufficiale si controlla l'utente con la chiave user: in compose; se la si omette, il container viene eseguito come root. In entrambi i casi, la regola è la stessa: lo uid/gid con cui viene eseguito il container deve poter leggere e attraversare ogni directory dei contenuti multimediali.

Eseguiremo il container con uid/gid 1000, il primo utente non root in un'installazione standard di Ubuntu. Verifica il tuo uid/gid e imposta il proprietario:

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 ~/jellyfin

Le directory richiedono il bit execute (il x in 755), non soltanto il permesso di lettura. Senza questo bit, il container non può entrare nella directory, anche se può elencarne il nome. Il problema che svuota un'intera libreria è la directory padre: se lo uid del container non può attraversare il mount stesso, non raggiunge mai /media/Movies o /media/Shows e tutte le librerie risultano immediatamente vuote, con Access to the path ... is denied nel log. Qualsiasi singola directory multimediale che il container non può leggere viene registrata nel log e ignorata. Di conseguenza, un gruppo di file copiati come root scompare dalla libreria senza messaggi evidenti. Per questo modifichiamo ricorsivamente il proprietario e impostiamo il bit execute su ogni directory, invece di correggere una sola cartella.

Il file docker-compose

services:
  jellyfin:
    image: jellyfin/jellyfin:12
    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.com

Riga per riga: jellyfin/jellyfin:12 segue il ramo di release 12.x corrente (12.2 al 2026-10-10). user: "1000:1000" imposta uid/gid con cui viene eseguito il processo Jellyfin. Non modifica il proprietario né i permessi di alcun file, perché Docker mantiene uid/gid dell'host sulle directory montate. Per questo chown deve corrispondere a quei valori. /config contiene l'intero server, gli account, le librerie, i metadati e lo stato di visione. Deve quindi essere scrivibile ed è il contenuto di cui eseguire il backup. /cache è uno spazio di lavoro temporaneo. Il mount dei contenuti multimediali è :ro (sola lettura) intenzionalmente: Jellyfin salva per impostazione predefinita copertine e metadati in /config, quindi non deve mai scrivere nella libreria. La modalità di sola lettura protegge inoltre i file da eliminazioni accidentali o da plugin difettosi. La porta è associata deliberatamente a 127.0.0.1. L'accesso web di Jellyfin usa HTTP non cifrato, quindi non pubblichiamo mai la porta 8096 su Internet pubblico. JELLYFIN_PublishedServerUrl è l'indirizzo che il server pubblicizza per l'autodiscovery locale tramite broadcast UDP sulla LAN. I client su Internet non lo vedono e usano semplicemente l'URL immesso nell'app. Impostalo sull'indirizzo che deve essere comunicato ai client e prevedi di inserire manualmente questo URL nei dispositivi remoti.

Avvialo dalla directory di compose:

docker compose up -d
docker logs -f jellyfin

Prima esecuzione: procedura guidata di configurazione e librerie

Poiché la porta è associata a localhost, accedi alla procedura guidata tramite un tunnel SSH dal laptop invece di aprire una porta nel firewall:

ssh -L 8096:127.0.0.1:8096 you@your-vps-ip

Ora apri http://localhost:8096 nel browser. La procedura guidata ti guida nella scelta della lingua e nella creazione di un utente amministratore con una password complessa. Questo account consente di amministrare il server, quindi non riutilizzare una password temporanea. Aggiungi la prima libreria: scegli il tipo di contenuto Movies, indica /media/Movies (il percorso all'interno del container, non il percorso sull'host) e ripeti la procedura selezionando Shows e indicando /media/Shows. Completa la configurazione e Jellyfin avvierà la scansione. Per una libreria di piccole dimensioni, il risultato corretto consiste nella comparsa di poster e titoli entro uno o due minuti. In seguito puoi aggiungere o modificare le librerie in Dashboard → Libraries e forzare una nuova scansione con Scan All Libraries. I poster sono anche il motivo per cui vale la pena provare un frontend alternativo dopo aver verificato il funzionamento di base, perché Halcyon ricrea la stessa libreria come un videonoleggio degli anni 90 percorribile usando le immagini che Jellyfin ha già scaricato.

Se utilizzi anche una sola funzione di transcodifica, apri Dashboard → Playback → Transcoding e imposta il percorso temporaneo della transcodifica su /cache/transcodes, in modo che i file temporanei vengano scritti sul volume di cache invece di far crescere /config. Lascia l'accelerazione hardware impostata su None, perché non è disponibile alcuna GPU da utilizzare.

Accesso remoto: reverse proxy TLS oppure accesso solo tramite VPN

Esistono due modalità sicure per raggiungere Jellyfin dall’esterno e una modalità non sicura da evitare. La modalità non sicura consiste nel pubblicare direttamente su Internet la porta 8096: le credenziali viaggiano in chiaro e la porta viene sottoposta a tentativi di forza bruta nel giro di poche ore.

Opzione A: reverse proxy TLS. Pubblica Jellyfin su un sottodominio tramite Traefik con TLS automatico per le applicazioni Docker oppure tramite nginx con un certificato Let's Encrypt emesso da Certbot. Jellyfin usa WebSocket per gli aggiornamenti in tempo reale, quindi il proxy deve inoltrare gli header di upgrade. Traefik lo fa automaticamente; con nginx devi specificare gli header e usare HTTP/1.1 verso il servizio upstream, altrimenti l’upgrade non viene eseguito:

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";
}

Imposta JELLYFIN_PublishedServerUrl sull’indirizzo https://, in modo che l’autodiscovery locale pubblichi l’URL corretto e le applicazioni remote usino l’indirizzo che fornisci. Aggiungi inoltre fail2ban per rallentare i tentativi di forza bruta contro il login. Dopo aver reso pubblico il server, configura Uptime Kuma sull’URL, così ricevi una notifica del downtime prima dei tuoi utenti. Configura le notifiche verso un server ntfy self-hosted: in caso di controllo fallito ricevi una notifica push sul telefono invece di un’email che potresti leggere solo il mattino seguente. Pubblicare un login su Internet è anche un buon momento per verificare il resto del server. Non dare per scontato che sia in ascolto soltanto la porta 443: sudo ss -tlnp elenca tutte le porte in ascolto e il processo associato. Se sul server esegui anche codice sviluppato internamente, open-kritt esegue la scansione dei repository e delle relative dipendenze per individuare vulnerabilità; controlla il codice sorgente, non le porte in ascolto.

Opzione B: mantienilo privato su una VPN. Non pubblicare affatto la porta 8096 su Internet. Raggiungi Jellyfin solo tramite un tunnel WireGuard terminato sullo stesso server. Per un ambiente domestico è la scelta sicura più semplice: non richiede certificati, non espone il servizio pubblicamente e non offre una superficie per i tentativi di forza bruta. Il binding su localhost definito nel file compose non è sufficiente, perché una porta pubblicata su 127.0.0.1 è raggiungibile solo dal VPS stesso, non dai peer della VPN. Pubblicala invece sull’indirizzo del tunnel del server, ad esempio "10.8.0.1:8096:8096" usando l’indirizzo IP del tuo server WireGuard, oppure mantieni il binding su localhost e anteponi un proxy in ascolto sull’indirizzo del tunnel. Collegati quindi tramite la VPN. Per configurare il tunnel, consulta la configurazione di WireGuard VPN per un VPS privato.

Dimensionamento dello storage e backup

Pianifica in base alla qualità, non al numero di file. I film 1080p compressi occupano 4-15 GB ciascuno; un remux 1080p occupa 20-40 GB; una stagione TV in 1080p occupa 15-40 GB; qualsiasi contenuto 4K occupa 40-100 GB per film. Una libreria con alcune centinaia di film e alcune serie richiede un volume da 2-4 TB. È più economico sovradimensionare una volta il volume a blocchi che migrare in seguito.

/config contiene l'intero stato del server, quindi è l'unico elemento di cui devi necessariamente eseguire il backup. Crea uno snapshot oppure arresta il servizio e crea un archivio tar, quindi conserva la copia fuori dal server:

docker compose down
sudo tar czf jellyfin-config-$(date +%F).tgz -C ~/jellyfin config
docker compose up -d

/cache e la cartella di transcodifica sono ricreabili. Esegui il backup dei contenuti multimediali su /mnt/media separatamente oppure considera i supporti nuovamente rippabili; date le dimensioni, la maggior parte degli utenti sceglie la seconda opzione. Gli aggiornamenti sono docker compose pull && docker compose up -d; il tag :12 riportato sopra resta nella serie 12.x, quindi il passaggio alla versione major successiva richiede una modifica intenzionale del tag. Prima di eseguirla, consulta rapidamente le note di rilascio di Jellyfin, perché le migrazioni dello schema della libreria avvengono nelle versioni major. Jellyfin ha rimosso il prefisso "10." dai numeri di versione con la versione 12.0 (settembre 2026): 12.0 corrisponde a quella che sarebbe stata la versione 10.12, non esiste alcuna versione 11.x e 10.11.x è stata l'ultima serie secondo il vecchio schema. Se un tuo vecchio file Compose contiene ancora jellyfin/jellyfin:10, è bloccato sulla versione 10.11 (quel tag è stato aggiornato l'ultima volta il 2026-06-06, alla versione 10.11.11) e non scaricherà mai la versione 12.x. Il passaggio a :12 non consiste soltanto nella modifica del tag: la versione 12.0 modifica lo schema del database e riscrive i dati al primo avvio. Arresta quindi il server ed esegui prima il backup indicato sopra, perché quella copia è l'unico modo per tornare indietro. La versione 12.0 si aggiorna direttamente dalla versione 10.10.7 o da qualsiasi release 10.11.x e, al termine, richiede una scansione completa della libreria, che dura sensibilmente più del normale. Un tag fissato e una directory di stato sottoposta a backup sono l'intera configurazione necessaria per qualsiasi container sempre attivo. È lo stesso modello alla base di mantenere attivi memoria e pianificazioni di un agent self-hosted dopo i riavvii.

Modalità di errore e stringhe visualizzate

La libreria è vuota dopo una scansione. Il log in Dashboard → Logs (oppure ~/jellyfin/config/log/log_*.log) mostra:

System.UnauthorizedAccessException: Access to the path '/media/Movies' is denied.

L’uid del container non può leggere quel percorso. Le cause possono essere file multimediali di proprietà di root o di un uid diverso dal valore user:, una directory priva del bit di esecuzione oppure il mount padre non attraversabile da quell’uid. Correzione: chown -R 1000:1000 /mnt/media, directory 755, file 644, quindi esegui nuovamente la scansione.

La riproduzione porta la CPU al limite e va in buffering. docker stats jellyfin mostra una CPU vicina al 100% moltiplicato per il numero di core e Dashboard → Playback indica la sessione come Transcode, con una velocità inferiore a 1.0x. Il client non sta eseguendo la riproduzione diretta. Il VPS sta eseguendo la transcodifica tramite CPU a una velocità inferiore al tempo reale e non riesce a mantenere il ritmo. Le cause possono essere un codec o un container non supportato, il burn-in dei sottotitoli oppure il tone mapping HDR. Correzione: usa un client con riproduzione diretta, mantieni le sorgenti in H.264/AAC, usa sottotitoli testuali (SRT) invece di sottotitoli immagine (PGS/VOBSUB), che la maggior parte dei client Jellyfin non può visualizzare direttamente e che quindi forza il burn-in, e non riprodurre contenuti 4K HDR su una macchina che dispone soltanto della CPU.

"No compatible streams are available." Il messaggio completo è in genere "This client isn't compatible with the media and the server isn't sending a compatible media format." Il client ha rifiutato la sorgente e anche la transcodifica di fallback non è riuscita ad avviarsi. Le cause possono essere un comando ffmpeg non valido, un file non leggibile oppure il profilo dell’utente che blocca la conversione video. Correzione: leggi la riga ffmpeg in Dashboard → Logs, verifica che il file sia riproducibile, controlla i permessi di riproduzione dell’utente se dipendi dalla transcodifica e prova un secondo client per escludere problemi relativi ai codec del browser.

I film non hanno un poster oppure mostrano quello errato. I metadati non corrispondono al contenuto. Le cause possono essere una directory o un nome file che non corrisponde al titolo indicato dal provider di metadati, un remake o un titolo comune senza l’anno per distinguere le versioni, una directory della stagione denominata S01 invece di Season 01 oppure episodi che non rispettano il formato S01E01. Correzione: rinomina gli elementi secondo la struttura precedente, quindi seleziona Refresh metadata → Replace all, oppure usa Identify su un singolo elemento per associare la voce corretta di TMDB/TVDB.

FAQ

Un VPS può eseguire la transcodifica video senza una GPU?

Sì, ma soltanto sulla CPU, con un costo elevato in termini di risorse. Una singola transcodifica software a 1080p può saturare diverse vCPU, mentre il 4K o HEVC di solito non riesce a mantenere il tempo reale e la riproduzione entra in buffering. La soluzione più efficace è evitare la transcodifica: mantieni la libreria in H.264/AAC e usa applicazioni client che supportano la riproduzione diretta, così il VPS deve soltanto trasferire i dati. Noleggia un'istanza con GPU solo se ti serve realmente la transcodifica durante la riproduzione.

Perché la libreria Jellyfin è vuota dopo una scansione?

Quasi sempre il problema riguarda i permessi. L'immagine ufficiale jellyfin/jellyfin viene eseguita con l'utente impostato in user:, oppure come root. Se quell'uid non può leggere i file, la scansione registra Access to the path ... is denied nei log e li ignora. Correggi il proprietario con chown -R 1000:1000 /mnt/media, assegna alle directory il permesso di esecuzione con 755, quindi avvia una nuova scansione. Controlla anche la directory padre: se l'uid del container non può attraversare /mnt/media, non raggiunge mai le directory della libreria e il risultato è una libreria vuota. La seconda causa più comune è una struttura delle directory non conforme alle aspettative di Jellyfin.

Come posso accedere a Jellyfin da remoto in modo sicuro?

Ci sono due opzioni valide. Pubblicalo dietro un reverse proxy TLS su un sottodominio, in modo da cifrare l'accesso e lo streaming, e aggiungi fail2ban. Non esporre mai la porta non cifrata 8096, perché invia la password in chiaro. In alternativa, mantieni Jellyfin completamente privato e accedivi soltanto tramite una VPN: per una rete domestica è la scelta sicura più semplice. Configura nelle applicazioni direttamente l'indirizzo pubblico. L'autodiscovery è un broadcast sulla rete locale, quindi non raggiunge i client che si collegano da Internet.

Di quanto spazio disco e banda ha bisogno un VPS con Jellyfin?

Lo spazio disco dipende dalla qualità: considera 4-15 GB per un film 1080p compresso, 20-40 GB per un remux e 40-100 GB per un contenuto 4K. Per la maggior parte delle librerie serve quindi un volume a blocchi da 2-4 TB. La banda dipende dal bitrate della riproduzione diretta: 8-12 Mbps per ogni streaming 1080p e molto di più per il 4K. Verifica quindi che la velocità della porta sia sufficiente per il numero di spettatori simultanei e controlla il limite mensile di trasferimento. Aggiungi margine sulla CPU se prevedi di eseguire transcodifiche. Se prevedi di usare la riproduzione diretta, dai priorità alla banda rispetto al numero di core.

È legale eseguire Jellyfin su un VPS?

Jellyfin è un software gratuito e open source, quindi eseguirlo è perfettamente legale. Il punto è il contenuto: trasmetti in streaming soltanto contenuti multimediali che possiedi o per i quali disponi di una licenza, come copie personali dei tuoi dischi, registrazioni o file che hai il diritto di conservare. Jellyfin non include alcun contenuto multimediale e non offre strumenti per ottenerlo. È un riproduttore per una libreria che già possiedi.