SSD Nodes Learn Hosting plans →
Guias Matt ConnorPor Matt Connor

Jellyfin na VPS: transmita sua mídia com Docker

Configure o Jellyfin em Docker numa VPS com armazenamento em blocos, permissões corretas, reprodução direta e acesso remoto seguro, evitando transcodificação sem GPU.

O que você vai criar

Um servidor de multimédia Jellyfin numa VPS: um contentor, três volumes e um disco de armazenamento em blocos com os seus filmes e séries, acessível a partir de qualquer navegador ou aplicação Jellyfin. A instalação é feita com um ficheiro compose de quinze linhas. Tudo o que falha depois disso resulta de dois problemas: permissões de ficheiros que impedem o contentor de os ler e pedidos de transcodificação de vídeo a uma VPS sem GPU, que não tem capacidade para os processar. Este guia dedica a maior parte do conteúdo a esses dois problemas, porque é aí que surgem os pedidos de suporte.

O Jellyfin é gratuito e totalmente open source, sem conta, funcionalidades pagas ou telemetria. Por isso, aparece em quase todas as listas de coisas que vale a pena alojar por conta própria em 2026. Reproduz os conteúdos multimédia que possui. Não inclui conteúdo, e este guia não aborda a obtenção de conteúdos.

A realidade da transcodificação, antes de alugar qualquer coisa

Leia isto primeiro, pois altera o que você deve comprar. Um servidor de mídia faz uma de duas coisas quando você pressiona o botão de reprodução. Direct play transmite o arquivo exatamente como ele está: a VPS lê os bytes do disco e os envia pela rede, consumindo quase zero de CPU. Transcoding re-codifica o vídeo em tempo real, com nova resolução, novo codec ou legendas embutidas, e isso é um trabalho intensivo de CPU.

Uma VPS típica não possui GPU. Portanto, toda transcodificação de vídeo é executada na CPU com libx264/libx265, e a codificação via software é custosa. Uma única transcodificação 1080p H.264 pode saturar vários vCPUs compartilhados; uma transcodificação 4K ou HEVC geralmente não consegue acompanhar o tempo real, fazendo com que a reprodução trave e fique carregando indefinidamente. A transcodificação por hardware, o recurso que torna isso barato em um servidor doméstico com uma iGPU Intel ou uma placa Nvidia, simplesmente não está disponível para você, a menos que seu provedor alugue instâncias com GPU.

A estratégia completa em uma VPS é, portanto: evite a transcodificação. Mantenha sua biblioteca em codecs que seus clientes reproduzam nativamente, vídeo H.264, áudio AAC ou AC3, em um container MP4 ou MKV, e escolha aplicativos clientes que façam direct play: os aplicativos nativos do Jellyfin para Android TV, iOS e Roku, além do Infuse, Kodi e o Jellyfin Media Player para desktop. Faça isso e a VPS nunca precisará executar uma transcodificação via ffmpeg durante a reprodução, e uma máquina modesta de 2 vCPU transmitirá para várias pessoas ao mesmo tempo. Se planejar usar transcodificação, você precisará de uma máquina muito maior e mais cara, e mesmo assim o 4K é uma aposta arriscada.

Faça também as contas da largura de banda, pois essa é a outra surpresa. O direct play envia o arquivo na sua própria taxa de bits (bitrate). Um arquivo 1080p comprimido consome de 8-12 Mbps; um remux de Blu-ray 1080p de 20-30 Mbps; 4K HDR de 40-80 Mbps. Três pessoas fazendo direct play de arquivos de 10 Mbps representam 30 Mbps de upload sustentado saindo da sua VPS. Verifique dois números no seu plano: a velocidade da porta (ela consegue enviar 30 Mbps de upstream?) e o limite de transferência mensal. Um filme de duas horas a 10 Mbps consome cerca de 9 GB, portanto, uma franquia limitada de 1 TB/mês permite pouco mais de cem filmes por mês, três ou quatro por dia, e uma residência assistindo a 4K, com quatro a oito vezes a taxa de bits, esgota esse limite muito mais rápido. Considere qualquer outra coisa que gere tráfego saindo da mesma máquina no seu orçamento, incluindo um relay RustDesk auto-hospedado, que carrega uma sessão inteira de área de trabalho remota sempre que dois pares não conseguem se conectar diretamente.

