Cómo configurar Nginx RTMP para streaming en vivo desde OBS
Guía técnica para instalar el módulo Nginx RTMP en Ubuntu, configurar la stream key, publicar desde OBS y generar HLS para distribución en navegador.
Un servidor de streaming propio significa control total: ingesta RTMP desde
OBS, distribución en HLS para navegador, posibilidad de grabar todo en
disco y cero dependencia de plataformas externas con reglas de contenido
opacas. El módulo nginx-rtmp resuelve esto en una sola pieza — recibe el
stream del encoder y lo empaqueta en fragmentos HLS dentro del mismo proceso.
Este tutorial cubre la instalación de Nginx con el módulo RTMP compilado
desde el código fuente en Ubuntu 24.04 LTS, la configuración de una
aplicación live con autenticación por clave, la generación del manifiesto
HLS para distribución y las reglas de firewall para reducir la superficie
de ataque. Al final, publicas desde OBS y reproduces en el navegador vía
HTML5.
Tiempo estimado: 30-40 minutos en una VPS limpia. Coste de ejecución: ~80 MB de paquetes de build + ~30 MB del binario Nginx final.
Requisitos previos
Necesitas Ubuntu 24.04 LTS con acceso sudo, al menos 2 GB de RAM (la compilación consume ~1 GB en pico) y los puertos 80, 443 y 1935 abiertos en el firewall externo del proveedor. SSH conectado y funcionando.
Ubuntu 24.04 LTS 1935/tcp 80/tcp o 443/tcp OBS Studio 30+ Paquetes que vamos a instalar para la compilación: build-essential,
libpcre3-dev, libssl-dev, zlib1g-dev y git. Todos del repositorio
oficial — ningún PPA externo.
Instalar dependencias de build
Antes de descargar el código fuente, prepara el sistema con las bibliotecas que Nginx necesita para compilar con soporte a SSL/TLS, regex y compresión gzip.
Actualiza el índice de paquetes de APT:
sudo apt updateEste comando sincroniza las listas de paquetes disponibles. Es necesario antes de instalar cualquier cosa nueva en un servidor que ha estado inactivo.
Instala el compilador y las bibliotecas de desarrollo:
sudo apt install -y build-essential libpcre3-dev libssl-dev zlib1g-dev git wgetbuild-essential trae gcc y make. libpcre3-dev es necesario para el
módulo de regex de Nginx, libssl-dev para HTTPS y zlib1g-dev para gzip.
Total de ~250 MB instalados.
Descargar el código fuente de Nginx y del módulo RTMP
El módulo nginx-rtmp-module se mantiene en un fork — el original de Arut
está archivado desde 2017. Para producción en 2026 usa el fork
arut/nginx-rtmp-module aún funcional para funciones clásicas, o
sergey-dryabzhinsky/nginx-rtmp-module que sigue recibiendo parches.
Crea un directorio de trabajo y descarga Nginx estable:
mkdir -p ~/build && cd ~/build
wget https://nginx.org/download/nginx-1.26.2.tar.gz
tar -xzvf nginx-1.26.2.tar.gzLa versión 1.26.x es la línea estable actual. Evita la mainline (1.27.x)
en producción a menos que necesites una característica específica.
Clona el módulo RTMP:
cd ~/build
git clone https://github.com/sergey-dryabzhinsky/nginx-rtmp-module.gitEste fork mantiene compatibilidad con Nginx moderno y tiene bugfixes que el original archivado no recibió.
Compilar e instalar Nginx con el módulo RTMP
La compilación lleva 4-5 minutos en una vCPU moderna. El configure acepta
decenas de flags — abajo mantenemos el conjunto mínimo necesario para un
servidor de streaming HTTPS funcional.
Configura el build apuntando al módulo RTMP:
cd ~/build/nginx-1.26.2
./configure \
--prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--pid-path=/var/run/nginx.pid \
--with-http_ssl_module \
--with-http_v2_module \
--add-module=../nginx-rtmp-moduleSi algún --with-* se queja por una biblioteca faltante, instala el -dev
correspondiente vía apt y vuelve a ejecutar configure.
Compila e instala:
make -j$(nproc)
sudo make install-j$(nproc) paraleliza la compilación en el número de núcleos disponibles.
En una vCPU única lleva ~7 minutos; en 4 vCPUs, ~2 minutos.
Crea el servicio systemd para gestionar Nginx:
sudo tee /etc/systemd/system/nginx.service > /dev/null <<'EOF'
[Unit]
Description=Nginx HTTP and RTMP server
After=network.target
[Service]
Type=forking
PIDFile=/var/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx
ExecReload=/usr/sbin/nginx -s reload
ExecStop=/usr/sbin/nginx -s quit
PrivateTmp=true
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable nginxExecStartPre valida la configuración antes de arrancar — evita un servicio
roto tras una edición.
Configurar la aplicación RTMP live
Ahora editamos nginx.conf para añadir el bloque rtmp (fuera del bloque
http, en el nivel raíz) y configurar una aplicación live que acepta la
publicación de OBS y genera fragmentos HLS.
Crea el directorio donde se escribirán los fragmentos HLS:
sudo mkdir -p /var/www/hls
sudo chown -R nobody:nogroup /var/www/hlsNginx, sin un usuario personalizado configurado, se ejecuta como nobody.
Ajusta esto si defines user www-data en el nginx.conf.
Sustituye /etc/nginx/nginx.conf por una configuración que combine HTTP +
RTMP:
worker_processes auto;
events {
worker_connections 1024;
}
rtmp {
server {
listen 1935;
chunk_size 4096;
application live {
live on;
record off;
hls on;
hls_path /var/www/hls;
hls_fragment 3s;
hls_playlist_length 60s;
allow publish 127.0.0.1;
allow publish 198.51.100.0/24;
deny publish all;
}
}
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name _;
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /var/www;
add_header Cache-Control no-cache;
add_header Access-Control-Allow-Origin *;
}
location /stat {
rtmp_stat all;
rtmp_stat_stylesheet stat.xsl;
}
location /stat.xsl {
root /var/www;
}
}
}Sustituye 198.51.100.0/24 por tu IP/red de origen real. El bloque
allow publish es la primera capa de defensa — sin él, cualquier persona
con tu URL RTMP podría publicar.
Error clásico: poner el bloque rtmp dentro de http {}. Es top-level,
paralelo a http. Si lo pegas dentro, Nginx fallará en nginx -t con un
mensaje confuso sobre unknown directive.
Valida la sintaxis y arranca el servicio:
sudo nginx -t
sudo systemctl start nginxnginx -t debería responder configuration file ... test is successful.
Cualquier error aquí indica la línea exacta — corrígelo antes de continuar.
Abrir puertos en el firewall
Sin reglas de firewall, el puerto 1935 queda expuesto a todo Internet. UFW en Ubuntu lo controla de forma sencilla.
Habilita UFW y abre los puertos necesarios:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 1935/tcp
sudo ufw enableConfirma con sudo ufw status numbered que aparecen las 4 reglas antes de
desconectar del SSH.
ufw enable aplica el default de denegar todo el tráfico entrante
inmediatamente. Si olvidas abrir OpenSSH primero, pierdes la sesión y
necesitas acceder por la consola serie del proveedor para desbloquearlo.
Publicar desde OBS
Con el servidor listo, configura OBS para la ingesta.
Abre OBS → Configuración → Emisión y completa:
Personalizado rtmp://TU-IP/live mi-stream-001 La “clave” es el nombre (name) que aparece en los fragmentos HLS y en los
logs de Nginx-RTMP. Puede ser cualquier cadena ASCII sin espacios.
Haz clic en “Iniciar transmisión” en OBS. OBS conecta a rtmp://TU-IP/live
y empieza a publicar el stream con el nombre mi-stream-001.
Verificar la transmisión
Comprueba que los fragmentos HLS se están generando y reproducen en un reproductor web.
Lista los archivos en /var/www/hls:
ls -la /var/www/hls/Salida esperada, ~3 segundos después de empezar a publicar:
mi-stream-001-0.ts
mi-stream-001-1.ts
mi-stream-001.m3u8Los .ts son fragmentos MPEG-TS de ~3 segundos cada uno. El .m3u8 es el
manifiesto que apunta a los fragmentos actuales.
Prueba en el navegador vía https://hls-js.netlify.app/demo/ o cualquier
reproductor que acepte HLS. Pega http://TU-IP/hls/mi-stream-001.m3u8 y
dale play.
Para una prueba rápida sin reproductor web, usa ffplay:
ffplay http://TU-IP/hls/mi-stream-001.m3u8La latencia esperada de OBS al reproductor es de 9-15 segundos con la
configuración por defecto de arriba (hls_fragment 3s × buffer de 3
fragmentos en el reproductor).
Resolución de problemas
OBS conecta pero no aparece nada en /var/www/hls
Verifica el permiso del directorio:
sudo -u nobody touch /var/www/hls/test && rm /var/www/hls/test
Si responde “Permission denied”, vuelve a aplicar chown -R nobody:nogroup /var/www/hls.
Error “Failed to connect to server” en OBS
Comprueba si el puerto 1935 está abierto desde fuera:
nc -zv TU-IP 1935
Si falla, revisa UFW (sudo ufw status) y el firewall del proveedor de la
VPS — algunos tienen un firewall externo separado.
El reproductor carga el m3u8 pero da “no supported source”
Falta CORS o el MIME type es incorrecto. Confirma que la respuesta del
.m3u8 trae Content-Type: application/vnd.apple.mpegurl y
Access-Control-Allow-Origin: *:
curl -I http://TU-IP/hls/mi-stream-001.m3u8
Próximos pasos
Ya tienes un servidor RTMP funcional convirtiendo a HLS. Desde aquí puedes profundizar en varias direcciones:
- Añadir HTTPS con Let’s Encrypt en el servidor HTTP (Certbot funciona con normalidad — el RTMP en sí no recibe TLS directo, pero el HLS sí).
- Implementar autenticación vía
on_publishapuntando a un endpoint propio que valide tokens contra la base de datos. - Habilitar grabación automática con
record all+record_path /var/www/recordingspara archivar todo en FLV. - Transcodificar a múltiples bitrates con
exec ffmpegdentro del bloque application — necesario para un ABR (adaptive bitrate) decente. - Cambiar HLS por LL-HLS (
hls_fragment 1s) o WebRTC si necesitas latencia sub-segundo.
Si vas a llevar esto a producción, una VPS Hostini de streaming ya viene con ancho de banda dedicado y los puertos RTMP/1935 abiertos por defecto — sin necesidad de abrir un ticket para el firewall externo.
Preguntas frecuentes
¿Por qué necesito compilar Nginx desde cero en lugar de usar el paquete de apt?
El módulo nginx-rtmp no es dinámico en el Nginx estable de Ubuntu — debe enlazarse en tiempo de compilación mediante `--add-module`. El paquete `libnginx-mod-rtmp` existe en algunos mirrors, pero suele quedar bloqueado en versiones antiguas del módulo (1.2.1 de 2017). Compilar lleva 4-5 minutos y te da control total de la versión.
¿Cuál es la latencia real de un stream RTMP convertido a HLS?
Entre 8 y 30 segundos con la configuración por defecto, dependiendo de `hls_fragment` (por defecto 5s) y `hls_playlist_length`. Para reducirla por debajo de 5s, considera LL-HLS con `hls_fragment 1s` y un reproductor compatible (hls.js 1.x), o cambia a WebRTC si necesitas sub-segundo.
¿Puedo transmitir desde OBS directamente a HLS sin RTMP?
No de forma nativa. OBS exporta en RTMP, SRT o WHIP (WebRTC). HLS es un formato de distribución para reproductor, no de ingesta. El flujo estándar es OBS → RTMP → Nginx-RTMP genera HLS → el reproductor consume HLS por HTTP.
¿Cómo restrinjo quién puede publicar en el stream sin exponer la clave?
Usa `on_publish` apuntando a un endpoint HTTP propio que valide el `name` (stream key) contra la base de datos. Nginx-RTMP solo permite la publicación si el endpoint responde 2xx. Combínalo con un firewall que cierre el puerto 1935 a IPs autorizadas si la base de transmisores es fija.
¿Cuántos streams simultáneos aguanta un servidor medio?
Depende de la CPU y el ancho de banda. En una VPS de 4 vCPU + 8 GB sin transcodificación (solo retransmitiendo RTMP y generando HLS por fragmento de copia directa), atiende 50-100 publishers concurrentes. Con transcodificación H.264 vía ffmpeg para múltiples bitrates, baja a 4-8 streams por CPU.
Los fragmentos HLS se generan pero el reproductor no consigue reproducir — ¿qué revisar?
Tres causas comunes: 1) CORS — añade `add_header 'Access-Control-Allow-Origin' '*'` en el location HLS; 2) MIME type — confirma que `.m3u8` es `application/vnd.apple.mpegurl` y `.ts` es `video/mp2t`; 3) permisos del directorio — `/var/www/hls` debe ser escribible por el usuario de Nginx (normalmente www-data).