Skip to main content

NGINX

NGINX HTTP-FLV Module: Build a Live Streaming Server

by ,


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 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_REUSEPORT support 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:

  1. RTMP block — handles ingest (publishing) and RTMP playback on port 1935
  2. 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_live directive in an HTTP location
  • RTMP — direct RTMP playback (native)
  • HLS — the module generates .m3u8 playlists and .ts segments on disk
  • DASH — the module generates .mpd manifests and .m4a/.m4v segments 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_module directive 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 zerolatency with -c:v flv to 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 detailsIP 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 than localhost in 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_keepalive option must be specified as a parameter of the listen directive (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

  1. Verify the stream is publishing. Check the stats endpoint:
    curl -s http://your-server/stat | python3 -m json.tool
    

    Look for your stream name with "publishing": true.

  2. Check the NGINX error log for connection issues:

    tail -f /var/log/nginx/error.log
    
  3. Verify the HTTP-FLV URL format. All three parameters are required:
    http://your-server/location?port=1935&app=APPLICATION&stream=STREAMNAME
    
  4. Use GET requests only. HTTP-FLV returns 405 Not Allowed for 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 on and wait_video on for clean playback.
  • For HLS, reduce hls_fragment to 2s or 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.

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.