32blogby Studio Mitsu

Construye un servidor de codificación FFmpeg en un VPS

Configura un servidor dedicado de codificación FFmpeg en un VPS. Cubre instalación, automatización con carpeta de vigilancia, servicio systemd y una API REST simple.

by omitsu17 min read

This article contains affiliate links.

Contenido

Para construir un servidor de codificación FFmpeg en un VPS, instala FFmpeg e inotify-tools en una instancia Ubuntu 24.04, crea una carpeta de vigilancia con inotifywait -e close_write, registra el vigilante como servicio systemd para disponibilidad 24/7, y opcionalmente agrega un endpoint REST con FastAPI para envío remoto de trabajos.

Si codificas video en tu máquina local, tu CPU se satura y no puedes hacer nada más durante la siguiente hora. Todos hemos pasado por eso — la máquina se arrastra mientras FFmpeg procesa el material.

Este artículo te guía paso a paso para construir un servidor dedicado de codificación FFmpeg en un VPS. Al final tendrás una carpeta de vigilancia que activa la codificación automáticamente, un servicio systemd que lo mantiene funcionando 24/7 y una API REST simple para enviar trabajos de forma remota.

Lo que aprenderás

  • Por qué tiene sentido delegar la codificación a un VPS
  • Cómo elegir las especificaciones adecuadas del VPS
  • Instalación y configuración de FFmpeg desde cero
  • Construcción de una carpeta de vigilancia con inotifywait
  • Ejecución del vigilante como servicio systemd
  • Exposición de una API REST mínima con FastAPI
  • Consejos de producción: gestión de disco, límites de recursos, registros

Por qué ejecutar la codificación en un VPS

El principal problema con la codificación local no es solo la carga de CPU — es la fiabilidad. Deja una codificación nocturna corriendo en un portátil y hay una buena probabilidad de que se haya despertado del modo suspensión a mitad del proceso, o de que el ventilador haya hecho tanto ruido que mataste el proceso.

Un VPS resuelve esto con tres beneficios concretos.

Tu máquina local queda libre. Una vez que subes el archivo, la codificación se ejecuta completamente en el VPS. Puedes cerrar tu portátil, cambiar de contexto y volver a un archivo terminado.

Tiempo de actividad garantizado. Las instancias VPS corren en centros de datos sin suspensión, sin cortes de energía, sin apagados accidentales. Las codificaciones largas terminan de forma fiable.

Escalado elástico. Cuando la capacidad no es suficiente, actualiza el VPS o levanta un segundo. Eso es más barato y rápido que comprar una nueva máquina local.

En cuanto al costo, un VPS con suficiente CPU para trabajo de video típico cuesta $3–15/mes dependiendo de la región y el proveedor. Mucho menos que una estación de trabajo dedicada para codificación. Proveedores como Kamatera ofrecen una prueba gratuita de 30 días, para que puedas probar trabajos reales de codificación antes de comprometerte con un plan.

Si estás evaluando los costos de codificación auto-hospedada vs servicios gestionados en la nube, consulta FFmpeg vs AWS MediaConvert: Comparación de costos para un desglose con números reales.

Elección del VPS y estimación de especificaciones

La cantidad de núcleos de CPU es la especificación más importante para FFmpeg. Usa todos los hilos disponibles para la codificación por software, así que más núcleos significa directamente codificaciones más rápidas.

Guía de especificaciones

Caso de usoCPURAMAlmacenamiento
Pruebas / personal2 núcleos2 GB50 GB SSD
Escala media (varios archivos/día)4 núcleos4 GB100 GB SSD
Producción8+ núcleos8+ GB200+ GB SSD

La codificación por GPU (NVENC, VAAPI) es significativamente más rápida pero requiere un plan con GPU habilitada, que cuesta considerablemente más. Para la mayoría de los flujos de trabajo, libx264 o libx265 en CPU entrega excelente calidad sin el sobreprecio.

Proveedores recomendados

  • Kamatera — 13 centros de datos globales, desde $4/mes, prueba gratuita de 30 días
  • DigitalOcean — precios directos, buena documentación, fácil de redimensionar
  • Hetzner — excelente relación precio-rendimiento en Europa
  • Vultr — precios competitivos a nivel global, facturación por hora

Elige Ubuntu 24.04 LTS como sistema operativo. Soporte estándar hasta abril de 2029, documentación extensa, y apt install ffmpeg te da FFmpeg 6.1.x directamente.

Instalación y configuración de FFmpeg

Conéctate por SSH a tu VPS y comienza con una actualización del sistema.

