Site icon GetPageSpeed

NGINX SRT Module: Low-Latency Live Streaming

NGINX SRT Module: Low-Latency Live Streaming with Secure Reliable Transport

The NGINX SRT module transforms NGINX into a bidirectional gateway between the SRT (Secure Reliable Transport) protocol and TCP. If you run live streaming infrastructure, this module solves one of the most persistent headaches in the industry: delivering video reliably over unpredictable networks.

RTMP — the protocol most streamers still use — was designed in the early 2000s for Flash Player. It offers no built-in encryption, suffers from head-of-line blocking on lossy connections, and struggles with packet loss. When your stream crosses continents or traverses congested links, RTMP drops frames, introduces buffering, and leaves your viewers staring at a spinning wheel.

SRT fixes this. Developed by Haivision and now open source, SRT provides AES encryption, forward error correction, and selective retransmission — all over UDP at sub-second latency. The NGINX SRT module lets you accept SRT streams and forward them to TCP backends, or take TCP streams and transmit them over SRT. This article is a complete guide to installing, configuring, and deploying the NGINX SRT module in production.

How the NGINX SRT Module Works

The NGINX SRT module introduces a new top-level srt {} configuration block, similar to the existing stream {} block. Under the hood, it uses libsrt for all SRT protocol handling. Because libsrt requires its own event loop, the module runs SRT operations on a separate thread, using eventfd notifications to communicate with NGINX’s main event loop.

This architecture means SRT processing does not block NGINX’s HTTP or stream workers. The NGINX SRT module supports two proxy directions:

Both directions support bidirectional data transfer, which is essential for interactive streaming protocols that require two-way communication.

Installing the NGINX SRT Module

RHEL, CentOS, AlmaLinux, Rocky Linux

sudo dnf install https://extras.getpagespeed.com/release-latest.rpm
sudo dnf install nginx-module-srt

The package installs two dynamic module files:

Add the following to the top of /etc/nginx/nginx.conf to load both modules:

load_module modules/ngx_srt_module.so;
load_module modules/ngx_stream_srt_proxy_module.so;

If you only need SRT-to-TCP proxying (the more common use case), you can load just the core module:

load_module modules/ngx_srt_module.so;

Debian and Ubuntu

First, set up the GetPageSpeed APT repository, then install:

sudo apt-get update
sudo apt-get install nginx-module-srt

On Debian/Ubuntu, the package handles module loading automatically. No load_module directive is needed.

Module pages: RPM packages | APT packages

Basic Configuration: SRT-to-TCP Proxy

The most common use case for the NGINX SRT module is receiving SRT streams from encoders like OBS Studio or FFmpeg, then forwarding them to a TCP-based media server. Here is a minimal working configuration:

load_module modules/ngx_srt_module.so;

events {
    worker_connections 1024;
}

http {
    # Your existing HTTP configuration
    server {
        listen 80;
        location / {
            return 200 "ok\n";
        }
    }
}

srt {
    server {
        listen 4321;
        proxy_pass tcp://127.0.0.1:5678;
    }
}

In this configuration, NGINX listens for SRT connections on UDP port 4321 and forwards all received data to a TCP server at 127.0.0.1:5678. Your media server (Nimble Streamer, custom MPEG-TS receiver, etc.) listens on TCP port 5678.

To send a stream from OBS Studio, set the output URL to srt://your-server-ip:4321. To send a test stream with FFmpeg:

ffmpeg -re -i input.mp4 -c copy -f mpegts srt://your-server-ip:4321

Basic Configuration: TCP-to-SRT Proxy

The reverse direction uses NGINX’s stream {} block with the NGINX SRT module’s stream proxy component. This is useful when you want to accept TCP connections from a local application and forward them over SRT to a remote server:

load_module modules/ngx_srt_module.so;
load_module modules/ngx_stream_srt_proxy_module.so;

events {
    worker_connections 1024;
}

http {
    server {
        listen 80;
        location / {
            return 200 "ok\n";
        }
    }
}

stream {
    server {
        listen 5432;
        srt_proxy_pass srt://remote-server:4321;
        srt_proxy_connect_timeout 30s;
        srt_proxy_timeout 5m;
    }
}

Here, NGINX listens for TCP connections on port 5432 and proxies the data over SRT to remote-server:4321. The remote side must have an SRT listener (another NGINX SRT module instance, or any SRT-compatible receiver).

Encrypting SRT Streams

SRT provides built-in AES-128 and AES-256 encryption. Unlike RTMP, which requires wrapping the entire connection in TLS, the NGINX SRT module encrypts at the protocol level with minimal overhead.