Pré-requisitos

  • Um VPS KVM novo com Ubuntu 24.04, acesso root ou sudo, e Docker com o plugin Compose instalado.
  • Um volume de armazenamento em bloco para os ficheiros multimédia, com capacidade adequada ao tamanho da sua biblioteca (consulte a secção sobre dimensionamento abaixo). O pequeno disco raiz incluído num VPS não é o local adequado para guardar os seus filmes.
  • Um nome de domínio se quiser acesso HTTPS público, ou uma VPN WireGuard no mesmo VPS se preferir manter todo o serviço privado.
  • Conteúdo multimédia que tenha autorização legal para transmitir, como cópias feitas por si, gravações suas ou ficheiros de que é proprietário.

Monte o armazenamento em bloco primeiro

Anexe o volume no painel do seu provedor, localize-o e monte-o. Obtenha o nome do dispositivo através do lsblk; ele será algo como /dev/sdb ou /dev/vdb, nunca o disco raiz.

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

Monte-o pelo UUID, não pelo /dev/sdb, pois as letras dos dispositivos são reordenadas após reboots e você pode acabar formatando ou montando o disco errado. Adicione uma linha ao /etc/fstab:

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

O nofail é importante: sem ele, se o volume em bloco for desconectado, o servidor recusará o boot e cairá em um shell de emergência. O erro mais comum aqui é executar o mkfs.ext4 em um volume que já contém dados, pois isso os apaga. Formate apenas volumes novos; se o disco já contiver sua biblioteca, pule diretamente para a linha no fstab.

Organize a mídia conforme o esperado pelo Jellyfin

O Jellyfin associa metadados através dos nomes de pastas e arquivos. Se a estrutura estiver incorreta, os filmes aparecerão como arquivos sem título e sem pôster, ou um episódio será associado à série errada. Quatro convenções resolvem quase todo o trabalho: cada filme deve ficar em sua própria pasta com um nome de arquivo correspondente, nomeado como Name (Year) (o Jellyfin trata o ano como opcional, mas ele torna a associação muito mais confiável); as pastas de temporadas são nomeadas como Season 01, e não S01; os arquivos de episódios usam S01E01; e os especiais devem ser colocados em 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

O (Year) nos filmes não é decorativo; ele diferencia remakes para que o mecanismo de busca obtenha o título correto. Mantenha Movies e Shows como pastas de nível superior separadas, pois cada uma se torna uma biblioteca do Jellyfin de um tipo de conteúdo específico, e misturá-las confunde o provedor de metadados. O Jellyfin indexará prontamente uma terceira pasta de fotos, mas a experiência é limitada em comparação a um servidor de fotos dedicado. Portanto, se seus álbuns forem importantes, utilize uma máquina separada rodando PhotoPrism ou Immich e deixe esta dedicada a filmes e séries.

Permissões: a principal causa de bibliotecas vazias

Este é o equívoco que faz perder uma noite. A imagem oficial jellyfin/jellyfin não respeita as variáveis de ambiente PUID/PGID. Essas variáveis pertencem à imagem LinuxServer.io (lscr.io/linuxserver/jellyfin). Na imagem oficial, o utilizador é definido com a chave user: no compose. Se a chave for omitida, o contentor é executado como root. Em qualquer dos casos, a regra é a mesma: o uid/gid com que o contentor é executado tem de conseguir ler e atravessar todos os diretórios de multimédia.

Vamos executar o serviço com o uid/gid 1000, o primeiro utilizador não root numa instalação padrão do Ubuntu. Confirme o seu e defina o proprietário:

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

Os diretórios precisam do bit execute (o x em 755), e não apenas de permissão de leitura. Sem esse bit, o contentor não consegue entrar na pasta, mesmo que consiga listar o nome. O problema que esvazia uma biblioteca inteira está no diretório pai: se o uid do contentor não conseguir atravessar o próprio ponto de montagem, nunca chega a /media/Movies ou /media/Shows. Nesse caso, todas as bibliotecas ficam vazias de uma só vez e Access to the path ... is denied aparece no log. Qualquer pasta de multimédia que não consiga ler é registada e ignorada. Por isso, um conjunto de ficheiros copiados como root desaparece silenciosamente da biblioteca. É por isso que alteramos o proprietário recursivamente e definimos o bit execute em todos os diretórios, em vez de corrigir apenas uma pasta.

O ficheiro 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