bash
# Update the system
sudo apt update && sudo apt upgrade -y

# Install FFmpeg and required tools
sudo apt install -y ffmpeg inotify-tools python3-pip python3-venv

# Verify the install
ffmpeg -version

En Ubuntu 24.04, deberías ver ffmpeg version 6.1.x. En 22.04, el predeterminado es 4.4.x — igualmente funcional para esta guía. Si el comando no se encuentra, la instalación con apt falló — vuelve a ejecutar con sudo apt install -y ffmpeg. Para opciones de instalación detalladas, incluyendo compilación desde el código fuente, consulta la Guía completa de instalación de FFmpeg.

Configura la estructura de directorios.

bash
# Create the encoding server directories
sudo mkdir -p /opt/encoder/{watch,processing,done,failed,logs}

# Create a dedicated system user (never run as root)
sudo useradd -r -s /bin/false encoder

# Transfer ownership
sudo chown -R encoder:encoder /opt/encoder

Roles de los directorios:

  • watch/ — deja archivos aquí para activar la codificación
  • processing/ — los archivos se mueven aquí mientras la codificación se ejecuta
  • done/ — las codificaciones completadas aterrizan aquí
  • failed/ — los archivos con errores aterrizan aquí
  • logs/ — archivos de registro para el vigilante y FFmpeg
SCP / APIUploadwatch/Queueinotifyprocessing/FFmpeg encodeencodedone/Output

A continuación, escribe el script de codificación que hace la transcodificación real.

bash
# Create /opt/encoder/encode.sh
sudo tee /opt/encoder/encode.sh > /dev/null << 'EOF'
#!/bin/bash
set -euo pipefail

INPUT="$1"
BASENAME=$(basename "$INPUT" | sed 's/\.[^.]*$//')
OUTPUT="/opt/encoder/done/${BASENAME}_encoded.mp4"

ffmpeg -i "$INPUT" \
  -c:v libx264 \
  -preset slow \
  -crf 23 \
  -c:a aac \
  -b:a 128k \
  -movflags +faststart \
  "$OUTPUT" \
  2>> /opt/encoder/logs/ffmpeg.log

echo "Done: $OUTPUT"
EOF

sudo chmod +x /opt/encoder/encode.sh
sudo chown encoder:encoder /opt/encoder/encode.sh

-crf 23 es un buen punto de partida para el balance calidad/tamaño. Valores más bajos (18–20) dan mayor calidad con archivos más grandes. Valores más altos (26–28) comprimen más agresivamente. El punto óptimo para la mayoría del contenido es 20–26. Para una guía detallada sobre ajuste de CRF y configuraciones por caso de uso, consulta la Guía de compresión de video con FFmpeg.

¿Quieres automatizar la codificación de múltiples archivos? La guía de Automatización por lotes con Python y FFmpeg cubre flujos de trabajo multi-archivo con seguimiento de progreso.

Automatización de la codificación con una carpeta de vigilancia

El script de vigilancia escucha nuevos archivos en watch/ y ejecuta el script de codificación en cada uno.

bash
# Create /opt/encoder/watch.sh
sudo tee /opt/encoder/watch.sh > /dev/null << 'EOF'
#!/bin/bash
set -euo pipefail

WATCH_DIR="/opt/encoder/watch"
PROCESSING_DIR="/opt/encoder/processing"
FAILED_DIR="/opt/encoder/failed"
LOG="/opt/encoder/logs/watch.log"

log() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"
}

log "Encoder watch started. Watching: $WATCH_DIR"

inotifywait -m -e close_write --format '%f' "$WATCH_DIR" | while read -r FILENAME; do
  FILEPATH="${WATCH_DIR}/${FILENAME}"

  # Only process supported video formats
  EXT="${FILENAME##*.}"
  EXT_LOWER=$(echo "$EXT" | tr '[:upper:]' '[:lower:]')
  if [[ ! "$EXT_LOWER" =~ ^(mp4|mkv|mov|avi)$ ]]; then
    log "Skipped (unsupported format): $FILENAME"
    continue
  fi

  log "Detected: $FILENAME"

  # Move to processing to prevent double-triggering
  PROC_PATH="${PROCESSING_DIR}/${FILENAME}"
  mv "$FILEPATH" "$PROC_PATH"
  log "Moved to processing: $FILENAME"

  # Run the encode
  if /opt/encoder/encode.sh "$PROC_PATH"; then
    rm -f "$PROC_PATH"
    log "Success: $FILENAME"
  else
    mv "$PROC_PATH" "${FAILED_DIR}/${FILENAME}"
    log "Failed: $FILENAME — moved to failed/"
  fi