Encryption on the SRT Listener (SRT → TCP)

srt {
    server {
        listen 4321;
        passphrase "a-strong-secret-at-least-10-chars";
        proxy_pass tcp://127.0.0.1:5678;
    }
}

The passphrase directive sets the encryption key. It must be between 10 and 79 characters. The sender (OBS, FFmpeg) must use the same passphrase:

ffmpeg -re -i input.mp4 -c copy -f mpegts \
    "srt://your-server-ip:4321?passphrase=a-strong-secret-at-least-10-chars"

Encryption on the TCP-to-SRT Proxy

stream {
    server {
        listen 5432;
        srt_proxy_pass srt://remote-server:4321;
        srt_proxy_passphrase "a-strong-secret-at-least-10-chars";
    }
}

The passphrase directives support variables, so you can derive the passphrase dynamically (for example, from the SRT stream ID).

Stream ID and Routing

SRT’s stream ID feature allows the sender to include metadata with each connection. This is commonly used to distinguish between different streams or authenticate users. The NGINX SRT module exposes the stream ID through the $stream_id variable.

Using Stream ID in Logging

srt {
    log_format srt_main "$remote_addr [$time_local] "
                        "stream=$stream_id status=$status "
                        "in=$bytes_received out=$bytes_sent "
                        "time=$session_time";

    server {
        listen 4321;
        access_log /var/log/nginx/srt.log srt_main;
        proxy_pass tcp://127.0.0.1:5678;
    }
}

Using Stream ID with Map for Dynamic Passphrase

The map directive works inside the srt {} block, similar to how it works in stream {}. You can configure encryption dynamically based on the stream ID:

srt {
    map $stream_id $srt_passphrase {
        ~^secure/   "encrypted-stream-key-1234";
        default     "";
    }

    server {
        listen 4321;
        passphrase $srt_passphrase;
        proxy_pass tcp://127.0.0.1:5678;
    }
}

In this example, streams whose stream ID starts with secure/ require encryption, while others connect without a passphrase.

An OBS user or FFmpeg sender sets the stream ID via the URL query:

ffmpeg -re -i input.mp4 -c copy -f mpegts \
    "srt://your-server-ip:4321?streamid=secure/channel1&passphrase=encrypted-stream-key-1234"

Using Stream ID in the TCP-to-SRT Direction

When proxying from TCP to SRT, you can set the stream ID that NGINX sends to the remote SRT server:

stream {
    server {
        listen 5432;
        srt_proxy_pass srt://remote-server:4321;
        srt_proxy_stream_id "live/channel1";
    }
}

The srt_proxy_stream_id directive supports variables, so you can build the stream ID dynamically from NGINX stream variables.

JSON Access Logging

For production monitoring and integration with log aggregation systems (Elasticsearch, Loki, Datadog), JSON-formatted logs are essential. The NGINX SRT module supports the escape=json option in log_format:

srt {
    log_format srt_json escape=json
        '{"time":"$time_iso8601",'
        '"remote_addr":"$remote_addr",'
        '"protocol":"$protocol",'
        '"stream_id":"$stream_id",'
        '"status":"$status",'
        '"bytes_sent":$bytes_sent,'
        '"bytes_received":$bytes_received,'
        '"session_time":"$session_time",'
        '"upstream_addr":"$upstream_addr",'
        '"upstream_connect_time":"$upstream_connect_time",'
        '"upstream_session_time":"$upstream_session_time",'
        '"peer_version":"$peer_version"}';

    server {
        listen 4321;
        access_log /var/log/nginx/srt_access.log srt_json;
        proxy_pass tcp://127.0.0.1:5678;
    }
}

This produces structured log entries like:

{"time":"2026-03-24T12:00:00+00:00","remote_addr":"203.0.113.10","protocol":"SRT","stream_id":"live/channel1","status":"200","bytes_sent":1048576,"bytes_received":52428800,"session_time":"300.123","upstream_addr":"127.0.0.1:5678","upstream_connect_time":"0.002","upstream_session_time":"300.120","peer_version":"1.5.4"}

You can also use the NGINX JSON Var module for more advanced structured logging scenarios across your HTTP and SRT configurations.

The open_log_file_cache directive can optimize log file access when using variables in log paths:

srt {
    open_log_file_cache max=16 inactive=60s min_uses=2 valid=30s;

    server {
        listen 4321;
        access_log /var/log/nginx/srt/$stream_id.log srt_json;
        proxy_pass tcp://127.0.0.1:5678;
    }
}

