The NGINX HTTP-FLV module transforms NGINX into a full-featured media streaming server. It extends the well-known NGINX RTMP module with HTTP-FLV live playback, GOP caching for instant viewer experience, virtual host support, and JSON statistics. Whether you need a private live streaming platform, a surveillance system, or a broadcast relay network, the NGINX HTTP-FLV module provides everything you need.
Why Use the NGINX HTTP-FLV Module?
Traditional RTMP streaming requires Flash-based players on the client side. However, Flash reached end-of-life in December 2020. The NGINX HTTP-FLV module solves this problem by delivering live RTMP streams over standard HTTP as FLV containers. Modern JavaScript players like flv.js play these HTTP-FLV streams directly in the browser without any plugin.
Compared to HLS and DASH, HTTP-FLV offers significantly lower latency. HLS typically adds 10–30 seconds of delay due to segment buffering. In contrast, HTTP-FLV achieves sub-second latency because it delivers a continuous byte stream rather than discrete segments.
NGINX HTTP-FLV Module vs. nginx-rtmp-module
The NGINX HTTP-FLV module is a superset of nginx-rtmp-module. It includes every feature from the original module, plus several important additions:
- HTTP-FLV live streaming with HTTPS and chunked transfer encoding support
- GOP caching to eliminate the initial buffering wait for new viewers
- Virtual hosting — serve multiple domains from a single RTMP port
- JSON statistics — a machine-readable alternative to XML stats
- Audio-only streaming for radio-style broadcasts
SO_REUSEPORTsupport for better multi-worker load distribution
If you currently use nginx-rtmp-module, switching to the NGINX HTTP-FLV module is a drop-in upgrade. All existing RTMP configurations remain compatible.
How the NGINX HTTP-FLV Module Works
The module operates in two distinct NGINX contexts:
- RTMP block — handles ingest (publishing) and RTMP playback on port 1935
- HTTP block — serves HTTP-FLV, HLS, DASH streams and statistics via standard HTTP
When a publisher (such as FFmpeg or OBS) sends a live stream via RTMP, the module receives it in the rtmp block. Viewers can then consume that same stream through multiple protocols simultaneously:
- HTTP-FLV — via the
flv_livedirective in an HTTP location - RTMP — direct RTMP playback (native)
- HLS — the module generates
.m3u8playlists and.tssegments on disk - DASH — the module generates
.mpdmanifests and.m4a/.m4vsegments on disk
This multi-protocol architecture serves the same live stream to browsers, mobile apps, and legacy RTMP players — all from one NGINX instance. For more on HLS and DASH live streaming, see our dedicated guide.
Installation
RHEL, CentOS, AlmaLinux, Rocky Linux
sudo dnf install https://extras.getpagespeed.com/release-latest.rpm
sudo dnf install nginx-module-flv
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_http_flv_live_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-flv
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 serves them as HTTP-FLV:
load_module modules/ngx_http_flv_live_module.so;
worker_processes 1;
events {
worker_connections 1024;
}
rtmp {
server {
listen 1935;
application live {
live on;
}
}
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
server {
listen 80;
location /live {
flv_live on;
chunked_transfer_encoding on;
add_header Access-Control-Allow-Origin * always;
}
}
}
Publishing a Stream
Use FFmpeg to publish a test stream to the RTMP server:
ffmpeg -re -i input.mp4 -c copy -f flv rtmp://your-server/live/mystream
Or generate a test pattern for quick verification:
ffmpeg -re -f lavfi -i "testsrc=size=1280x720:rate=30" \
-f lavfi -i "sine=frequency=440:sample_rate=44100" \
-c:v libx264 -preset ultrafast -tune zerolatency \
-c:a aac -f flv rtmp://your-server/live/mystream
Note: The test pattern command above requires
libx264. On RHEL-based systems, you may need FFmpeg from RPM Fusion. As a quick alternative, replace-c:v libx264 -preset ultrafast -tune zerolatencywith-c:v flvto use the built-in Sorenson H.263 codec.
Playing the HTTP-FLV Stream
The HTTP-FLV playback URL follows this format:
http://your-server/live?port=1935&app=live&stream=mystream
You can test playback with curl:
curl -o output.flv "http://your-server/live?port=1935&app=live&stream=mystream"
Then verify the downloaded file is valid media:
ffprobe output.flv
Or use flv.js in a web page for browser playback:
<script src="https://cdn.jsdelivr.net/npm/flv.js/dist/flv.min.js"></script>
<video id="player" controls></video>
<script>
if (flvjs.isSupported()) {
var player = document.getElementById('player');
var flvPlayer = flvjs.createPlayer({
type: 'flv',
url: 'http://your-server/live?port=1935&app=live&stream=mystream'
});
flvPlayer.attachMediaElement(player);
flvPlayer.load();
flvPlayer.play();
}
</script>
RTMP playback remains available at rtmp://your-server/live/mystream.
GOP Caching for Instant Playback
Without GOP caching, a new viewer must wait for the next video keyframe before playback begins. Depending on encoder settings, this delay ranges from 1 to 10 seconds. The NGINX HTTP-FLV module’s GOP cache stores the most recent Group of Pictures in memory. As a result, new viewers start playback immediately.
rtmp {
server {
listen 1935;
application live {
live on;
gop_cache on;
gop_max_frame_count 2048;
gop_max_video_count 1024;
gop_max_audio_count 4096;
}
}
}
GOP Cache Directives
| Directive | Default | Description |
|---|---|---|
gop_cache |
off |
Enable or disable GOP caching |
gop_max_frame_count |
0 (unlimited) |
Maximum total frames in the cache |
gop_max_video_count |
0 (unlimited) |
Maximum video frames in the cache |
gop_max_audio_count |
0 (unlimited) |
Maximum audio frames in the cache |
Setting frame count limits prevents excessive memory usage on streams with long keyframe intervals. A good starting point is 2–3 GOPs worth of frames based on your encoder’s keyframe interval.
Multi-Format Output: HLS and DASH
The NGINX HTTP-FLV module can output HLS and DASH alongside HTTP-FLV simultaneously. Therefore, you can serve low-latency HTTP-FLV to desktop browsers while providing HLS for iOS/Safari and DASH for adaptive bitrate playback.
rtmp {
server {
listen 1935;
application live {
live on;
gop_cache on;
# HLS output
hls on;
hls_path /var/cache/nginx/hls;
hls_fragment 3s;
hls_playlist_length 30s;
hls_cleanup on;
hls_nested on;
# DASH output
dash on;
dash_path /var/cache/nginx/dash;
dash_fragment 3s;
dash_playlist_length 30s;
dash_cleanup on;
dash_nested on;
}
}
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
server {
listen 80;
# HTTP-FLV
location /live {
flv_live on;
chunked_transfer_encoding on;
add_header Access-Control-Allow-Origin * always;
}
# HLS
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;
}
# DASH
location /dash {
types {
application/dash+xml mpd;
}
alias /var/cache/nginx/dash;
add_header Cache-Control no-cache;
add_header Access-Control-Allow-Origin * always;
}
}
}
With hls_nested on and dash_nested on, each stream gets its own subdirectory. The hls_cleanup on and dash_cleanup on directives remove old segments automatically.
Playback URLs
- HLS: `http://your-server/hls/mystream/index.m3u8`
- DASH: `http://your-server/dash/mystream/index.mpd`
Key HLS Directives
| Directive | Default | Description |
|---|---|---|
hls |
off |
Enable HLS output |
hls_path |
— | Directory for HLS segments |
hls_fragment |
5s |
Duration of each segment |
hls_playlist_length |
30s |
Total playlist duration |
hls_cleanup |
on |
Remove old segments automatically |
hls_nested |
off |
Create a subdirectory per stream |
hls_continuous |
off |
Continue numbering across restarts |
hls_type |
live |
Playlist type: live or event |
hls_keys |
off |
Enable AES-128 encryption |
Stream Recording
The NGINX HTTP-FLV module records live streams to FLV files on disk. This is useful for archiving broadcasts, creating VOD content, or compliance recording.
rtmp {
server {
listen 1935;
application live {
live on;
gop_cache on;
record all;
record_path /var/recordings;
record_suffix -%Y%m%d-%H%M%S.flv;
record_unique on;
}
}
}
Recording Directives
| Directive | Default | Description |
|---|---|---|
record |
off |
What to record: off, all, audio, video, keyframes, or manual |
record_path |
— | Directory for recorded files |
record_suffix |
.flv |
File suffix (supports strftime format) |
record_unique |
off |
Append timestamp to prevent overwriting |
record_append |
off |
Append to existing file |
record_lock |
off |
Lock the file while writing |
record_max_size |
0 |
Maximum recording file size |
record_max_frames |
0 |
Maximum frames per file |
record_interval |
0 |
Split recording at this interval |
record_notify |
off |
Send notifications on start/stop |
You can also control recording at runtime via the control API. This lets you start and stop recording without reloading NGINX.
Real-Time JSON Statistics
The NGINX HTTP-FLV module provides a built-in statistics endpoint. It exposes information about active streams, connected clients, bandwidth, and codec metadata. Unlike nginx-rtmp-module’s XML-only output, this module adds native JSON support.
http {
server {
listen 80;
location /stat {
rtmp_stat all;
rtmp_stat_format json;
}
}
}
What the Stats Endpoint Returns
The JSON response includes the following data:
- Server uptime and version information
- Bandwidth metrics — total bytes in/out, current bandwidth
- Per-stream data — stream name, publish time, client count
- Client details — IP address, connection time, publish/play state
- Codec metadata — video resolution, frame rate, audio sample rate
Example JSON output (abbreviated):
{
"http-flv": {
"nginx_version": "1.28.2",
"nginx_http_flv_version": "1.2.13",
"uptime": 3600,
"servers": [{
"applications": [{
"name": "live",
"live": {
"streams": [{
"name": "mystream",
"bw_in": 524288,
"meta": {
"video": {"width": 1280, "height": 720, "codec": "H264"},
"audio": {"codec": "AAC", "sample_rate": 44100}
},
"nclients": 42,
"publishing": true
}]
}
}]
}]
}
}
You can integrate this endpoint with monitoring systems like Prometheus or Grafana.
For XML output with an XSL stylesheet, use:
location /stat {
rtmp_stat all;
rtmp_stat_stylesheet stat.xsl;
}
Stream Control API
The control module provides an HTTP API for runtime stream management. You can drop clients, start/stop recording, and redirect streams without reloading NGINX.
http {
server {
listen 80;
location /control {
rtmp_control all;
}
}
}
Control API Endpoints
Drop a publisher:
curl "http://your-server/control/drop/publisher?app=live&name=mystream"
Drop a specific client:
curl "http://your-server/control/drop/client?app=live&name=mystream&addr=10.0.0.5"
Start recording:
curl "http://your-server/control/record/start?app=live&name=mystream&rec=recorder1"
Stop recording:
curl "http://your-server/control/record/stop?app=live&name=mystream&rec=recorder1"
Security note: Always restrict access to the control endpoint. Leaving it open allows anyone to drop streams or manipulate recordings.
location /control {
rtmp_control all;
allow 127.0.0.1;
deny all;
}
Access Control and Security
IP-Based Access Control
The NGINX HTTP-FLV module includes built-in access control for the RTMP block. You can restrict who publishes and who plays:
rtmp {
server {
listen 1935;
application live {
live on;
# Only allow publishing from trusted IPs
allow publish 10.0.0.0/8;
allow publish 192.168.0.0/16;
deny publish all;
# Allow playback from anywhere
allow play all;
}
}
}
HTTP Callback Authentication
For flexible authentication, use the on_publish and on_play directives. The module sends an HTTP request to your backend when a client connects. If the backend returns HTTP 2xx, the connection proceeds. Any other response code rejects it.
rtmp {
server {
listen 1935;
application live {
live on;
on_publish http://127.0.0.1/auth/publish;
on_play http://127.0.0.1/auth/play;
on_publish_done http://127.0.0.1/auth/done;
notify_method get;
}
}
}
Tip: Use an IP address (e.g.,
127.0.0.1) rather thanlocalhostin callback URLs to avoid DNS resolution issues, especially with IPv6-enabled systems.
The callback URL receives query parameters including the stream name, client addr, and the app name. Your backend can validate stream keys, enforce rate limits, or log activity.
For additional security on the HTTP side, consider using signed URLs or time-limited token authentication to protect your streaming endpoints.
Connection Limits
Prevent resource exhaustion by limiting concurrent RTMP connections:
rtmp {
max_connections 500;
server {
listen 1935;
application live {
live on;
}
}
}
Securing the HTTP-FLV Endpoint
Since HTTP-FLV streams travel over standard HTTP, use NGINX’s built-in security features:
location /live {
flv_live on;
chunked_transfer_encoding on;
# IP restrictions
allow 10.0.0.0/8;
deny all;
}
For HTTPS streaming, configure SSL on the HTTP server block as usual.
Stream Relay: Push and Pull
For scalable streaming, the NGINX HTTP-FLV module supports relay between servers. This enables origin/edge architectures where one server receives the stream and edges distribute copies to local viewers.
Push Relay
Forward a published stream to downstream servers automatically:
rtmp {
server {
listen 1935;
application origin {
live on;
# Push to edge servers
push rtmp://edge1.example.com/live;
push rtmp://edge2.example.com/live;
push_reconnect 2s;
}
}
}
Pull Relay
Pull a stream from an upstream server when a viewer requests it:
rtmp {
server {
listen 1935;
application edge {
live on;
gop_cache on;
pull rtmp://origin.example.com/live;
pull_reconnect 2s;
}
}
}
Relay Directives
| Directive | Default | Description |
|---|---|---|
push |
— | Push stream to a remote server |
pull |
— | Pull stream from a remote server |
push_reconnect |
3s |
Reconnect interval for push |
pull_reconnect |
3s |
Reconnect interval for pull |
relay_buffer |
0 |
Relay buffer size |
session_relay |
off |
Relay individual client sessions |
You can also push to external platforms like YouTube or Twitch:
application live {
live on;
push rtmp://a.rtmp.youtube.com/live2/YOUR-STREAM-KEY;
}
Event Hooks with exec and notify
exec Directives
Execute shell commands when streams start or stop. Use this for triggering transcoding, sending alerts, or logging events.
rtmp {
server {
listen 1935;
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";
}
}
}
The following exec directives are available: exec, exec_push, exec_pull, exec_publish, exec_publish_done, exec_play, exec_play_done, exec_record_done, and exec_static.
notify Directives
Send HTTP callbacks for stream events:
| Directive | Description |
|---|---|
on_connect |
Called when a client connects |
on_disconnect |
Called when a client disconnects |
on_publish |
Called when streaming starts |
on_publish_done |
Called when publishing stops |
on_play |
Called when playback starts |
on_play_done |
Called when playback stops |
on_record_done |
Called when recording finishes |
on_update |
Called periodically during streaming |
notify_method |
HTTP method: get or post |
notify_update_timeout |
Interval between updates |
notify_update_strict |
Disconnect if callback fails |
Virtual Host Support
The NGINX HTTP-FLV module supports multiple RTMP virtual hosts on a single IP. Use the server_name directive within the rtmp block:
rtmp {
server {
listen 1935;
server_name stream1.example.com;
application live {
live on;
}
}
server {
listen 1935;
server_name stream2.example.com;
application live {
live on;
}
}
}
Important: Virtual host support works best with
worker_processes 1. In multi-worker mode, statistics may be inaccurate.
Performance Tuning
Worker Processes
For accurate statistics and reliable virtual hosting, use worker_processes 1. For multi-worker performance, enable reuseport:
rtmp {
server {
listen 1935 reuseport;
application live {
live on;
}
}
}
Stream Quality Settings
Fine-tune stream handling:
rtmp {
server {
listen 1935;
chunk_size 4096;
out_queue 256;
timeout 30s;
application live {
live on;
gop_cache on;
wait_key on;
wait_video on;
interleave on;
meta copy;
sync 300ms;
buffer 1s;
}
}
}
| Directive | Default | Description |
|---|---|---|
chunk_size |
4096 |
RTMP chunk size in bytes |
out_queue |
256 |
Output message queue size |
timeout |
60s |
Idle connection timeout |
wait_key |
off |
Wait for a keyframe before sending |
wait_video |
off |
Wait for video data before sending |
interleave |
off |
Interleave audio and video packets |
meta |
copy |
Metadata: copy, parse, or ignore |
sync |
300ms |
Audio/video sync tolerance |
buffer |
0 |
Stream buffer duration |
Socket Tuning
For high-bandwidth streaming, enable socket options on the listen directive:
rtmp {
server {
listen 1935 so_keepalive=on;
tcp_nodelay on;
}
}
Note: The
so_keepaliveoption must be specified as a parameter of thelistendirective (e.g.,listen 1935 so_keepalive=on). Using it as a standalone directive is deprecated.
RTMP Logging
The module has its own access logging for RTMP connections, separate from the HTTP access log:
rtmp {
log_format custom '$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 custom;
log_interval 30s;
}
}
}
Available log variables: $remote_addr, $time_local, $command, $app, $name, $args, $bytes_received, $bytes_sent, $session_readable_time, and $flashver.
SELinux Configuration
On RHEL-based systems with SELinux enabled, you must allow NGINX to bind to the RTMP port. 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.), you must also allow NGINX to make outbound HTTP connections:
sudo setsebool -P httpd_can_network_connect on
For custom recording or HLS/DASH output directories, set the SELinux context:
sudo chcon -R -t httpd_sys_rw_content_t /var/recordings
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
Troubleshooting
Stream Not Playing
- Verify the stream is publishing. Check the stats endpoint:
curl -s http://your-server/stat | python3 -m json.toolLook for your stream name with
"publishing": true. -
Check the NGINX error log for connection issues:
tail -f /var/log/nginx/error.log - Verify the HTTP-FLV URL format. All three parameters are required:
http://your-server/location?port=1935&app=APPLICATION&stream=STREAMNAME - Use GET requests only. HTTP-FLV returns
405 Not Allowedfor HEAD requests.
High Latency
- Enable GOP caching to reduce initial buffering.
- Lower the keyframe interval in your encoder to 1–2 seconds.
- Use
wait_key onandwait_video onfor clean playback. - For HLS, reduce
hls_fragmentto2sor less.
HTTP Callbacks Failing
If on_publish or on_play callbacks fail with “Permission denied” in the error log, SELinux is blocking outbound connections. Run:
sudo setsebool -P httpd_can_network_connect on
Also, use an IP address (e.g., http://127.0.0.1/auth`) instead oflocalhost` in callback URLs to avoid DNS resolution issues.
SELinux Denials
If NGINX fails to bind to port 1935 or write to directories, check for denials:
ausearch -m avc -ts recent | grep nginx
Apply the SELinux context changes shown above.
Statistics Show Zero Clients
If statistics show "nclients": 0 despite active viewers, check your worker count. Each worker maintains independent state. Set worker_processes 1 or accept per-worker statistics.
Complete Production Configuration
Here is a comprehensive configuration for a production streaming server:
load_module modules/ngx_http_flv_live_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';
server {
listen 1935 so_keepalive=on;
chunk_size 4096;
timeout 30s;
tcp_nodelay on;
application live {
live on;
gop_cache on;
gop_max_frame_count 2048;
gop_max_video_count 1024;
gop_max_audio_count 4096;
wait_key on;
wait_video on;
interleave on;
meta copy;
# Only allow publishing from trusted sources
allow publish 10.0.0.0/8;
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;
# Recording
record all;
record_path /var/recordings;
record_suffix -%Y%m%d-%H%M%S.flv;
record_unique on;
# Event hooks
on_publish http://127.0.0.1:8080/api/stream/start;
on_publish_done http://127.0.0.1:8080/api/stream/stop;
notify_method get;
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;
# HTTP-FLV live stream
location /live {
flv_live on;
chunked_transfer_encoding on;
add_header Access-Control-Allow-Origin * always;
}
# 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 (JSON)
location /stat {
rtmp_stat all;
rtmp_stat_format json;
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 HTTP-FLV module delivers a complete live streaming solution built on the proven NGINX architecture. With HTTP-FLV for low-latency browser playback, GOP caching for instant viewer experience, and simultaneous HLS/DASH output for broad device compatibility, it covers every live streaming need. Additionally, the built-in JSON statistics, HTTP callback system, and control API simplify integration with your monitoring infrastructure.
The module is available as a pre-built package from the GetPageSpeed repository for RHEL-based and Debian/Ubuntu systems. For video-on-demand streaming, see our NGINX VOD module guide.
For the complete source code and updates, visit the nginx-http-flv-module GitHub repository.

