How the module is laid out
The rtmp { } block lives at the root of nginx.conf, beside http { }, never inside it: the directive's context is root. Inside it a server holds a listen socket (1935 in the project's own example) and one or more application blocks. The application is the first path element of the RTMP URL, rtmp://rtmp.example.com/app[/name], and unlike an http location its name cannot be a pattern.
live on makes an application one-to-many. hls on makes it write an .m3u8 playlist and MPEG-TS fragments into hls_path, which the module creates if it is missing. The module never serves those files itself; an http location does. The README is explicit that the incoming stream must be H.264 and AAC for HLS, so an encoder sending anything else needs ffmpeg in front.
| Directive | Default | What it controls |
|---|---|---|
| hls_fragment | 5s | Target fragment length |
| hls_playlist_length | 30s | How much of the past the playlist lists |
| hls_cleanup | on | Old fragments and playlists removed |
| hls_nested | off | One subdirectory per stream when on |
| respawn | on | Restarts an exec child that dies mid-publish |
| drop_idle_publisher | off | Drops a publisher sending no audio or video |
In the module's source a fragment is closed only on a keyframe once hls_fragment has elapsed, with a forced split at ten times that length. Pin the encoder's keyframe interval (2 s is common) or fragments come out uneven.
Sizing the port and the cores
Taking RTMP in and handing out HLS files is light work for a processor; the port runs out first. The figures below are from our dedicated streaming server page: every viewer on the top rendition, a fifth of the port held back.
| Rendition | Per viewer | 1 Gbps | 10 Gbps |
|---|---|---|---|
| 480p | 1.5 Mbps | 533 | 5,333 |
| 720p | 2.5 Mbps | 320 | 3,200 |
| 1080p | 4.5 Mbps | 177 | 1,777 |
| 1080p60 | 6 Mbps | 133 | 1,333 |
Cores matter once ffmpeg transcodes. Every rung of a libx264 ladder is its own software encode, so CPU load grows with rungs times channels; measure one channel with top before adding the next. Past a handful of channels the work belongs on a GPU, which is a different machine: video transcoding servers.
Disk barely matters for live HLS. With the defaults the playlist covers 30 seconds, so by our arithmetic a 6 Mbps rung lists about 23 MB of fragments at any moment. The README suggests a tmpfs directory for them.
Why this belongs on a whole machine
- The port is not shared. One switch port per server, unmetered, with no transfer allowance attached and no overage price. 1 Gbps is included on 10 of our 18 lines and 500 Mbps on the other 8; 2 to 100 Gbps are chosen on the order form.
- Root is yours. The service page names OBS pushing to nginx-rtmp as one of the stacks people run; nothing of ours runs on the box.
- A console when the stream dies at night. HPE iLO, Dell iDRAC or a Supermicro BMC on the blade and rack lines and the m510 and m710x cartridges, so a firewall rule or kernel update that cuts off SSH becomes a screen you can fix rather than a ticket.
- Five cities on AS19624. New York, Miami, San Francisco, Amsterdam and Bucharest, all on our own network. Pick the one nearest most of the audience.
- Delivery. A racked machine is live about 30 minutes after the order is approved; a build with a faster port takes 4 to 24 hours.
Setting it up on Debian or Ubuntu
- Install the pieces:
sudo apt install nginx libnginx-mod-rtmp ffmpeg(on Ubuntu the module sits in universe). Debian's own package test checks that nginx runs with the module straight after installation. - Create the fragment directory for the worker user:
sudo install -d -o www-data -g www-data /var/www/hls. - Append the block below to
/etc/nginx/nginx.conf, after the closing brace ofhttp { }. Files inconf.dare included insidehttp, where anrtmpblock is refused. - Add the http server from the second block as a site, then run
sudo nginx -tandsudo systemctl reload nginx. - Set the encoder to server
rtmp://your-host/ingestand stream keyshow. The master playlist appears at/hls/show.m3u8.
rtmp_auto_push on;
rtmp {
server {
listen 1935;
application ingest {
live on;
allow publish 198.51.100.24;
deny publish all;
allow play 127.0.0.1;
deny play all;
exec_push /usr/bin/ffmpeg -i rtmp://127.0.0.1/ingest/$name
-c:v libx264 -preset veryfast -b:v 2500k -s 1280x720 -g 60 -c:a aac -b:a 128k -f flv rtmp://127.0.0.1/hls/$name_720
-c:v libx264 -preset veryfast -b:v 800k -s 640x360 -g 60 -c:a aac -b:a 96k -f flv rtmp://127.0.0.1/hls/$name_360;
}
application hls {
live on;
allow publish 127.0.0.1;
deny publish all;
deny play all;
hls on;
hls_path /var/www/hls;
hls_nested on;
hls_fragment 4s;
hls_playlist_length 24s;
hls_variant _720 BANDWIDTH=2700000;
hls_variant _360 BANDWIDTH=950000;
}
}
}
rtmp_auto_push is the README's switch for running with several nginx workers, which Debian's default worker_processes auto gives you; without it the local ffmpeg can land on a worker that never saw the stream. -g 60 is a 2 s keyframe interval at 30 fps; change it if the source runs at another rate. ffmpeg is called by full path because nginx clears the environment before running it, which can leave a bare command name unresolved.
server {
listen 80;
server_name live.example.com;
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /var/www;
add_header Cache-Control no-cache;
}
}
Put a certificate on that hostname before you embed the player in an https page. If the player lives on another domain, add an Access-Control-Allow-Origin header naming that domain; the wiki's example uses *, which it marks as a development setting.
Who may publish, and who may pull
allow and deny take publish or play and an address, a subnet or all, and nginx checks them in the order they appear. In the config above only 198.51.100.24 (replace it with your encoder's address) can go live on ingest, and only ffmpeg on 127.0.0.1 can publish into hls. Without the deny publish all line, anyone who finds port 1935 can publish into your applications.
- Encoder without a fixed address: replace the
allow publishanddeny publish alllines iningestwithon_publishand a URL. nginx calls it before accepting the stream; a 2xx reply lets it through, a 3xx redirects it, anything else drops the connection. Arguments added to the stream name, such asshow?key=..., are passed along to that callback. Addaccess_log off;to the rtmp server too: its default log format records those arguments, key included. - Raw RTMP pulls:
deny play allon the ingest application stops outsiders pulling the full-bitrate feed off your port; only the local ffmpeg reads it. - Firewall: open TCP 1935 to the encoder's address only, and 80 and 443 to everyone.
- Dead encoders:
drop_idle_publisher 10sfrees the stream name when an encoder stops sending but keeps the socket open.
Running it day to day
- Updates: the Debian package carries a dependency on the nginx ABI it was built against, so
apt upgradekeeps nginx and the module in step instead of loading a mismatched module. - Statistics:
rtmp_stat allin an http location gives XML on streams and clients. Bind that server to 127.0.0.1 and read it over an SSH tunnel; it lists client addresses. - Child processes: with
respawnon, nginx restarts an exec'd ffmpeg that crashes while the encoder is still publishing;exec_kill_signaldefaults to SIGKILL when publishing stops. - Logs: a failed ffmpeg shows up as a rendition directory that never appears under
/var/www/hls. Run the same ffmpeg line by hand against the ingest URL to see its error.
Limits worth knowing first
The module's feature list covers RTMP in and RTMP, HLS and MPEG-DASH out. An encoder that speaks only SRT needs a different engine; our SRT streaming server page covers that. Live HLS from this setup trails the encoder by several fragments, so it suits broadcast, not two-way conversation.
One origin in one city carries most audiences. When it does not, put the video CDN in front of this machine rather than replacing it. If you would rather not run nginx at all, our managed live video plans take RTMP in and run the software for you, with an audience cap per plan. And if the job is getting OBS onto your own web page, start with streaming OBS to a website.
Questions
Is an nginx RTMP server a separate program from nginx?
No. It is the nginx you already know with one extra module, nginx-rtmp-module, loaded at start. On Debian and Ubuntu that module ships as libnginx-mod-rtmp, built against the distribution's nginx. The same process serves your HLS files over http.
Which port does the encoder connect to?
Whatever the listen directive in the rtmp server says; 1935 is the conventional RTMP port and the one in the project's example. Open it in the firewall only to the address your encoder publishes from. Viewers never touch it: they fetch HLS over 80 or 443.
Can nginx-rtmp transcode by itself?
No. It starts external programs with exec_push, once per published stream, and stops them when publishing ends. ffmpeg reads the stream back from the module and publishes each rendition into a second application. The CPU work is ffmpeg's, so size cores to the ladder.
Will it work on a Windows server?
Only partly. The README lists exec, static pulls and auto_push as unsupported on Windows, which removes the ffmpeg ladder and multi-worker streaming. Run it on Linux; Ubuntu is among the systems our order form installs.
How many viewers can one server carry?
Divide the usable port by the bitrate each viewer receives. At 2.5 Mbps per viewer, with a fifth of the port held back, a 1 Gbps port carries about 320 viewers at once and a 10 Gbps port about 3,200. The machine's CPU is rarely the limit unless it also transcodes.
Can the playlist be served over HTTPS?
Yes. The HLS files are ordinary files served by nginx's http server, so they take the same certificate as any other site on the box. On a dedicated server the hostname and the certificate are yours, and nobody else can change them under the player.