yum upgrades for production use, this is the repository for you.
Active subscription is required.
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:
- SRT → TCP: Accepts incoming SRT connections and proxies the data to a TCP backend (e.g., a media server like Nimble Streamer, Wowza, or a custom MPEG-TS receiver).
- TCP → SRT: Accepts incoming TCP connections (via the
stream {}block) and proxies the data out over SRT to a remote SRT receiver.
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:
ngx_srt_module.so— The core SRT module (SRT listener, proxy to TCP, logging, map, variables)ngx_stream_srt_proxy_module.so— The stream SRT proxy module (TCP-to-SRT direction)
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_moduledirective 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:
- Local network (same datacenter): 20–50ms
- Same region (< 50ms RTT): 80–120ms (default)
- Cross-continent (100–200ms RTT): 200–500ms
- Satellite or high-jitter links: 500–2000ms
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:
- Check
recv_latencyandsend_latency— lower values reduce delay but increase packet loss risk - Verify network RTT with
ping— latency settings should be at least 3–4x the RTT - Check
fc_pkts— increase if you see SRT stats showing flow control window exhaustion - Monitor with the access log — compare
session_timeandupstream_session_timefor 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.