Performance Tuning

Latency Settings

The two most important directives for the NGINX SRT module’s latency control are recv_latency and send_latency. These control the SRT buffer window — the amount of time SRT will buffer packets before declaring them lost and requesting retransmission.

srt {
    server {
        listen 4321;

        # Lower latency for reliable networks
        recv_latency 80ms;
        send_latency 80ms;

        proxy_pass tcp://127.0.0.1:5678;
    }
}

The default of 120ms works well for most scenarios. For intercontinental streaming, increase to 200–500ms. For local datacenter use, you can go as low as 20–50ms.

Latency guidelines:

Buffer Sizes

The NGINX SRT module provides several buffer size controls:

srt {
    server {
        listen 4321;

        # SRT protocol buffers
        recv_buf 8192;        # SRT receive buffer (default: 8192 buffers)
        send_buf 8192;        # SRT send buffer (default: 8192 buffers)

        # UDP socket buffers
        recv_udp_buf 8192;    # UDP receive buffer (default: 8192)
        send_udp_buf 65536;   # UDP send buffer (default: 65536)

        # NGINX proxy buffers
        in_buf_size 128k;     # Client read buffer (default: 64k)
        proxy_buffer_size 128k; # Upstream read buffer (default: 64k)

        proxy_pass tcp://127.0.0.1:5678;
    }
}

For high-bitrate streams (e.g., 4K at 20+ Mbps), increase in_buf_size and proxy_buffer_size to 128k or 256k to avoid fragmenting large MPEG-TS packets.

Flow Control

The fc_pkts directive controls the maximum number of in-flight packets (sent but not yet acknowledged):

srt {
    server {
        listen 4321;
        fc_pkts 32000;  # Default: 25600
        proxy_pass tcp://127.0.0.1:5678;
    }
}

Increase this value for high-bandwidth, high-latency links. The default of 25600 is sufficient for most streams up to about 100 Mbps on typical internet connections.

Maximum Segment Size

The mss directive sets the maximum SRT packet size:

srt {
    server {
        listen 4321;
        mss 1400;  # Default: 1500
        proxy_pass tcp://127.0.0.1:5678;
    }
}

Lower this below 1500 if your network has a smaller MTU (common with VPNs, tunnels, or PPPoE connections). Setting MSS too high causes packet fragmentation, which defeats SRT’s error correction.

TCP Proxy Optimization

When proxying SRT to a TCP backend, enable TCP_NODELAY to reduce latency on the backend connection:

srt {
    server {
        listen 4321;
        proxy_tcp_nodelay on;
        proxy_pass tcp://127.0.0.1:5678;
    }
}

This disables Nagle’s algorithm on the TCP connection to the backend, which prevents the kernel from buffering small writes. For live streaming, this reduces end-to-end latency.

PROXY Protocol

If your TCP backend needs to know the original client IP address, enable the PROXY protocol:

srt {
    server {
        listen 4321;
        proxy_protocol on;
        proxy_pass tcp://127.0.0.1:5678;
    }
}

The backend must be configured to accept PROXY protocol headers. This is a standard mechanism also used by HAProxy, AWS ELB, and other NGINX proxy configurations.

Custom Proxy Header

The proxy_header directive lets you send arbitrary data to the TCP backend before the stream data begins:

srt {
    server {
        listen 4321;
        proxy_header "STREAM-ID: $stream_id\r\n";
        proxy_pass tcp://127.0.0.1:5678;
    }
}

This can pass metadata (such as the stream ID) to backends that do not support SRT natively but need to identify the incoming stream.

Cryptographic Utilities

The NGINX SRT module includes built-in base64 decoding and AES decryption directives. These are useful for handling encrypted stream IDs or authentication tokens:

srt {
    # Decode a base64-encoded stream ID
    set_decode_base64 $decoded_id $stream_id;

    # Decode a URL-safe base64 value
    set_decode_base64url $decoded_token $stream_id;

    # Decrypt an AES-256-CBC encrypted value
    # Arguments: $output base64_key base64_iv source
    set_aes_decrypt $decrypted_payload
        "BASE64_ENCODED_KEY_HERE"
        "BASE64_ENCODED_IV_HERE"
        $stream_id;

    server {
        listen 4321;
        proxy_pass tcp://127.0.0.1:5678;
    }
}

These directives execute in the srt {} context and are useful for building secure authentication schemes where the stream ID carries an encrypted token that the server decrypts to verify the sender’s identity.