Linha por linha: jellyfin/jellyfin:12 rastreia a linha de lançamento 12.x atual (12.2 em 2026-10-10). user: "1000:1000" define o uid/gid com o qual o processo Jellyfin é executado. Isto não altera a propriedade ou o modo de nenhum ficheiro, porque o Docker mantém o uid/gid do host nos diretórios montados, razão pela qual o chown acima tem de coincidir com ele. /config contém todo o servidor, contas, bibliotecas, metadados e estado de visualização, por isso deve ter permissão de escrita e é o que deve ser copiado em backups. /cache é um espaço de trabalho descartável. A montagem de media é :ro (apenas leitura) propositadamente: o Jellyfin armazena, por predefinição, ilustrações e metadados em /config, pelo que nunca precisa de escrever na sua biblioteca, e o modo de apenas leitura protege os seus ficheiros contra uma eliminação acidental ou um plugin com falhas. A porta está vinculada a 127.0.0.1 deliberadamente; o login web do Jellyfin é HTTP simples, por isso nunca publicamos a 8096 na internet pública. JELLYFIN_PublishedServerUrl é o endereço que o servidor anuncia para autodescoberta local, uma difusão UDP na LAN, pelo que os clientes na internet nunca o veem e utilizam simplesmente o URL que introduz na aplicação. Defina-o para o endereço que deve ser comunicado aos clientes e espere ter de introduzir esse URL manualmente em dispositivos remotos.

Inicie-o a partir do diretório do compose:

docker compose up -d
docker logs -f jellyfin

Primeira execução: o assistente de configuração e as suas bibliotecas

Como a porta está associada a localhost, aceda ao assistente através de um túnel SSH a partir do seu portátil, em vez de abrir uma porta na firewall:

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

Agora aceda a http://localhost:8096. O assistente guia-o pela seleção do idioma e, depois, pela criação de um utilizador administrador com uma palavra-passe forte. Esta conta dá acesso ao servidor, por isso não reutilize uma palavra-passe descartável. Adicione a sua primeira biblioteca: escolha o tipo de conteúdo Filmes, indique /media/Movies (o caminho dentro do contentor, não o caminho no host) e repita o processo com Séries em /media/Shows. Conclua o assistente para que o Jellyfin faça a leitura dos ficheiros. Numa biblioteca pequena, o resultado esperado é ver posters e títulos preenchidos dentro de um ou dois minutos. Mais tarde, pode adicionar ou editar bibliotecas em Dashboard → Libraries e forçar uma nova leitura com Scan All Libraries. Esses posters também são o que torna interessante experimentar um frontend alternativo quando o funcionamento básico estiver confirmado, porque Halcyon reconstrói a mesma biblioteca como uma videolocadora dos anos 90 que pode percorrer a partir das imagens que o Jellyfin já obteve.

Se utilizar qualquer transcodificação, abra Dashboard → Playback → Transcoding e defina o caminho temporário da transcodificação como /cache/transcodes. Assim, os ficheiros temporários ficam no volume de cache, em vez de aumentarem /config. Mantenha a aceleração de hardware definida como None, porque não existe uma GPU para a executar.

Acesso remoto: reverse proxy TLS ou manter na VPN

Existem duas formas seguras de aceder ao Jellyfin a partir do exterior e uma forma insegura que deve evitar. A forma insegura é expor a porta 8096 diretamente à internet: o login é transmitido em texto simples e a porta sofre ataques de brute-force em poucas horas.

Opção A, reverse proxy TLS. Coloque o Jellyfin num subdomínio atrás de Traefik com TLS automático para as suas aplicações Docker, ou atrás de nginx com um certificado Let's Encrypt emitido pelo Certbot. O Jellyfin utiliza WebSockets para atualizações em tempo real, por isso o proxy deve encaminhar os headers de upgrade. O Traefik faz isto automaticamente; o nginx precisa que sejam especificados e requer HTTP/1.1 para o upstream, caso contrário o upgrade nunca ocorre:

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

Defina JELLYFIN_PublishedServerUrl para o endereço https:// para que qualquer autodescoberta local anuncie o URL correto, as aplicações remotas utilizem o endereço que lhes fornece e adicione fail2ban para atrasar tentativas de brute-force contra o login. Assim que o servidor estiver público, aponte o Uptime Kuma para o URL para que saiba de períodos de inatividade antes dos seus utilizadores. Aponte as suas notificações para um servidor ntfy autoalojado e uma verificação falhada chegará como uma notificação push no telemóvel em vez de um e-mail que só lê na manhã seguinte. Colocar um login na internet pública é também um bom momento para auditar o resto da máquina em vez de confiar apenas que a porta 443 está a responder: sudo ss -tlnp lista todas as portas em escuta e o processo associado. Se também implementar código próprio na máquina, o open-kritt analisa repositórios e as suas dependências em busca de vulnerabilidades; este audita o código-fonte, não as portas em escuta.

