If you want to run a streaming server without panel software — pure bare-metal RTMP ingest with HLS/DASH output — the open-source NGINX with the RTMP module is the most battle-tested option. It's what most "cheap" CDN providers run under the hood, and it's free.
This guide covers a full NGINX-RTMP build that accepts RTMP input, transcodes (optionally), and outputs HLS for IPTV apps and websites.
1. Server requirements
- OS: Ubuntu 22.04 LTS
- RAM: 4 GB minimum (16 GB if transcoding)
- CPU: 4 vCPU minimum, 8+ for transcoding 1080p streams
- Disk: 50 GB SSD (more if recording HLS to disk for catch-up)
- Bandwidth: Calculate using our bandwidth calculator
2. Install build dependencies
apt update
apt install -y build-essential libpcre3 libpcre3-dev libssl-dev zlib1g-dev \
libxml2-dev libxslt1-dev libgd-dev libgeoip-dev wget unzip git ffmpeg
3. Build NGINX with the RTMP module
Ubuntu's stock NGINX doesn't include the RTMP module — you must compile from source.
cd /usr/src
wget http://nginx.org/download/nginx-1.24.0.tar.gz
tar -xzf nginx-1.24.0.tar.gz
git clone https://github.com/arut/nginx-rtmp-module.git
cd nginx-1.24.0
./configure --prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--pid-path=/var/run/nginx.pid \
--lock-path=/var/lock/nginx.lock \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_realip_module \
--with-http_stub_status_module \
--with-http_gzip_static_module \
--add-module=/usr/src/nginx-rtmp-module
make -j$(nproc)
make install
4. Create a systemd service
Create /etc/systemd/system/nginx.service:
[Unit]
Description=The NGINX HTTP and reverse proxy server
After=syslog.target network-online.target remote-fs.target nss-lookup.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=/bin/kill -s QUIT $MAINPID
PrivateTmp=true
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable nginx
systemctl start nginx
5. NGINX-RTMP configuration
Replace /etc/nginx/nginx.conf with:
worker_processes auto;
events { worker_connections 4096; }
# RTMP ingest
rtmp {
server {
listen 1935;
chunk_size 4096;
application live {
live on;
record off;
# Convert RTMP to HLS automatically
hls on;
hls_path /var/www/hls;
hls_fragment 4s;
hls_playlist_length 60s;
hls_continuous on;
hls_cleanup on;
# Allow only specific IPs to publish
allow publish 127.0.0.1;
allow publish YOUR_INGEST_IP;
deny publish all;
# Anyone can pull
allow play all;
}
}
}
# HTTP for HLS playback
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name stream.yourdomain.com;
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 /usr/src/nginx-rtmp-module;
}
}
}
mkdir -p /var/www/hls
chown -R nginx:nginx /var/www/hls
nginx -t
systemctl reload nginx
6. Open required ports
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 1935/tcp # RTMP
ufw enable
7. Push your first stream
Using OBS or FFmpeg, push to:
rtmp://your-server-ip/live/STREAM_KEY
FFmpeg example (push a test loop):
ffmpeg -re -stream_loop -1 -i sample.mp4 -c copy -f flv \
rtmp://your-server-ip/live/test123
Then play HLS at:
http://your-server-ip/hls/test123.m3u8
8. Multi-bitrate transcoding (ABR)
For adaptive bitrate (1080p / 720p / 480p variants from a single source), add an exec directive to spawn FFmpeg per stream:
application live {
live on;
exec ffmpeg -i rtmp://localhost/live/$name
-c:v libx264 -preset veryfast -b:v 4500k -s 1920x1080 -c:a aac -b:a 128k -f flv rtmp://localhost/show/$name_1080p
-c:v libx264 -preset veryfast -b:v 2500k -s 1280x720 -c:a aac -b:a 128k -f flv rtmp://localhost/show/$name_720p
-c:v libx264 -preset veryfast -b:v 800k -s 854x480 -c:a aac -b:a 96k -f flv rtmp://localhost/show/$name_480p;
}
application show {
live on;
hls on;
hls_path /var/www/hls;
hls_nested on;
hls_fragment 4s;
hls_variant _1080p BANDWIDTH=4500000,RESOLUTION=1920x1080;
hls_variant _720p BANDWIDTH=2500000,RESOLUTION=1280x720;
hls_variant _480p BANDWIDTH=800000,RESOLUTION=854x480;
}
Warning: transcoding is CPU-heavy. A single 1080p ABR ladder uses ~2 cores. Plan capacity accordingly.
9. SSL for HLS
HLS over plain HTTP is fine for testing but not for production. Use Certbot:
apt install -y certbot python3-certbot-nginx
certbot --nginx -d stream.yourdomain.com
10. Common NGINX-RTMP issues
- RTMP push connects then drops: Almost always firewall — ports 1935 inbound must be open
- HLS plays but shows "buffering" every few seconds: Reduce
hls_fragmentto 2s andhls_playlist_lengthto 30s - Transcoding is slow / stutters: CPU bottleneck; switch
libx264 -presetfromveryfasttoultrafast, or upgrade to a server withlibx264-cudafor GPU encoding - Latency too high: Lower
hls_fragmentto 1s, switch to LL-HLS, or use SRT/WebRTC for sub-second use cases
NGINX-RTMP is great when it works, but production support means dealing with kernel tuning, codec edge cases, and DDoS protection. Our restream service handles all of this for you, with anti-freeze and global CDN built in. Compare options →