done
EOF

sudo chmod +x /opt/encoder/watch.sh
sudo chown encoder:encoder /opt/encoder/watch.sh

Pruébalo manualmente antes de conectarlo a systemd.

bash
# Start the watcher in the background
sudo -u encoder /opt/encoder/watch.sh &

# Drop a test file into the watch folder
cp /path/to/test.mp4 /opt/encoder/watch/

# Tail the log
tail -f /opt/encoder/logs/watch.log

Ejecución del vigilante como servicio systemd

Un script corriendo en segundo plano no es apto para producción. Regístralo como un servicio systemd para que se inicie en el arranque y se reinicie automáticamente si falla.

ini
# /etc/systemd/system/encoder.service
[Unit]
Description=FFmpeg Encoding Watch Service
After=network.target

[Service]
Type=simple
User=encoder
Group=encoder
ExecStart=/opt/encoder/watch.sh
Restart=on-failure
RestartSec=5s
StandardOutput=append:/opt/encoder/logs/systemd.log
StandardError=append:/opt/encoder/logs/systemd.log

# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/opt/encoder
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Habilita e inicia el servicio.

bash
# Reload systemd to pick up the new unit file
sudo systemctl daemon-reload

# Enable auto-start on boot
sudo systemctl enable encoder.service

# Start it now
sudo systemctl start encoder.service

# Check status
sudo systemctl status encoder.service

Active: active (running) significa que está funcionando. Usa journalctl para una inspección más profunda de los registros.

bash
# Follow logs in real time
sudo journalctl -u encoder.service -f

# View logs from the last hour
sudo journalctl -u encoder.service --since "1 hour ago"

Agregar una API de envío de trabajos remota

SCP a la carpeta de vigilancia es fiable pero requiere acceso SSH. Una API REST te permite enviar trabajos desde scripts, pipelines de CI o cualquier cliente HTTP.

Instala FastAPI en un entorno virtual.

bash
# Crear un entorno virtual de Python
python3 -m venv /opt/encoder/venv

# Instalar FastAPI y Uvicorn
/opt/encoder/venv/bin/pip install "fastapi[standard]" python-multipart

Escribe el servidor API.

python
# /opt/encoder/api.py
import os
import shutil
from pathlib import Path

from fastapi import Depends, FastAPI, File, HTTPException, Security, UploadFile
from fastapi.responses import JSONResponse
from fastapi.security import APIKeyHeader

app = FastAPI(title="FFmpeg Encoding API")

API_KEY = os.environ.get("ENCODER_API_KEY", "")
api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)

WATCH_DIR = Path("/opt/encoder/watch")
DONE_DIR = Path("/opt/encoder/done")
FAILED_DIR = Path("/opt/encoder/failed")
PROCESSING_DIR = Path("/opt/encoder/processing")


def verify_api_key(key: str = Security(api_key_header)) -> str:
    if not API_KEY:
        raise HTTPException(status_code=500, detail="API key not configured on server")
    if key != API_KEY:
        raise HTTPException(status_code=403, detail="Invalid API key")
    return key


@app.post("/encode")
async def submit_encode(
    file: UploadFile = File(...),
    _: str = Depends(verify_api_key),
) -> JSONResponse:
    """Upload a video file and queue it for encoding."""
    allowed_exts = {".mp4", ".mkv", ".mov", ".avi"}
    ext = Path(file.filename).suffix.lower()

    if ext not in allowed_exts:
        raise HTTPException(
            status_code=400,
            detail=f"Unsupported file type: {ext}. Allowed: {allowed_exts}",
        )

    dest = WATCH_DIR / file.filename
    with dest.open("wb") as f:
        shutil.copyfileobj(file.file, f)

    return JSONResponse(
        status_code=202,
        content={"status": "queued", "filename": file.filename},
    )


@app.get("/status")
async def get_status(_: str = Depends(verify_api_key)) -> JSONResponse:
    """Return file counts for each directory."""
    return JSONResponse(
        content={
            "watching": len(list(WATCH_DIR.iterdir())),
            "processing": len(list(PROCESSING_DIR.iterdir())),
            "done": len(list(DONE_DIR.iterdir())),
            "failed": len(list(FAILED_DIR.iterdir())),
        }
    )


@app.get("/health")
async def health_check() -> JSONResponse:
    """Health check endpoint — no auth required."""
    return JSONResponse(content={"status": "ok"})

Registra la API como servicio systemd.

bash
# Create the API service unit file
sudo tee /etc/systemd/system/encoder-api.service > /dev/null << 'EOF'
[Unit]
Description=FFmpeg Encoding API Server
After=network.target