Embedded Variables Reference

The NGINX SRT module provides a rich set of variables for logging, routing, and dynamic configuration.

Core Variables

Variable Description
$remote_addr Client IP address
$remote_port Client port
$server_addr Server address that accepted the connection
$server_port Server port that accepted the connection
$binary_remote_addr Client address in binary form (4 bytes for IPv4, 16 for IPv6)
$protocol Always evaluates to SRT
$stream_id SRT stream ID set by the sender
$peer_version libsrt version of the remote peer
$status Session status code
$bytes_sent Bytes sent to the client
$bytes_received Bytes received from the client
$session_time Session duration in seconds with millisecond precision
$connection Connection serial number
$hostname Host name of the NGINX server
$pid PID of the worker process
$msec Current time in seconds with millisecond precision
$time_iso8601 Local time in ISO 8601 format
$time_local Local time in Common Log Format
$nginx_version NGINX version

Upstream Variables

Variable Description
$upstream_addr IP address and port of the upstream TCP server
$upstream_bytes_sent Bytes sent to the upstream server
$upstream_bytes_received Bytes received from the upstream server
$upstream_connect_time Time to connect to upstream (seconds, ms precision)
$upstream_first_byte_time Time to receive first byte from upstream (seconds, ms precision)
$upstream_session_time Total upstream session duration (seconds, ms precision)

Complete Directive Reference

SRT Core Directives

Directive Default Context Description
srt { ... } main Top-level block for SRT configuration
server { ... } srt Server block within SRT context
listen server Address and UDP port to listen on
error_log logs/error.log error srt, server Error log file and level
passphrase srt, server Encryption passphrase (10–79 chars, supports variables)
recv_latency 120ms srt, server Receive-side latency buffer
send_latency 120ms srt, server Minimum peer latency hint
fc_pkts 25600 srt, server Maximum in-flight packets
mss 1500 srt, server Maximum segment size in bytes
recv_buf 8192 srt, server SRT receive buffer size
send_buf 8192 srt, server SRT send buffer size
recv_udp_buf 8192 srt, server UDP socket receive buffer
send_udp_buf 65536 srt, server UDP socket send buffer
in_buf_size 64k srt, server Client read buffer size
variables_hash_max_size 1024 srt Variables hash table max size
variables_hash_bucket_size 64 srt Variables hash table bucket size

SRT Proxy Directives (SRT → TCP)

