The NGINX RTMP module is the most widely used solution for building a live streaming server on top of NGINX. It turns NGINX into a full-featured media streaming server that accepts RTMP streams from encoders like OBS Studio or FFmpeg and delivers them to viewers via RTMP, HLS, or DASH. Whether you need a private broadcasting platform, a surveillance recording system, or a relay network that pushes streams to YouTube and Twitch simultaneously, the NGINX RTMP module provides everything you need.
Why Use the NGINX RTMP Module?
RTMP (Real-Time Messaging Protocol) remains the dominant protocol for live stream ingest β the connection between your encoder and the server. Every major streaming platform accepts RTMP, and every serious encoder supports it. The NGINX RTMP module handles that ingest natively and then converts the stream into modern playback formats:
- HLS (HTTP Live Streaming) β the standard for iOS, Safari, and most mobile devices. The module generates
.m3u8playlists and.tssegments on disk automatically. - DASH (Dynamic Adaptive Streaming over HTTP) β the open standard for adaptive bitrate streaming. The module generates
.mpdmanifests and.m4a/.m4vsegments. - RTMP playback β for legacy players and low-latency scenarios where RTMP viewers connect directly.
This multi-protocol architecture means a single NGINX instance can accept one RTMP stream and serve it to browsers, mobile apps, smart TVs, and set-top boxes simultaneously.
NGINX RTMP Module vs. NGINX HTTP-FLV Module
The NGINX RTMP module is the original streaming module that started the ecosystem. The NGINX HTTP-FLV module is a fork that adds HTTP-FLV live playback, GOP caching for instant viewer experience, JSON statistics, and virtual host support. If you need sub-second latency with browser playback via flv.js, consider the HTTP-FLV module instead. However, if your workflow relies on HLS or DASH delivery β which covers the vast majority of production streaming use cases β the NGINX RTMP module is the proven, stable choice.
Why Not Just Use FFmpeg Alone?
FFmpeg can push streams to platforms directly, but it cannot receive streams from multiple publishers, serve multiple viewers simultaneously, or provide an HTTP-based statistics and control interface. The NGINX RTMP module acts as a media server β a central hub that decouples publishers from viewers, supports authentication, recording, relay, and multi-format output from a single ingest point.
How the NGINX RTMP Module Works
The module introduces a new top-level rtmp {} configuration block alongside the standard http {} and events {} blocks. Inside the rtmp block, you define servers, applications, and streaming behavior:
- A publisher (FFmpeg, OBS, Wirecast) connects to the RTMP server on port 1935 and sends a live audio/video stream.
- The
applicationblock receives the stream and can simultaneously:- Make it available for RTMP playback to other clients
- Generate HLS segments on disk for HTTP delivery
- Generate DASH segments on disk for adaptive streaming
- Record the stream to FLV files
- Relay (push/pull) the stream to other RTMP servers
- Execute shell commands or send HTTP callbacks on stream events
- The HTTP block serves the generated HLS/DASH files, exposes XML statistics, and provides a control API for runtime stream management.
The module supports H.264, H.265/HEVC, VP6, VP9, Sorenson H.263 video codecs and AAC, MP3, Speex, Opus audio codecs.
Installation
RHEL, CentOS, AlmaLinux, Rocky Linux
sudo dnf install https://extras.getpagespeed.com/release-latest.rpm
sudo dnf install nginx-module-rtmp
After installation, load the module by adding the following line at the top of /etc/nginx/nginx.conf, before the events block:
load_module modules/ngx_rtmp_module.so;
For more details on the RPM package, see the module page at nginx-extras.getpagespeed.com.
Debian and Ubuntu
First, set up the GetPageSpeed APT repository, then install:
sudo apt-get update
sudo apt-get install nginx-module-rtmp
For more details, see the APT module page.
On Debian/Ubuntu, the package handles module loading automatically. No
load_moduledirective is needed.
Basic Configuration
Here is a minimal working configuration that accepts RTMP streams and makes them available for RTMP playback:
load_module modules/ngx_rtmp_module.so;
worker_processes auto;
events {
worker_connections 1024;
}
rtmp {
server {
listen 1935;
application live {
live on;
}
}
}
http {
include mime.types;
default_type application/octet-stream;
server {
listen 80;
}
}
Publishing a Test Stream
Use FFmpeg to publish a test stream:
ffmpeg -re -f lavfi -i "testsrc=size=1280x720:rate=30" \
-f lavfi -i "sine=frequency=440:sample_rate=44100" \
-c:v flv -c:a aac -f flv rtmp://your-server/live/mystream
Note: The
-c:v flvoption uses the built-in Sorenson H.263 codec that works everywhere. For H.264 output, replace it with-c:v libx264 -preset ultrafast -tune zerolatency. On RHEL-based systems,libx264requires FFmpeg from RPM Fusion.
To publish an existing video file:
ffmpeg -re -i input.mp4 -c copy -f flv rtmp://your-server/live/mystream
Verifying the Stream
Play the RTMP stream with FFplay or VLC:
ffplay rtmp://your-server/live/mystream
Or use ffprobe to check stream metadata without playing:
ffprobe rtmp://your-server/live/mystream
HLS Output
HLS is the most widely supported streaming format across devices. The NGINX RTMP module generates HLS playlists and segments automatically from the incoming RTMP stream. You then serve those files over standard HTTP.
load_module modules/ngx_rtmp_module.so;
worker_processes auto;
events {
worker_connections 1024;
}
rtmp {
server {
listen 1935;
application live {
live on;
hls on;
hls_path /var/cache/nginx/hls;
hls_fragment 3s;
hls_playlist_length 30s;
hls_cleanup on;
}
}
}
http {
include mime.types;
default_type application/octet-stream;
server {
listen 80;
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /var/cache/nginx;
add_header Cache-Control no-cache;
add_header Access-Control-Allow-Origin *;
}
}
}
Create the output directory before starting NGINX:
sudo mkdir -p /var/cache/nginx/hls
sudo chown nginx:nginx /var/cache/nginx/hls
The HLS playback URL is:
http://your-server/hls/mystream.m3u8
With hls_nested on, each stream gets its own subdirectory. In that case the URL becomes:
http://your-server/hls/mystream/index.m3u8
HLS Directives Reference
| Directive | Default | Description |
|---|---|---|
hls |
off |
Enable HLS output |
hls_path |
β | Directory for .m3u8 playlists and .ts segments |
hls_fragment |
5s |
Target duration of each MPEG-TS segment |
hls_max_fragment |
fraglen * 10 |
Maximum segment duration (safety cap) |
hls_playlist_length |
30s |
Total duration of segments kept in the playlist |
hls_cleanup |
on |
Automatically delete old segments |
hls_continuous |
on |
Continue segment numbering across publisher reconnects |
hls_nested |
off |
Create a subdirectory per stream name |
hls_type |
live |
Playlist type: live or event |
hls_fragment_naming |
sequential |
Fragment naming: sequential or system (Unix timestamp) |
hls_fragment_slicing |
plain |
Slicing mode: plain, aligned, or keyframe |
hls_sync |
2ms |
Audio/video synchronization threshold |
hls_muxdelay |
700ms |
MPEG-TS muxing delay |
hls_max_audio_delay |
300ms |
Maximum audio delay before forcing a segment |
hls_audio_buffer_size |
1m |
Audio buffer size |
hls_base_url |
β | Base URL prefix for segment references in playlist |
hls_variant |
β | Define adaptive bitrate variants (multi-bitrate HLS) |
HLS Encryption (AES-128)
The NGINX RTMP module supports AES-128 encryption for HLS segments. This prevents unauthorized playback without the encryption key:
application live {
live on;
hls on;
hls_path /var/cache/nginx/hls;
hls_fragment 3s;
hls_playlist_length 30s;
hls_keys on;
hls_key_path /var/cache/nginx/hls-keys;
hls_key_url /keys/;
hls_fragments_per_key 10;
}
Then serve the encryption keys over HTTP:
location /keys {
alias /var/cache/nginx/hls-keys;
}
| Directive | Default | Description |
|---|---|---|
hls_keys |
off |
Enable AES-128 encryption |
hls_key_path |
hls_path |
Directory where encryption keys are stored |
hls_key_url |
β | URL prefix for keys in the playlist |
hls_fragments_per_key |
0 |
Rotate encryption key every N fragments (0 = one key per session) |
DASH Output
DASH (Dynamic Adaptive Streaming over HTTP) is the ISO standard for adaptive bitrate streaming. The NGINX RTMP module generates DASH manifests and segments alongside HLS:
rtmp {
server {
listen 1935;
application live {
live on;
dash on;
dash_path /var/cache/nginx/dash;
dash_fragment 3s;
dash_playlist_length 30s;
dash_cleanup on;
}
}
}
http {
server {
listen 80;
location /dash {
types {
application/dash+xml mpd;
video/mp4 mp4;
}
root /var/cache/nginx;
add_header Cache-Control no-cache;
add_header Access-Control-Allow-Origin *;
}
}
}
DASH playback URL:
http://your-server/dash/mystream.mpd
DASH Directives Reference
| Directive | Default | Description |
|---|---|---|
dash |
off |
Enable DASH output |
dash_path |
β | Directory for .mpd manifests and segments |
dash_fragment |
5s |
Target segment duration |
dash_playlist_length |
30s |
Manifest window duration |
dash_cleanup |
off |
Automatically delete old segments |
dash_nested |
off |
Create a subdirectory per stream |
Stream Recording
The NGINX RTMP module records live streams to FLV files on disk. This is useful for archiving broadcasts, building a VOD library, or compliance recording.
application live {
live on;
record all;
record_path /var/cache/nginx/recordings;
record_suffix -%Y%m%d-%H%M%S.flv;
record_unique on;
}
Create the recordings directory with correct permissions:
sudo mkdir -p /var/cache/nginx/recordings
sudo chown nginx:nginx /var/cache/nginx/recordings
Recording Directives Reference
| Directive | Default | Description |
|---|---|---|
record |
off |
What to record: off, all, audio, video, keyframes, or manual |
record_path |
β | Directory for recorded FLV files |
record_suffix |
.flv |
File suffix (supports strftime format codes) |
record_unique |
off |
Append a timestamp to prevent overwriting |
record_append |
off |
Append to existing file instead of creating new one |
record_lock |
off |
Lock the file with fcntl while recording |
record_max_size |
0 |
Maximum file size (0 = unlimited) |
record_max_frames |
0 |
Maximum number of frames per file |
record_interval |
β | Split recording into files at this interval |
record_notify |
off |
Send HTTP callback when recording starts/stops |
Named Recorders
For advanced recording scenarios β for example, recording video-only and audio-only in separate files β use named recorder blocks:
application live {
live on;
recorder video_only {
record video;
record_path /var/cache/nginx/recordings/video;
record_suffix -%Y%m%d-%H%M%S.flv;
}
recorder audio_only {
record audio;
record_path /var/cache/nginx/recordings/audio;
record_suffix -%Y%m%d-%H%M%S.flv;
}
}
You can control named recorders at runtime via the control API.
Access Control and Security
IP-Based Access Control
The NGINX RTMP module includes built-in allow and deny directives for the RTMP block. You can restrict who publishes and who plays:
application live {
live on;
# Only allow publishing from trusted IPs
allow publish 10.0.0.0/8;
allow publish 192.168.0.0/16;
allow publish 127.0.0.1;
deny publish all;
# Allow playback from anywhere
allow play all;
}
The allow and deny directives accept an optional publish or play qualifier. Without it, the rule applies to both publishing and playback.
Stream Key Authentication via HTTP Callbacks
For flexible authentication, use the on_publish and on_play callback directives. When a client connects, the module sends an HTTP request to your backend. If the backend returns HTTP 2xx, the connection proceeds. Any other response code rejects it.
application live {
live on;
on_publish http://127.0.0.1:8080/auth/publish;
on_play http://127.0.0.1:8080/auth/play;
notify_method get;
}
The callback URL receives query parameters including the stream name, client addr, and app name. Your backend can validate stream keys, enforce user limits, or log activity.
Tip: Use an IP address (e.g.,
127.0.0.1) rather thanlocalhostin callback URLs to avoid DNS resolution issues, especially on IPv6-enabled systems.
Connection Limits
Prevent resource exhaustion by limiting concurrent RTMP connections per application:
application live {
live on;
max_connections 500;
}
The max_connections directive uses shared memory, so it works correctly across all worker processes.
HTTP Callbacks (Notify)
Beyond authentication, the notify module provides a comprehensive event system. Your backend receives HTTP requests for every significant stream event:
application live {
live on;
on_connect http://127.0.0.1:8080/api/connect;
on_disconnect http://127.0.0.1:8080/api/disconnect;
on_publish http://127.0.0.1:8080/api/publish;
on_publish_done http://127.0.0.1:8080/api/publish_done;
on_play http://127.0.0.1:8080/api/play;
on_play_done http://127.0.0.1:8080/api/play_done;
on_record_done http://127.0.0.1:8080/api/record_done;
on_done http://127.0.0.1:8080/api/done;
on_update http://127.0.0.1:8080/api/update;
notify_method get;
notify_update_timeout 10s;
notify_update_strict on;
}
Notify Directives Reference
| Directive | Default | Description |
|---|---|---|
on_connect |
β | Called when a client connects to the RTMP server |
on_disconnect |
β | Called when a client disconnects |
on_publish |
β | Called when a publisher starts streaming |
on_publish_done |
β | Called when a publisher stops streaming |
on_play |
β | Called when a viewer starts playback |
on_play_done |
β | Called when a viewer stops playback |
on_record_done |
β | Called when recording finishes |
on_done |
β | Called on both publish_done and play_done |
on_update |
β | Called periodically during an active stream |
notify_method |
post |
HTTP method for callbacks: get or post |
notify_update_timeout |
30s |
Interval between on_update callbacks |
notify_update_strict |
off |
Disconnect the client if on_update callback returns non-2xx |
notify_relay_redirect |
off |
Follow redirects from notify responses for relay |
The on_update and notify_update_strict combination is particularly powerful: your backend can disconnect any client at any time by returning a non-2xx response to the periodic update check. This enables real-time access revocation without reloading NGINX.
Exec Hooks
The exec module runs shell commands when stream events occur. This is useful for triggering transcoding pipelines, sending alerts, updating dashboards, or managing external services.
application live {
live on;
exec_publish /usr/bin/logger -t nginx-rtmp "stream $name published by $addr";
exec_publish_done /usr/bin/logger -t nginx-rtmp "stream $name ended";
}
Transcoding with FFmpeg
A common use case is transcoding an incoming stream to multiple bitrates:
application live {
live on;
exec ffmpeg -i rtmp://localhost/live/$name
-c:v libx264 -preset veryfast -b:v 1500k -c:a aac -b:a 128k
-f flv rtmp://localhost/hls/$name_720p
-c:v libx264 -preset veryfast -b:v 800k -s 640x360 -c:a aac -b:a 96k
-f flv rtmp://localhost/hls/$name_360p;
}
application hls {
live on;
hls on;
hls_path /var/cache/nginx/hls;
hls_nested on;
hls_cleanup on;
}
Exec Directives Reference
| Directive | Description |
|---|---|
exec |
Run command when a stream is published (the streaming process itself) |
exec_static |
Run command at NGINX startup (not tied to any stream) |
exec_publish |
Run command when publishing begins |
exec_publish_done |
Run command when publishing ends |
exec_play |
Run command when playback begins |
exec_play_done |
Run command when playback ends |
exec_push |
Run command when a push relay starts |
exec_pull |
Run command when a pull relay starts |
exec_record_done |
Run command when recording finishes |
respawn |
Restart the command if it exits (default: on) |
respawn_timeout |
Wait time before respawning (default: 5s) |
exec_kill_signal |
Signal to send when stopping: term or kill (default: kill) |
The exec directive (without a suffix) is special β it runs as a child process of the stream. The command receives the RTMP stream URL via variables like $name, $app, and $addr. When the stream ends, the process is killed with the signal set by exec_kill_signal. This is how the transcoding example above works: FFmpeg runs as long as the stream is live.
Stream Relay: Push and Pull
For scalable streaming infrastructure, the NGINX RTMP module supports relay between servers. This enables origin/edge architectures where one server receives the stream and edges distribute copies to viewers.
Push Relay
Forward a published stream to downstream servers automatically:
application live {
live on;
push rtmp://10.0.0.2/live;
push rtmp://10.0.0.3/live;
push_reconnect 2s;
}
You can also push to external platforms like YouTube or Twitch:
application live {
live on;
# Restream to YouTube
push rtmp://a.rtmp.youtube.com/live2/YOUR-STREAM-KEY;
# Restream to Twitch
push rtmp://live.twitch.tv/app/YOUR-STREAM-KEY;
}
Pull Relay
Pull a stream from an upstream server when a viewer requests it:
application edge {
live on;
pull rtmp://10.0.0.1/live;
pull_reconnect 2s;
}
Relay Directives Reference
| Directive | Default | Description |
|---|---|---|
push |
β | Push stream to a remote RTMP server |
pull |
β | Pull stream from a remote RTMP server |
push_reconnect |
3s |
Reconnect interval for push relay |
pull_reconnect |
3s |
Reconnect interval for pull relay |
relay_buffer |
5s |
Buffer duration for relay connections |
session_relay |
off |
Relay individual client sessions instead of streams |
Multi-Worker Streaming with Auto-Push
By default, each NGINX worker process maintains its own independent RTMP state. A stream published to one worker is invisible to viewers connected to another worker. The rtmp_auto_push directive solves this by relaying streams between workers automatically through Unix sockets.
load_module modules/ngx_rtmp_module.so;
worker_processes auto;
rtmp_auto_push on;
rtmp_auto_push_reconnect 1s;
rtmp_socket_dir /tmp;
events {
worker_connections 1024;
}
rtmp {
server {
listen 1935;
application live {
live on;
}
}
}
| Directive | Default | Description |
|---|---|---|
rtmp_auto_push |
off |
Enable automatic stream relay between workers |
rtmp_auto_push_reconnect |
100ms |
Reconnect interval for inter-worker relay |
rtmp_socket_dir |
/tmp |
Directory for Unix sockets used by auto-push |
Important: These three directives must appear at the top level of
nginx.conf(outside all blocks), not insidertmp {}. Thertmp_socket_dirmust be writable by the NGINX worker process. The default/tmpworks out of the box. If you change it to another directory (e.g.,/var/run), ensure the NGINX worker user has write permission, otherwise workers will crash withworker_socket bind failed (Permission denied).
Statistics and Monitoring
The NGINX RTMP module provides a built-in XML statistics endpoint that exposes information about active streams, connected clients, bandwidth usage, and codec metadata. This is an HTTP-side directive that goes in the http {} block:
http {
server {
listen 80;
location /stat {
rtmp_stat all;
rtmp_stat_stylesheet stat.xsl;
}
}
}
What the Stats Endpoint Returns
The XML response includes:
- NGINX and module version information
- Uptime and total bytes in/out
- Per-application stream listings
- Per-stream data β stream name, publish time, bandwidth, client count
- Client details β IP address, connection time, publish/play state, dropped frames
- Codec metadata β video resolution, frame rate, codec name, audio sample rate
Example XML output (abbreviated):
<rtmp>
<nginx_version>1.28.2</nginx_version>
<nginx_rtmp_version>1.1.4</nginx_rtmp_version>
<uptime>3600</uptime>
<server>
<application>
<name>live</name>
<live>
<stream>
<name>mystream</name>
<bw_in>524288</bw_in>
<meta>
<video>
<width>1280</width>
<height>720</height>
<frame_rate>30</frame_rate>
<codec>H264</codec>
</video>
<audio>
<codec>AAC</codec>
<sample_rate>44100</sample_rate>
</audio>
</meta>
<nclients>42</nclients>
</stream>
</live>
</application>
</server>
</rtmp>
Tip: For machine-readable JSON statistics, consider the NGINX HTTP-FLV module which adds a
rtmp_stat_format jsonoption while maintaining full compatibility with this moduleβs configuration.
Restricting Stats Access
Always restrict access to the statistics endpoint:
location /stat {
rtmp_stat all;
allow 127.0.0.1;
allow 10.0.0.0/8;
deny all;
}
Stream Control API
The control module provides an HTTP API for runtime stream management. You can drop clients, start and stop recording, and redirect streams without reloading NGINX.
http {
server {
listen 80;
location /control {
rtmp_control all;
allow 127.0.0.1;
deny all;
}
}
}
Control API Endpoints
Drop a publisher:
curl "http://localhost/control/drop/publisher?app=live&name=mystream"
Drop a specific client by IP:
curl "http://localhost/control/drop/client?app=live&name=mystream&addr=10.0.0.5"
Start recording (requires record manual in the application):
curl "http://localhost/control/record/start?app=live&name=mystream&rec=myrecorder"
Stop recording:
curl "http://localhost/control/record/stop?app=live&name=mystream&rec=myrecorder"
Redirect a stream to a different application:
curl "http://localhost/control/redirect/publisher?app=live&name=mystream&newname=backup"
Security note: Always restrict the control endpoint with
allow/denydirectives. Leaving it open allows anyone to drop streams or manipulate recordings.
Custom RTMP Logging
The module has its own access logging for RTMP connections, separate from the HTTP access log. Define a custom log format and assign it to an application:
rtmp {
log_format streaming '$remote_addr [$time_local] $command "$app" "$name" '
'$bytes_received $bytes_sent "$flashver"';
server {
listen 1935;
application live {
live on;
access_log /var/log/nginx/rtmp-access.log streaming;
}
}
}
Available Log Variables
| Variable | Description |
|---|---|
$remote_addr |
Client IP address |
$remote_port |
Client port |
$connection |
Connection serial number |
$time_local |
Local time in standard format |
$command |
RTMP command (PUBLISH, PLAY, etc.) |
$app |
Application name |
$name |
Stream name |
$args |
Stream query arguments |
$bytes_received |
Bytes received from client |
$bytes_sent |
Bytes sent to client |
$flashver |
Client software version string |
$session_readable_time |
Human-readable session duration |
$session_time |
Session duration in seconds |
$pageurl |
Page URL from which the client connected |
Example log output:
127.0.0.1 [21/Mar/2026:00:05:01 +0800] PUBLISH "live" "teststream" 970222 409 "FMLE/3.0 (compatible; Lavf61.7."
Performance Tuning
Core Directives
Fine-tune RTMP connection handling at the server level:
rtmp {
server {
listen 1935;
chunk_size 4096;
max_streams 32;
max_message 1m;
out_queue 256;
out_cork 32;
timeout 60s;
ping 30s;
ping_timeout 15s;
application live {
live on;
}
}
}
| Directive | Default | Description |
|---|---|---|
chunk_size |
4096 |
RTMP chunk size in bytes. Larger values reduce overhead; smaller values reduce latency |
max_streams |
32 |
Maximum number of RTMP multiplexed streams per connection |
max_message |
1m |
Maximum RTMP message size. Increase for high-bitrate streams |
out_queue |
256 |
Output message queue length per connection |
out_cork |
queue/8 |
Number of messages to accumulate before sending |
timeout |
60s |
Connection timeout for idle clients |
ping |
60s |
RTMP ping interval to detect dead connections |
ping_timeout |
30s |
Time to wait for a ping response before disconnecting |
ack_window |
5000000 |
RTMP acknowledgement window size in bytes |
buflen |
1000ms |
Buffer length for stream data |
Live Stream Quality Directives
application live {
live on;
wait_key on;
wait_video on;
interleave on;
meta copy;
sync 300ms;
buffer 1s;
drop_idle_publisher 10s;
idle_streams on;
}
| Directive | Default | Description |
|---|---|---|
wait_key |
off |
Wait for a keyframe before sending data to viewers |
wait_video |
off |
Wait for video data before starting playback |
interleave |
off |
Interleave audio and video packets for smoother delivery |
meta |
on |
Metadata handling: on (parsed), copy (passthrough), or off |
sync |
300ms |
Audio/video synchronization tolerance |
buffer |
0 |
Stream buffer duration |
drop_idle_publisher |
0 |
Drop publisher after N seconds of no data (0 = disabled) |
idle_streams |
on |
Keep idle streams available for viewers |
publish_notify |
off |
Send play_done/play notifications when publisher connects/disconnects |
play_restart |
off |
Restart playback when publisher reconnects |
play_time_fix |
on |
Fix playback timestamps for smoother experience |
HLS Tuning Tips
For lower HLS latency:
- Set
hls_fragmentto2s(the tradeoff is more HTTP requests per viewer) - Set
hls_playlist_lengthto6s(minimum 3 segments) - Use
hls_fragment_slicing alignedto slice on audio boundaries for smoother segments - Ensure your encoder sends keyframes at a regular interval matching
hls_fragment
For high-throughput HLS:
- Use
hls_nested onwhen serving many streams to distribute filesystem load - Place
hls_pathon a fast filesystem (SSD, tmpfs) for high segment write throughput - Set
hls_cleanup onto prevent disk space exhaustion
SELinux Configuration
On RHEL-based systems with SELinux enabled, you must allow NGINX to bind to the RTMP port and write to streaming directories. For a comprehensive guide, see our NGINX SELinux configuration article.
sudo semanage port -a -t http_port_t -p tcp 1935
If you use HTTP callbacks (on_publish, on_play, etc.), allow outbound HTTP connections:
sudo setsebool -P httpd_can_network_connect on
For HLS, DASH, or recording output directories, set the correct context:
sudo chcon -R -t httpd_sys_rw_content_t /var/cache/nginx/hls
sudo chcon -R -t httpd_sys_rw_content_t /var/cache/nginx/dash
sudo chcon -R -t httpd_sys_rw_content_t /var/cache/nginx/recordings
Note: The
nginx-module-rtmp-selinuxpackage is installed automatically alongside the module on supported systems. It provides a base SELinux policy for the RTMP port.
Troubleshooting
Stream Not Playing
- Verify the stream is publishing. Check the stats endpoint:
curl -s http://localhost/stat | grep -E "<(name|publishing|nclients)>"Look for your stream name with a
<publishing/>tag. -
Check the NGINX error log for connection issues:
tail -f /var/log/nginx/error.log - Verify port 1935 is open. RTMP uses TCP port 1935 by default:
ss -tlnp | grep 1935If you have a firewall, open the port:
sudo firewall-cmd --add-port=1935/tcp --permanent sudo firewall-cmd --reload
HLS Segments Not Generated
- Check directory permissions. The NGINX worker process (usually user
nginx) must be able to write tohls_path:ls -la /var/cache/nginx/hls/Fix with:
sudo chown nginx:nginx /var/cache/nginx/hls -
Check SELinux. Look for denials:
ausearch -m avc -ts recent | grep nginx - Verify the stream contains video. HLS requires at least a video track with keyframes. Audio-only streams will not generate segments.
HTTP Callbacks Failing
If on_publish or on_play callbacks fail with βPermission deniedβ in the error log, SELinux is blocking outbound connections:
sudo setsebool -P httpd_can_network_connect on
Also use an IP address instead of localhost in callback URLs to avoid IPv6 DNS resolution issues.
Recording Files Are Empty or Missing
- Check permissions on
record_pathβ thenginxuser must have write access. - Verify
recordis not set toofformanual. If set tomanual, you must start recording via the control API. - Check
record_max_sizeβ if set too low, files may rotate before any meaningful data is written.
Statistics Show Zero Clients
Each NGINX worker maintains independent RTMP state. If worker_processes is greater than 1, statistics only show clients connected to the worker handling the stats request. Enable rtmp_auto_push on for consistent behavior, or set worker_processes 1 for accurate global statistics.
Complete Production Configuration
Here is a comprehensive configuration for a production live streaming server that accepts RTMP streams and delivers them via HLS, with recording, access control, monitoring, and event hooks:
load_module modules/ngx_rtmp_module.so;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
events {
worker_connections 4096;
}
rtmp {
log_format streaming '$remote_addr [$time_local] $command "$app" "$name" '
'$bytes_received $bytes_sent "$session_readable_time"';
server {
listen 1935;
chunk_size 4096;
timeout 30s;
ping 30s;
ping_timeout 15s;
application live {
live on;
wait_key on;
wait_video on;
interleave on;
meta copy;
# Only allow publishing from trusted sources
allow publish 10.0.0.0/8;
allow publish 192.168.0.0/16;
deny publish all;
allow play all;
# HLS output
hls on;
hls_path /var/cache/nginx/hls;
hls_fragment 3s;
hls_playlist_length 30s;
hls_cleanup on;
hls_nested on;
# Stream recording
record all;
record_path /var/cache/nginx/recordings;
record_suffix -%Y%m%d-%H%M%S.flv;
record_unique on;
# Event hooks
exec_publish /usr/bin/logger -t nginx-rtmp "PUBLISH $name from $addr";
exec_publish_done /usr/bin/logger -t nginx-rtmp "UNPUBLISH $name";
access_log /var/log/nginx/rtmp-access.log streaming;
}
}
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
# HLS playback
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
alias /var/cache/nginx/hls;
add_header Cache-Control no-cache;
add_header Access-Control-Allow-Origin * always;
}
# Statistics (XML)
location /stat {
rtmp_stat all;
rtmp_stat_stylesheet stat.xsl;
allow 127.0.0.1;
allow 10.0.0.0/8;
deny all;
}
# Control API
location /control {
rtmp_control all;
allow 127.0.0.1;
deny all;
}
}
}
Conclusion
The NGINX RTMP module delivers a battle-tested live streaming solution built on the proven NGINX architecture. With native RTMP ingest, automatic HLS and DASH conversion, stream recording, push/pull relay for multi-server architectures, and a comprehensive event system through HTTP callbacks and exec hooks, it handles every live streaming requirement from a single configuration file. The built-in XML statistics and HTTP control API simplify integration with your monitoring and management infrastructure.
For lower-latency browser playback via HTTP-FLV, GOP caching, and JSON statistics, consider the NGINX HTTP-FLV module β a fully compatible superset. For video-on-demand streaming of pre-recorded files, see the NGINX VOD module guide. For MPEG-TS ingest instead of RTMP, check the NGINX MPEG-TS module.
The NGINX RTMP module is available as a pre-built package from the GetPageSpeed RPM repository and APT repository for RHEL-based, Debian, and Ubuntu systems. For the complete source code and updates, visit the nginx-rtmp-module GitHub repository.

