SSD Nodes Learn
Guías Matt ConnorPor Matt Connor · Actualizado 2026-07-24

Instalar Jellyfin en un VPS con Docker

Guía para configurar Jellyfin en Docker usando block storage. Evite errores de permisos de archivos y problemas de transcodificación por falta de GPU.

Qué vas a construir

Un servidor de medios Jellyfin en un VPS: un contenedor, tres volúmenes y un disco de almacenamiento en bloque para tus películas y series, accesible desde cualquier navegador o aplicación de Jellyfin. La instalación consiste en un archivo compose de quince líneas. Los errores posteriores suelen deberse a dos causas: permisos de archivos que el contenedor no puede leer, o solicitar la transcodificación de video a un VPS sin GPU. Esta guía se centra principalmente en esos dos puntos, ya que son la causa principal de los problemas de soporte.

Jellyfin es gratuito y de código abierto; no requiere cuenta, no tiene funciones de pago ni telemetría. Por ello, aparece en casi todas las listas de cosas que vale la pena auto-alojar en 2026. Reproduce contenido multimedia de tu propiedad. No incluye contenido propio, y esta guía no trata sobre la adquisición de archivos.

La realidad de la transcodificación, antes de alquilar nada

Lea esto primero, ya que cambia su decisión de compra. Un servidor de medios realiza una de estas dos acciones al reproducir contenido. El Direct play transmite el archivo tal cual: el VPS lee los bytes del disco y los envía por la red, consumiendo casi nada de CPU. La Transcodificación recodifica el video al vuelo —nueva resolución, nuevo codec o subtítulos incrustados— y eso es trabajo puro de CPU.

Un VPS típico no tiene GPU. Por lo tanto, cada transcodificación se ejecuta en la CPU con libx264/libx265, y la codificación por software es costosa. Una sola transcodificación de 1080p H.264 puede saturar varios vCPUs compartidos; una transcodificación de 4K o HEVC generalmente no puede mantener el tiempo real, por lo que la reproducción se detiene y se queda en buffer permanentemente. La transcodificación por hardware —lo que hace que esto sea económico en un equipo doméstico con una iGPU de Intel o una tarjeta Nvidia— simplemente no está disponible a menos que su proveedor alquile instancias con GPU.

Por lo tanto, la estrategia en un VPS es: evitar la transcodificación. Mantenga su biblioteca en codecs que sus clientes reproduzcan de forma nativa —video H.264, audio AAC o AC3, en un contenedor MP4 o MKV— y elija aplicaciones cliente que permitan el direct-play: las apps nativas de Jellyfin para Android TV, iOS y Roku, además de Infuse, Kodi y el Jellyfin Media Player de escritorio. Si hace eso, el VPS nunca ejecutará ffmpeg y un servidor modesto de 2 vCPU podrá transmitir a varias personas a la vez. Si planea transcodificar, necesitará un servidor mucho más grande y caro, e incluso así, el 4K es una opción arriesgada.

Calcule también el ancho de banda, porque es la otra sorpresa. El direct-play envía el archivo a su propio bitrate. Un archivo 1080p comprimido funciona a 8-12 Mbps; un remux de Blu-ray 1080p a 20-30 Mbps; 4K HDR a 40-80 Mbps. Tres personas reproduciendo archivos de 10 Mbps consumen 30 Mbps de subida sostenida desde su VPS. Verifique dos valores en su plan: la velocidad del puerto (¿puede enviar 30 Mbps de subida?) y el límite de transferencia mensual. Una película de dos horas a 10 Mbps consume unos 9 GB de salida, por lo que un límite de 1 TB/mes permite poco más de cien películas de ese tipo al mes —tres o cuatro al día—; un hogar que vea contenido 4K, con un bitrate de cuatro a ocho veces mayor, agotará el límite mucho más rápido.

Requisitos previos

  • Un VPS Ubuntu 24.04 KVM recién instalado con acceso root o sudo, y Docker con el plugin Compose instalado.
  • Un volumen de almacenamiento en bloque para los archivos multimedia, con el tamaño adecuado para su biblioteca (ver sección de dimensionamiento abajo). No debe usar el disco raíz pequeño que incluye el VPS para guardar sus películas.
  • Un nombre de dominio si desea acceso HTTPS público, o una VPN WireGuard en el mismo VPS si prefiere mantener todo el sistema privado.
  • Contenido multimedia con derechos legales para su transmisión: sus propios rips, sus propias grabaciones o archivos de su propiedad.

Monte primero el almacenamiento de bloque

Adjunte el volumen en el panel de su proveedor, luego identifíquelo y móntelo. Obtenga el nombre del dispositivo en lsblk; será algo similar a /dev/sdb o /dev/vdb, nunca el disco raíz.

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