[Service]
Type=simple
User=encoder
Group=encoder
WorkingDirectory=/opt/encoder
Environment="ENCODER_API_KEY=your-secret-key-here"
ExecStart=/opt/encoder/venv/bin/uvicorn api:app --host 127.0.0.1 --port 8000
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable encoder-api.service
sudo systemctl start encoder-api.service

Genera una clave API fuerte con openssl.

bash
# Generate a random 32-byte hex key
openssl rand -hex 32

Envía un trabajo via curl.

bash
# Upload a file for encoding
curl -X POST http://your-vps-ip:8000/encode \
  -H "X-API-Key: your-secret-key-here" \
  -F "file=@/path/to/video.mp4"

# Check queue status
curl http://your-vps-ip:8000/status \
  -H "X-API-Key: your-secret-key-here"

Para producción, pon Nginx al frente de la API como proxy inverso y agrega TLS. Enlazar Uvicorn a 127.0.0.1 significa que solo escucha en localhost — Nginx maneja la conexión HTTPS pública.

Consideraciones de producción

Gestión de espacio en disco

Los archivos de video llenan los discos rápidamente. Establece una política para la carpeta done/ antes de entrar en producción. El enfoque más simple es un cron job que elimina archivos con más de N días de antigüedad.

bash
# Delete files in done/ older than 7 days (add to crontab -e)
0 3 * * * find /opt/encoder/done -type f -mtime +7 -delete

Limitar codificaciones concurrentes

Si múltiples archivos llegan a la carpeta de vigilancia simultáneamente, el vigilante inicia codificaciones paralelas y satura la CPU. Agrega un archivo de bloqueo o usa flock en el script de codificación para forzar una codificación a la vez.

Baja la prioridad del proceso con nice para que el sistema operativo siga respondiendo incluso durante codificaciones pesadas.

bash
# In encode.sh — wrap the ffmpeg call with nice
nice -n 10 ffmpeg -i "$INPUT" ...

Rotación de registros

Los registros en /opt/encoder/logs/ crecen indefinidamente sin rotación. Configura logrotate.

