Skip to main content

NGINX

NGINX RTMP Module: Build a Live Streaming Server

by , , revisited on


We have by far the largest RPM repository with NGINX module packages and VMODs for Varnish. If you want to install NGINX, Varnish, and lots of useful performance/security software with smooth yum upgrades for production use, this is the repository for you.
Active subscription is required.

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 .m3u8 playlists and .ts segments on disk automatically.
  • DASH (Dynamic Adaptive Streaming over HTTP) — the open standard for adaptive bitrate streaming. The module generates .mpd manifests and .m4a/.m4v segments.
  • 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:

  1. A publisher (FFmpeg, OBS, Wirecast) connects to the RTMP server on port 1935 and sends a live audio/video stream.
  2. The application block 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
  3. 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_module directive 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 flv option 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, libx264 requires 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 than localhost in 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 inside rtmp {}. The rtmp_socket_dir must be writable by the NGINX worker process. The default /tmp works 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 with worker_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 json option 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/deny directives. 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_fragment to 2s (the tradeoff is more HTTP requests per viewer)
  • Set hls_playlist_length to 6s (minimum 3 segments)
  • Use hls_fragment_slicing aligned to 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 on when serving many streams to distribute filesystem load
  • Place hls_path on a fast filesystem (SSD, tmpfs) for high segment write throughput
  • Set hls_cleanup on to 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-selinux package is installed automatically alongside the module on supported systems. It provides a base SELinux policy for the RTMP port.

Troubleshooting

Stream Not Playing

  1. 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.

  2. Check the NGINX error log for connection issues:

    tail -f /var/log/nginx/error.log
    
  3. Verify port 1935 is open. RTMP uses TCP port 1935 by default:
    ss -tlnp | grep 1935
    

    If you have a firewall, open the port:

    sudo firewall-cmd --add-port=1935/tcp --permanent
    sudo firewall-cmd --reload
    

HLS Segments Not Generated

  1. Check directory permissions. The NGINX worker process (usually user nginx) must be able to write to hls_path:
    ls -la /var/cache/nginx/hls/
    

    Fix with: sudo chown nginx:nginx /var/cache/nginx/hls

  2. Check SELinux. Look for denials:

    ausearch -m avc -ts recent | grep nginx
    
  3. 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

  1. Check permissions on record_path — the nginx user must have write access.
  2. Verify record is not set to off or manual. If set to manual, you must start recording via the control API.
  3. 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.

D

Danila Vershinin

Founder & Lead Engineer

NGINX configuration and optimizationLinux system administrationWeb performance engineering

10+ years NGINX experience • Maintainer of GetPageSpeed RPM repository • Contributor to open-source NGINX modules

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

This site uses Akismet to reduce spam. Learn how your comment data is processed.