Móntelo mediante UUID, no mediante /dev/sdb, ya que las letras de los dispositivos cambian tras reiniciar y podría formatear o montar el disco incorrecto. Añada una línea a /etc/fstab:

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

nofail es importante: sin esto, si el volumen de bloque se desvincula, el sistema no arrancará y entrará en un shell de emergencia. El error más común es ejecutar mkfs.ext4 en un volumen que ya contiene datos; esto los borrará. Formatee solo volúmenes nuevos; si el disco ya contiene su librería, salte directamente a la línea en fstab.

Organice los archivos multimedia según el formato de Jellyfin

Jellyfin identifica los metadatos mediante los nombres de las carpetas y los archivos. Si la estructura es incorrecta, las películas aparecerán sin título ni póster, o un episodio podría asignarse a la serie equivocada. Existen tres reglas estrictas: cada película debe estar en su propia carpeta Name (Year) con un nombre de archivo correspondiente; las carpetas de temporadas deben llamarse Season 01 en lugar de S01; los archivos de episodios deben usar S01E01; y los especiales deben guardarse en 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

El uso de (Year) en las películas no es estético; sirve para diferenciar remakes y asegurar que el buscador obtenga el título correcto. Mantenga Movies y Shows en carpetas de nivel superior separadas, ya que cada una se convierte en una biblioteca de Jellyfin de un tipo de contenido específico. Mezclarlas confunde al proveedor de metadatos.

Permisos: la razón principal por la que las librerías aparecen vacías

Este es el error conceptual que hace perder tiempo a los usuarios. La imagen oficial de jellyfin/jellyfin no reconoce las variables de entorno PUID/PGID; estas pertenecen a la imagen de LinuxServer.io (lscr.io/linuxserver/jellyfin). En la imagen oficial, el usuario se controla mediante la clave user: en el archivo compose; si se omite, el contenedor se ejecuta como root. Independientemente de la opción que elija, la regla es la misma: el uid/gid con el que se ejecuta el contenedor debe tener permisos para leer y recorrer cada directorio de medios.

Ejecutaremos con el uid/gid 1000, el primer usuario que no es root en una instalación estándar de Ubuntu. Verifique el suyo y configure la propiedad:

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

Los directorios necesitan el bit de ejecución (el x en 755), no solo el de lectura; sin este bit, el contenedor no puede entrar en la carpeta aunque pueda listar los nombres. El error que vacía una librería completa ocurre en el directorio padre: si el uid del contenedor no puede recorrer el punto de montaje, nunca llegará a /media/Movies o /media/Shows, y todas las librerías aparecerán vacías con el error Access to the path ... is denied en el log. Cualquier carpeta de medios que no pueda leer se registrará y se omitirá; por tanto, un lote de archivos copiados como root desaparecerá silenciosamente de la librería. Por este motivo, aplicamos chown de forma recursiva y asignamos el bit de ejecución en cada directorio en lugar de corregir una sola carpeta.

El archivo 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.com

Línea por línea: user: "1000:1000" establece los permisos de archivo, coincidiendo con la propiedad definida arriba. /config contiene todo el servidor —cuentas, librerías, metadatos y estado de observación— por lo que debe tener permisos de escritura y es el elemento que se respalda. /cache es espacio de trabajo temporal. El montaje de medios es :ro (solo lectura) por diseño: Jellyfin almacena por defecto el arte y los metadatos en /config, por lo que nunca necesita escribir en su librería; el modo solo lectura protege sus archivos de eliminaciones accidentales o de un plugin defectuoso. El puerto se vincula a 127.0.0.1 deliberadamente —el inicio de sesión web de Jellyfin es HTTP simple, por lo que nunca publicamos el puerto 8096 a la internet pública. JELLYFIN_PublishedServerUrl es la dirección que el servidor anuncia para el autodescubrimiento local —un broadcast UDP en la LAN, por lo que los clientes en internet nunca lo ven y simplemente usan la URL que se ingresa en la app. Configure esto con la dirección que deben conocer los clientes; en dispositivos remotos, deberá ingresar esa URL manualmente.

Ejecute el contenedor desde el directorio compose:

docker compose up -d
docker logs -f jellyfin

Primera ejecución: el asistente de configuración y sus librerías

Como el puerto está vinculado a localhost, acceda al asistente mediante un túnel SSH desde su laptop en lugar de abrir un puerto en el firewall:

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