bash
# Create /etc/logrotate.d/encoder
sudo tee /etc/logrotate.d/encoder > /dev/null << 'EOF'
/opt/encoder/logs/*.log {
    daily
    rotate 7
    compress
    missingok
    notifempty
}
EOF

Transferencia de archivos

SCP es la forma más simple de enviar archivos desde una máquina local a la carpeta de vigilancia.

bash
# Push a local file to the watch folder
scp /path/to/video.mp4 user@your-vps-ip:/opt/encoder/watch/

Configura autenticación con clave pública SSH para no necesitar escribir una contraseña en cada transferencia. Agrega un alias a ~/.ssh/config si haces esto frecuentemente.

Almacenamiento y distribución

Una vez que la codificación termina, necesitas un lugar para poner la salida. Elige el servicio según tu caso de uso.

Almacenamiento de objetos en la nube

ServicioPrecioMejor para
Backblaze B2$6/TB/mes (10 GB gratis)Almacenamiento a gran escala más barato
Google Drive15 GB gratisCompartir rápido con una cuenta de Google
Cloudflare R2~$0.015/GB/mes, sin cargos de egresoDistribución CDN sin costos de ancho de banda
Dropbox2 GB gratisColaboración en equipo

Para archivos de video grandes, Backblaze B2 o Cloudflare R2 ofrecen el mejor valor. Para compartir de forma casual, Google Drive está bien.

Plataformas de video

PlataformaTipoMejor para
YouTubeGratuito, ilimitado, alcance masivoContenido público, monetización
VimeoSin anuncios, streaming de alta calidadPortafolios, revisiones de clientes
Bunny.netCDN asequible + streaming de videoInsertar video en tu propio servicio

NAS auto-hospedado

Si quieres control total sobre tu biblioteca de video, un NAS Synology o QNAP funciona bien. Instala Jellyfin o Plex para transcodificación bajo demanda basada en FFmpeg y un servidor de medios doméstico completo.

Si estás codificando para distribución web, el siguiente paso natural es streaming HLS con un CDN — tu VPS codifica, el CDN distribuye.

Artículos relacionados:

FAQ

¿Cuántos núcleos de CPU usa FFmpeg realmente?

FFmpeg escala a través de todos los hilos de CPU disponibles para codificación por software con libx264 y libx265. Un VPS de 4 núcleos usa 4 hilos por defecto. Puedes limitarlo con -threads N si quieres dejar capacidad para otros procesos. Para codificación por hardware (NVENC, QSV), el conteo de núcleos importa menos — la GPU hace el trabajo pesado. Consulta la guía de codificación GPU para más detalles.

¿Puedo usar esta configuración para salida HLS/DASH?

Sí. Modifica encode.sh para generar segmentos HLS en lugar de un solo MP4. Reemplaza el comando FFmpeg con -hls_time 6 -hls_list_size 0 output.m3u8 y el vigilante generará archivos listos para HLS automáticamente. La guía de streaming HLS cubre streaming adaptativo multi-bitrate desde este tipo de configuración.

¿Es un VPS de $5/mes suficientemente rápido para codificar video?

Para uso personal con codificaciones ocasionales de 1080p, un VPS de 2 núcleos/$5 lo maneja bien — un clip de 10 minutos en 1080p toma aproximadamente 15–25 minutos con -preset slow -crf 23. Para 4K o codificación por lotes, necesitarás 4+ núcleos. La ventaja del VPS es que puedes redimensionar al instante cuando la demanda aumenta.

¿Cómo transfiero archivos grandes al VPS más rápido?

SCP funciona pero puede ser lento para archivos de varios gigabytes a larga distancia. Usa rsync --partial --progress para transferencias reanudables. Para subidas masivas, considera rclone para sincronizar una carpeta local con la carpeta de vigilancia del VPS. Comprimir antes de transferir (ffmpeg -c copy para eliminar streams innecesarios) también ayuda.

¿Qué pasa si el VPS se reinicia durante una codificación?

El servicio systemd se reinicia automáticamente después del arranque. Sin embargo, el archivo que estaba siendo codificado quedará atascado en processing/ con una salida parcialmente escrita en done/. Agrega una verificación de inicio a watch.sh que mueva cualquier archivo en processing/ de vuelta a watch/ para que se re-encolen automáticamente.

¿Debería compilar FFmpeg desde el código fuente en el VPS?

Para la mayoría de los casos, el paquete apt es suficiente. Compila desde el código fuente solo si necesitas códecs no incluidos en la compilación predeterminada (como libfdk-aac o libsvtav1). La guía de instalación de FFmpeg cubre tanto la instalación por apt como la compilación desde fuente.

¿Cómo monitoreo el progreso de codificación remotamente?

El endpoint /status de la API muestra los conteos de la cola. Para progreso en tiempo real de codificaciones individuales, agrega -progress pipe:1 al comando FFmpeg y parsea la salida en tu script de vigilancia. También puedes usar watch -n 5 curl -s http://localhost:8000/status directamente en el VPS.

¿Esta configuración está lista para producción en un SaaS de video?

La arquitectura de carpeta de vigilancia + systemd maneja volumen bajo a medio de forma fiable. Para un SaaS con usuarios concurrentes, prioridades de trabajo y lógica de reintentos, necesitarás una cola de mensajes (Redis + Celery, o RabbitMQ) en lugar de activación basada en sistema de archivos. Esta guía te da la capa de codificación — la capa de orquestación es un tema separado.

Conclusión

Esto es lo que construimos y por qué funciona.

  • VPS en lugar de local: libera tu máquina, garantiza tiempo de actividad, escala sin cambios de hardware
  • Elección de especificaciones: 2 núcleos para uso personal, 8+ para producción
  • Instalación de FFmpeg: apt install ffmpeg inotify-tools cubre lo esencial
  • Carpeta de vigilancia: inotifywait -e close_write se activa solo después de que un archivo se ha escrito completamente — seguro para SCP
  • Servicio systemd: se inicia automáticamente en el arranque, se reinicia en fallos, registra en journald
  • API REST: FastAPI + autenticación por clave API para envío remoto de trabajos
  • Producción: cron de limpieza de disco, nice para cortesía de CPU, logrotate para higiene de registros

Comienza con la carpeta de vigilancia y el servicio systemd — ese es el núcleo del sistema y cubre la mayoría de los casos de uso. Agrega la API después si necesitas envío programático de trabajos. Cuando el VPS se quede sin capacidad, actualiza la instancia; el código no cambia.


¿Cansado de memorizar comandos de FFmpeg? Prueba ffmpeg-quick — una CLI de código abierto que envuelve tareas comunes como compresión, HLS y creación de GIF en presets simples que puedes ejecutar con npx.

Kamatera

VPS en la nube de grado empresarial con centros de datos globales

  • 13 centros de datos (EE.UU., Europa, Asia, Oriente Medio)
  • Desde $4/mes por 1GB RAM — pago por uso
  • Prueba gratuita de 30 días