Opção B, manter privado numa VPN. Não exponha a porta 8096 à internet pública; aceda ao Jellyfin apenas através de um túnel WireGuard terminado na mesma máquina. Para um ambiente doméstico, esta é a escolha segura mais simples: sem certificados, sem exposição pública, sem superfície para ataques de brute-force. O binding para localhost no ficheiro compose não é suficiente por si só neste caso, porque uma porta publicada em 127.0.0.1 é alcançável apenas a partir do próprio VPS, não a partir de pares da VPN. Publique-a no endereço do túnel do servidor (por exemplo, "10.8.0.1:8096:8096", utilizando o seu próprio IP de servidor WireGuard), ou mantenha localhost e coloque um proxy a escutar no endereço do túnel à frente, ligando-se depois através da VPN. Consulte a configuração de VPN WireGuard para um VPS privado para o túnel em si.

Dimensionamento de armazenamento e backups

Defina o orçamento pela qualidade, não pela contagem de ficheiros. Filmes em 1080p comprimidos ocupam entre 4-15 GB cada; um remux 1080p ocupa 20-40 GB; uma temporada de série em 1080p ocupa 15-40 GB; qualquer conteúdo em 4K ocupa 40-100 GB por filme. Uma biblioteca com algumas centenas de filmes e algumas séries requer um volume de 2-4 TB, e é mais barato sobre-dimensionar o volume de blocos uma vez do que migrar mais tarde.

/config contém todo o estado do servidor, por isso é a única coisa que deve copiar. Crie um snapshot ou pare o serviço e utilize o tar para o copiar para fora da máquina:

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

/cache e a pasta de transcodificação são descartáveis. O conteúdo multimédia em /mnt/media deve ser copiado separadamente ou aceite como algo que pode ser extraído novamente; a maioria das pessoas opta pela segunda opção devido ao tamanho. As atualizações são docker compose pull && docker compose up -d; a tag :12 acima mantém-se dentro da linha 12.x, por isso, passar para a próxima versão major é uma alteração deliberada de tag. Leia as notas de lançamento do Jellyfin antes de o fazer, pois ocorrem migrações de esquema de biblioteca em versões major. O Jellyfin removeu o prefixo "10." dos seus números de versão a partir da 12.0 (setembro de 2026): 12.0 é o que seria a 10.12, não existe 11.x, e a 10.11.x foi a última linha do esquema antigo. Se um ficheiro compose seu mais antigo ainda indicar jellyfin/jellyfin:10, ele está congelado na 10.11 (essa tag foi atualizada pela última vez em 2026-06-06, para a 10.11.11) e nunca irá descarregar a 12.x. Mudar para :12 é mais do que uma simples edição de tag: a 12.0 altera o esquema da base de dados e reescreve os dados no primeiro arranque. Por isso, pare o servidor e efetue o backup mencionado acima primeiro, pois esse backup é a única forma de reverter o processo. A 12.0 atualiza diretamente a partir da 10.10.7 ou de qualquer versão 10.11.x, e requer uma verificação completa da biblioteca após a atualização, o que demora visivelmente mais tempo do que o habitual. Uma tag fixa mais um diretório de estado copiado é a receita completa para qualquer contentor que esteja sempre ligado, e é o mesmo padrão por detrás de manter a memória e os agendamentos de um agente self-hosted ativos após reboots.

Modos de falha e as mensagens correspondentes

A biblioteca fica vazia após uma verificação. O log em Dashboard → Logs (ou ~/jellyfin/config/log/log_*.log) exibe:

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

O uid do container não consegue ler esse caminho. Causa: mídia pertencente ao root ou a um uid diferente do seu valor user:, um diretório sem o bit de execução, ou o próprio ponto de montagem pai inacessível para esse uid. Solução: chown -R 1000:1000 /mnt/media, diretórios 755, arquivos 644 e, em seguida, execute uma nova verificação.

A reprodução consome todo o CPU e causa buffering. O docker stats jellyfin mostra o uso de CPU próximo a 100% multiplicado pelo número de núcleos, e o Dashboard → Playback lista a sessão como Transcode com uma velocidade abaixo de 1.0x. O cliente não está em reprodução direta (direct-play), portanto o VPS está realizando transcodificação via CPU mais lenta que o tempo real. Causa: codec ou container não suportado, legendas embutidas (burn-in) ou mapeamento de tom HDR. Solução: utilize um cliente que suporte reprodução direta, mantenha as fontes em H.264/AAC, use legendas de texto (SRT) em vez de legendas de imagem (PGS/VOBSUB) — que a maioria dos clientes Jellyfin não consegue renderizar nativamente, forçando o burn-in — e evite conteúdo 4K HDR em servidores que dependem apenas de CPU.