Directive Default Context Description
proxy_pass server TCP backend address (tcp://host:port)
proxy_connect_timeout 60s srt, server Upstream connection timeout
proxy_timeout 10m srt, server Read/write idle timeout
proxy_buffer_size 64k srt, server Upstream read buffer size
proxy_protocol off srt, server Enable PROXY protocol to backend
proxy_tcp_nodelay srt, server Disable Nagle’s algorithm on backend
proxy_header srt, server Custom data sent before stream (supports variables)

SRT Map and Logging Directives

Directive Default Context Description
map srt Map variable values (like stream map)
map_hash_max_size 2048 srt Map hash table max size
map_hash_bucket_size 32\|64\|128 srt Map hash table bucket size
log_format srt Define access log format (supports escape=json)
access_log off srt, server Access log path and format
open_log_file_cache off srt, server Cache for log file descriptors

SRT Cryptographic Directives

Directive Context Description
set_decode_base64 srt Base64 decode a value into a variable
set_decode_base64url srt URL-safe base64 decode into a variable
set_aes_decrypt srt AES-256-CBC decrypt into a variable

Stream SRT Proxy Directives (TCP → SRT)

These directives are used inside the stream {} block and require loading ngx_stream_srt_proxy_module.so:

Directive Default Context Description
srt_proxy_pass server SRT upstream address (srt://host:port)
srt_proxy_connect_timeout 60s server SRT connection timeout
srt_proxy_timeout 10m server Read/write idle timeout
srt_proxy_buffer_size 64k server SRT read buffer size
srt_proxy_stream_id server Stream ID to send (supports variables)
srt_proxy_passphrase server Encryption passphrase (supports variables)

Production Example: Complete SRT Gateway

Here is a production-ready NGINX SRT module configuration combining ingest, egress, encryption, and logging:

load_module modules/ngx_srt_module.so;
load_module modules/ngx_stream_srt_proxy_module.so;

user nginx;
worker_processes auto;

error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    sendfile      on;
    keepalive_timeout 65;

    server {
        listen 80;
        location / {
            return 200 "SRT Gateway\n";
        }
    }
}

# Ingest: receive SRT streams, forward to media server
srt {
    log_format srt_json escape=json
        '{"time":"$time_iso8601",'
        '"remote_addr":"$remote_addr",'
        '"stream_id":"$stream_id",'
        '"status":"$status",'
        '"bytes_in":$bytes_received,'
        '"bytes_out":$bytes_sent,'
        '"session_time":"$session_time",'
        '"upstream":"$upstream_addr",'
        '"peer_version":"$peer_version"}';

    server {
        listen 4321;

        # Encryption
        passphrase "your-production-passphrase";

        # Latency tuning for internet delivery
        recv_latency 200ms;
        send_latency 200ms;

        # Performance
        fc_pkts 32000;
        in_buf_size 128k;
        proxy_buffer_size 128k;
        proxy_tcp_nodelay on;

        # Proxy to media server
        proxy_pass tcp://127.0.0.1:5678;
        proxy_connect_timeout 10s;
        proxy_timeout 5m;

        # Logging
        access_log /var/log/nginx/srt_access.log srt_json;
    }
}

# Egress: forward TCP streams out over SRT
stream {
    server {
        listen 5432;
        srt_proxy_pass srt://remote-receiver:4321;
        srt_proxy_passphrase "your-production-passphrase";
        srt_proxy_stream_id "live/output";
        srt_proxy_connect_timeout 10s;
        srt_proxy_timeout 5m;
        srt_proxy_buffer_size 128k;
    }
}

Firewall Configuration

SRT uses UDP, so you must open the appropriate UDP port in your firewall:

sudo firewall-cmd --permanent --add-port=4321/udp
sudo firewall-cmd --reload

If you also use the TCP-to-SRT proxy direction (stream block), open the TCP port as well:

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

On SELinux-enabled systems, you may also need to allow NGINX to bind to non-standard ports:

sudo semanage port -a -t http_port_t -p udp 4321

Troubleshooting

Module Not Loading

If you see unknown directive "srt", the module is not loaded. Verify the .so file exists and the load_module directive is at the top of nginx.conf:

ls -la /usr/lib64/nginx/modules/ngx_srt_module.so

Permission Denied on Port

SELinux may block NGINX from binding to non-standard UDP ports. Check the audit log and add the port:

sudo ausearch -m avc -ts recent | grep nginx
sudo semanage port -a -t http_port_t -p udp 4321

Connection Refused or Timeout

Verify the SRT port is listening on UDP (not TCP):

ss -ulnp | grep 4321

Ensure your firewall allows UDP traffic on the SRT port. SRT connections will silently fail if UDP is blocked.

Passphrase Mismatch

If the sender and receiver use different passphrases, the SRT handshake will fail silently. Both sides must use exactly the same passphrase string. Check your sender configuration:

# FFmpeg with passphrase
ffmpeg -re -i input.mp4 -c copy -f mpegts \
    "srt://server:4321?passphrase=your-production-passphrase"

High Latency or Buffering

If viewers experience excessive delay:

  1. Check recv_latency and send_latency — lower values reduce delay but increase packet loss risk
  2. Verify network RTT with ping — latency settings should be at least 3–4x the RTT
  3. Check fc_pkts — increase if you see SRT stats showing flow control window exhaustion
  4. Monitor with the access log — compare session_time and upstream_session_time for discrepancies

SRT vs RTMP: Why Upgrade?

Feature RTMP SRT
Protocol TCP UDP
Encryption None (requires RTMPS/TLS wrapper) Built-in AES-128/256
Packet loss recovery TCP retransmission (head-of-line blocking) Forward error correction + selective retransmission
Latency 1–5 seconds typical Sub-second possible
Firewall traversal TCP port 1935 Configurable UDP port
Stream ID No Yes (metadata per connection)
Open source No (Adobe spec) Yes (MPL 2.0 / Haivision)
OBS Studio support Yes Yes (since OBS 25.0)
Status Legacy, declining support Active development, growing adoption

For more on NGINX streaming, see also the NGINX RTMP module which provides HLS and DASH output capabilities. You can use both modules together — ingest via the NGINX SRT module for reliability, then use RTMP for HLS packaging and delivery to viewers.

Conclusion

The NGINX SRT module provides a production-grade SRT/TCP gateway that integrates naturally with NGINX’s configuration model. With its separate-thread architecture, AES encryption, and rich variable system, it handles the demands of professional live streaming infrastructure while remaining straightforward to configure and monitor.

The module source code is available on GitHub.

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

Exit mobile version