Ahora navegue a http://localhost:8096. El asistente le guiará para elegir el idioma y luego para crear un usuario administrador con una contraseña segura; esta cuenta es su servidor, así que no use una contraseña temporal. Añada su primera librería: elija el tipo de contenido Movies, apunte a /media/Movies (la ruta dentro del contenedor, no la ruta del host) y repita el proceso con Shows en /media/Shows. Finalice el proceso y Jellyfin realizará el escaneo. Un resultado correcto es que los pósters y títulos aparezcan en uno o dos minutos para una librería pequeña. Añada o edite librerías más tarde en Dashboard → Libraries, y fuerce un reescaneo con Scan All Libraries.

Si utiliza cualquier tipo de transcodificación, abra Dashboard → Playback → Transcoding y establezca la ruta temporal de transcodificación en /cache/transcodes para que el uso de disco se realice en el volumen de caché en lugar de saturar /config. Deje la aceleración por hardware configurada en None, ya que no hay una GPU disponible para la aceleración.

Acceso remoto: proxy inverso TLS, o mantenerlo en la VPN

Tiene dos métodos seguros para acceder a Jellyfin desde el exterior y un método inseguro que debe evitar. El método inseguro consiste en publicar el puerto 8096 directamente en internet: las credenciales viajan en texto plano y el puerto sufre ataques de fuerza bruta en cuestión de horas.

Opción A — Proxy inverso TLS. Coloque Jellyfin en un subdominio detrás de Traefik con TLS automático para sus apps Docker, o detrás de nginx con un certificado Let's Encrypt emitido por Certbot. Jellyfin utiliza WebSockets para actualizaciones en tiempo real, por lo que el proxy debe reenviar los encabezados de upgrade. Traefik realiza esto automáticamente; nginx requiere que se especifiquen y necesita HTTP/1.1 hacia el upstream para que el upgrade funcione:

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

Configure JELLYFIN_PublishedServerUrl con la dirección https:// para que cualquier autodescubrimiento local anuncie la URL correcta —las apps remotas usan la dirección que se les proporcione— y añada fail2ban para mitigar intentos de fuerza bruta contra el login. Una vez que el servidor sea público, apunte Uptime Kuma a la URL para recibir alertas de caídas antes que sus usuarios.

Opción B — mantenerlo privado en una VPN. No publique el puerto 8096 en absoluto; acceda a Jellyfin únicamente a través de un túnel WireGuard que termine en el mismo equipo. Para un entorno doméstico, esta es la opción segura más sencilla: sin certificados, sin exposición pública y sin superficie de ataque para fuerza bruta. Vincule el contenedor a la dirección del túnel o a localhost y conéctese a través de la VPN. Consulte la configuración de WireGuard VPN para un VPS privado para la creación del túnel.

Dimensionamiento de almacenamiento y copias de seguridad

Presupueste por calidad, no por número de archivos. Las películas 1080p comprimidas ocupan entre 4 y 15 GB cada una; un remux 1080p ocupa entre 20 y 40 GB; una temporada de series 1080p ocupa entre 15 y 40 GB; cualquier contenido 4K ocupa entre 40 y 100 GB por película. Una biblioteca de unos pocos cientos de películas más algunas series requiere un volumen de 2-4 TB. Es más económico sobredimensionar el volumen de bloques una vez que realizar una migración posterior.

/config es el estado completo del servidor, por lo que es lo único que debe respaldar. Realice un snapshot o detenga el servicio y cree un tar, y guarde la copia fuera del equipo:

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

/cache y la carpeta de transcodificación son desechables. El contenido multimedia en /mnt/media se respalda por separado o se acepta como re-rippable; la mayoría de los usuarios opta por esto último debido al tamaño. Las actualizaciones son docker compose pull && docker compose up -d; la etiqueta :10 anterior se mantiene dentro de la versión mayor 10.x, por lo que pasar a la siguiente versión mayor requiere una edición deliberada de la etiqueta. Revise las notas de lanzamiento de Jellyfin antes de realizar el cambio, ya que las migraciones del esquema de la biblioteca ocurren en las versiones mayores.

Modos de fallo y los mensajes que verá

La biblioteca está vacía después de un escaneo. El log en Dashboard → Logs (o ~/jellyfin/config/log/log_*.log) muestra:

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

El uid del contenedor no puede leer esa ruta. Causa: los archivos multimedia pertenecen a root o a un uid distinto a su valor user:, un directorio no tiene el bit de ejecución, o el punto de montaje padre no es accesible para ese uid. Solución: chown -R 1000:1000 /mnt/media, directorios 755, archivos 644, y luego reescanear.