"No compatible streams are available." A mensagem completa geralmente é "This client isn't compatible with the media and the server isn't sending a compatible media format." O cliente rejeitou a fonte e a transcodificação de fallback também falhou ao iniciar. Causa: um comando ffmpeg corrompido, um arquivo ilegível ou o perfil do usuário bloqueando a conversão de vídeo. Solução: leia a linha do ffmpeg em Dashboard → Logs, confirme se o arquivo é reproduzível, verifique as permissões de reprodução do usuário caso dependa de transcodificação e teste um segundo cliente para descartar peculiaridades de codecs do navegador.

Os filmes não possuem pôster ou exibem o pôster incorreto. Os metadados não corresponderam. Causa: uma pasta ou nome de arquivo que não coincide com o título listado pelo seu provedor de metadados, um remake ou título comum sem o ano para diferenciar as versões, uma pasta de temporada nomeada como S01 em vez de Season 01, ou episódios que não seguem o formato S01E01. Solução: renomeie seguindo o layout acima, depois selecione Refresh metadata → Replace all, ou use Identify em um item específico para definir a entrada correta do TMDB/TVDB.

FAQ

Um VPS pode transcodificar vídeo sem uma GPU?

Sim, mas apenas na CPU, e isso é dispendioso. Uma única transcodificação por software de 1080p pode saturar vários vCPUs, e 4K ou HEVC normalmente não acompanha o tempo real, causando buffering durante a reprodução. A melhor opção é evitar a transcodificação: mantenha a biblioteca em H.264/AAC e use aplicações cliente que façam reprodução direta, para que o VPS se limite a transmitir bytes. Alugue uma instância com GPU apenas se precisar realmente de transcodificação em tempo real.

Por que motivo a minha biblioteca Jellyfin está vazia depois de uma verificação?

Quase sempre, o problema são as permissões. A imagem oficial jellyfin/jellyfin é executada com o user: que definiu, ou como root, e se os ficheiros não forem legíveis por esse uid, os logs da verificação registam Access to the path ... is denied e ignoram-nos. Corrija o proprietário com chown -R 1000:1000 /mnt/media, atribua o bit de execução aos diretórios com 755 e faça uma nova verificação. Verifique também o diretório pai, porque, se o uid do contentor não puder atravessar o próprio /mnt/media, nunca chegará às pastas da biblioteca e tudo ficará vazio. A segunda causa mais comum é uma estrutura de diretórios que não corresponde ao formato esperado pelo Jellyfin.

Como posso aceder ao Jellyfin remotamente e em segurança?

Há duas boas opções. Coloque-o atrás de um reverse proxy TLS num subdomínio, para que o início de sessão e a transmissão sejam cifrados, e adicione fail2ban. Nunca exponha a porta simples 8096, porque isso envia a sua palavra-passe em texto simples. Em alternativa, mantenha o serviço totalmente privado e aceda a ele apenas através de uma VPN, que é a opção segura mais simples para uma casa. Configure nas aplicações o endereço público diretamente. A autodeteção é uma transmissão na rede local, por isso não chega aos clientes que entram pela Internet.

De quanto espaço em disco e largura de banda precisa um VPS Jellyfin?

O espaço em disco depende da qualidade: reserve 4-15 GB por filme 1080p comprimido, 20-40 GB por remux e 40-100 GB para 4K. Assim, a maioria das bibliotecas precisa de um volume de bloco de 2-4 TB. A largura de banda é determinada pelo bitrate da reprodução direta: 8-12 Mbps por transmissão 1080p e muito mais para 4K. Confirme se a velocidade da porta suporta o número de espectadores simultâneos e monitorize o limite mensal de transferência. Reserve capacidade adicional de CPU se planear transcodificar. Se planear fazer reprodução direta, dê prioridade à largura de banda em vez do número de cores.

O Jellyfin é software gratuito e de código aberto, e a sua execução é totalmente legal. O que importa é o conteúdo: transmita apenas meios que possui ou que está autorizado a armazenar, como cópias dos seus próprios discos, gravações ou ficheiros para os quais tem direitos. O Jellyfin não inclui meios nem fornece qualquer forma de os obter. É um leitor para uma biblioteca que já possui.