La reproducción satura la CPU y el buffer. docker stats jellyfin muestra la CPU cerca del 100% multiplicado por el número de núcleos, y Dashboard → Playback lista la sesión como Transcode con una velocidad inferior a 1.0x. El cliente no está realizando direct-play, por lo que el VPS está realizando transcodificación de CPU a una velocidad menor que el tiempo real y pierde sincronización. Causa: un codec o contenedor no compatible, quemado de subtítulos (burn-in), o tone-mapping de HDR. Solución: cambie a un cliente con direct-play, mantenga las fuentes en H.264/AAC, use subtítulos de texto (SRT) en lugar de subtítulos de imagen (PGS/VOBSUB) que fuerzan el burn-in, y no use contenido 4K HDR en equipos que solo tengan CPU.

"No hay streams compatibles disponibles." El mensaje completo suele ser "This client isn't compatible with the media and the server isn't sending a compatible media format." El cliente rechazó la fuente y la transcodificación de respaldo también falló al iniciar. Causa: un comando ffmpeg erróneo, un archivo ilegible, o el perfil del usuario bloquea la conversión de video. Solución: lea la línea de ffmpeg en Dashboard → Logs, confirme que el archivo se puede reproducir, verifique los permisos de reproducción del usuario si depende de la transcodificación, y pruebe con un segundo cliente para descartar problemas de codecs del navegador.

Las películas no tienen póster o tienen el incorrecto. Los metadatos no coincidieron. Causa: una película no está en su propia carpeta Name (Year), una carpeta de temporada tiene el nombre S01 en lugar de Season 01, los episodios no están en formato S01E01, o falta el año. Solución: renombre siguiendo la estructura anterior, luego use Refresh metadata → Replace all, o use Identify en un elemento individual para asignar la entrada correcta de TMDB/TVDB.

FAQ

¿Puede un VPS transcodificar video sin una GPU?

Sí, pero solo mediante la CPU, y es costoso. Una sola transcodificación por software en 1080p puede saturar varios vCPUs. El formato 4K o HEVC usualmente no puede procesarse en tiempo real y provoca buffering. La mejor estrategia es evitar la transcodificación: mantenga su biblioteca en H.264/AAC y use aplicaciones cliente con soporte para direct-play, de modo que el VPS solo transmita bytes. Alquile una instancia con GPU solo si necesita transcodificación en tiempo real de forma indispensable.

¿Por qué mi biblioteca de Jellyfin aparece vacía tras un escaneo?

Casi siempre es un problema de permisos. La imagen oficial de jellyfin/jellyfin se ejecuta con el user: que usted defina (o como root). Si los archivos no son legibles por ese uid, los logs de Access to the path ... is denied registrarán el error y se omitirán los archivos. Corrija la propiedad con chown -R 1000:1000 /mnt/media, asigne el bit de ejecución a los directorios (755) y vuelva a escanear. Verifique también el directorio padre; si el uid del contenedor no puede atravesar /mnt/media, no llegará a las carpetas de la biblioteca y todo aparecerá vacío. La segunda causa más común es una estructura de carpetas que no coincide con lo que Jellyfin espera.

¿Cómo accedo a Jellyfin de forma remota y segura?

Existen dos opciones recomendadas. Use un proxy inverso TLS en un subdominio para cifrar el inicio de sesión y la transmisión, y añada fail2ban; nunca exponga el puerto 8096 sin cifrar, ya que envía la contraseña en texto plano. Otra opción es mantenerlo totalmente privado y acceder solo mediante una VPN, que es la opción segura más sencilla para un hogar. Proporcione la dirección pública directamente a las aplicaciones; la autodescubrimiento funciona mediante broadcast en la red local y no llegará a los clientes que se conecten a través de internet.

¿Cuánto disco y ancho de banda necesita un VPS para Jellyfin?

El disco depende de la calidad: estime entre 4-15 GB por cada película 1080p comprimida, 20-40 GB por cada remux, y 40-100 GB para 4K. Por tanto, la mayoría de las bibliotecas requieren un volumen de bloque de 2-4 TB. El ancho de banda depende del bitrate de direct-play: 8-12 Mbps por flujo 1080p, y mucho más para 4K. Confirme que la velocidad de su puerto soporte el número de espectadores simultáneos y vigile el límite de transferencia mensual. Añada capacidad de CPU si planea transcodificar; priorice el ancho de banda sobre los núcleos si planea usar direct-play.

Jellyfin es software libre y de código abierto, por lo que ejecutarlo es totalmente legal. Lo importante es el contenido: transmita solo medios de los que sea propietario o tenga licencia (rips de sus propios discos, grabaciones o archivos con derechos legales). Jellyfin no incluye contenido ni proporciona medios para obtenerlo; es un reproductor para una biblioteca que usted ya posee.

#jellyfin#media-server#docker#self-hosting#transcoding