Skip to content
rtpengine Deployment Guide: RTP Proxy at Scale
SBC

rtpengine Deployment Guide: RTP Proxy at Scale

Deploy rtpengine as a high-performance RTP proxy: kernel forwarding module, Kamailio integration, SRTP transcoding, multi-instance clustering, and capacity planning.

Tumarm Engineering9 min read

rtpengine Deployment Guide: RTP Proxy at Scale

rtpengine is the RTP proxy of choice for production SIP infrastructure. It replaces the older rtpproxy with kernel-space packet forwarding, SRTP/DTLS support, codec transcoding, and a gRPC/ng control protocol that Kamailio and OpenSIPS integrate natively. At scale — carrier interconnects, WebRTC gateways, SBCs — rtpengine handles tens of thousands of concurrent RTP sessions on commodity hardware. This guide covers installation, kernel module setup, Kamailio integration, and the operational details that documentation skips.

Why rtpengine Over Alternatives

The core advantage is the kernel forwarding module (xt_RTPENGINE). When a session is established, rtpengine installs packet forwarding rules directly into the Linux kernel's netfilter framework. Subsequent RTP packets are forwarded in kernel space without a context switch to userspace — effectively wire-speed forwarding at the cost of a single netfilter lookup.

Without the kernel module, rtpengine falls back to userspace forwarding: each RTP packet traverses recvmsg() → userspace → sendmsg(). This is still fast (~1 µs per packet on modern hardware) but limits throughput to roughly 300,000 packets/sec per core. With the kernel module, a 4-core server forwards over 2 million packets/sec — enough for 20,000+ concurrent audio sessions.

Installation

# Debian / Ubuntu — use the official Sipwise package repo
echo "deb http://packages.sipwise.com/spce bookworm main" \
    > /etc/apt/sources.list.d/sipwise.list

apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 0x... # Sipwise key
apt-get update
apt-get install ngcp-rtpengine

# Or build from source (for custom transcoding codecs)
apt-get install build-essential pkg-config libssl-dev libpcre3-dev \
    libjson-glib-dev libcurl4-openssl-dev libxmlrpc-c3-dev libglib2.0-dev

git clone https://github.com/sipwise/rtpengine.git
cd rtpengine
make
make install

Kernel Module Setup

# Install kernel headers for your running kernel
apt-get install linux-headers-$(uname -r)

# Build and load the kernel module
cd rtpengine/kernel-module
make
insmod xt_RTPENGINE.ko

# Make it persistent
cp xt_RTPENGINE.ko /lib/modules/$(uname -r)/kernel/net/netfilter/
depmod -a
echo "xt_RTPENGINE" >> /etc/modules

# Verify it loaded
lsmod | grep xt_RTPENGINE
# Expected: xt_RTPENGINE    16384  0

Verify kernel forwarding is active after rtpengine starts:

cat /proc/rtpengine/0/list
# Should show active kernel-forwarded sessions
# Empty = kernel module not loaded, falling back to userspace

Configuration File

# /etc/rtpengine/rtpengine.conf
[rtpengine]
# Network
interface = eth0/203.0.113.10
listen-ng = 127.0.0.1:2223
listen-tcp-ng = 127.0.0.1:2223

# Port range for RTP relay
port-min = 30000
port-max = 40000

# DTLS/SRTP
dtls-passive = yes
tls-certificate = /etc/ssl/certs/rtpengine.pem
tls-private-key = /etc/ssl/private/rtpengine.key

# Kernel forwarding (0 = use kernel module if available)
table = 0

# Logging
log-level = 5
log-facility = daemon
log-facility-rtcp = local0

# Performance
timeout = 60
silent-timeout = 3600
tos = 184                    # DSCP EF (0xB8) for RTP traffic

# Transcoding (requires ffmpeg libraries)
# codec-except = PCMU        # Uncomment to disable specific codecs

# Prometheus
prometheus = yes
prometheus-listen = 127.0.0.1:9900

# Homer SIPcapture
homer-ng = udp:127.0.0.1:9060
homer-protocol = 17
homer-id = 2002

The interface parameter format physical-interface/public-ip is critical for NAT traversal. rtpengine binds on the physical interface IP but advertises the public IP in SDP rewriting. Get this wrong and media flows to unreachable addresses.

Kamailio Integration

Kamailio controls rtpengine via the rtpengine module using the ng control protocol:

loadmodule "rtpengine.so"

modparam("rtpengine", "rtpengine_sock", "udp:127.0.0.1:2223")
modparam("rtpengine", "rtpengine_disable_tout", 20)
modparam("rtpengine", "rtpengine_retr", 5)
modparam("rtpengine", "rtpengine_tout_ms", 1000)

# For multiple rtpengine instances:
# modparam("rtpengine", "rtpengine_sock", "udp:10.0.1.10:2223 udp:10.0.1.11:2223")

request_route {
    if (is_method("INVITE")) {
        if (has_body("application/sdp")) {
            # Offer — rewrite SDP to proxy through rtpengine
            rtpengine_offer("trust-address replace-origin replace-session-connection");
        }
    }
    
    if (is_method("BYE") || is_method("CANCEL")) {
        rtpengine_delete();
    }
    
    t_relay();
}

onreply_route {
    if (t_check_status("18[0-9]") || t_check_status("2[0-9][0-9]")) {
        if (has_body("application/sdp")) {
            # Answer — complete the SDP rewrite
            rtpengine_answer("trust-address replace-origin replace-session-connection");
        }
    }
}

The flags string controls rtpengine behavior:

FlagEffect
trust-addressTrust the SDP connection address (don't override with signaling source IP)
replace-originRewrite the SDP o= line with rtpengine's address
replace-session-connectionRewrite the session-level c= line
ICE=removeStrip ICE candidates (for SIP-to-SIP, not WebRTC)
DTLS=passiveForce DTLS passive mode (for WebRTC endpoints)
SRTPEnable SRTP for this leg
transcode-PCMUTranscode to PCMU if needed
record-call=yesEnable call recording for this session

SRTP Transcoding: Plain SIP to WebRTC

The most common rtpengine use case is bridging plain SIP (with plain RTP) to WebRTC (which requires SRTP/DTLS). rtpengine handles the SRTP key exchange and media encryption transparently:

# For the WebRTC leg (DTLS-SRTP required)
rtpengine_offer("ICE=force DTLS=passive SDES-off");

# For the SIP leg (plain RTP)
rtpengine_answer("ICE=remove DTLS=off SDES-off");

With this configuration, rtpengine:

  1. Receives DTLS from the WebRTC client and negotiates SRTP keys
  2. Decrypts SRTP from the WebRTC client
  3. Forwards plain RTP to the SIP endpoint
  4. Encrypts in the reverse direction

This all happens in userspace (DTLS negotiation cannot be kernel-forwarded), but once the session is established and xt_RTPENGINE takes over, subsequent packets bypass userspace.

Multi-Instance Clustering

For high-availability and horizontal scaling, run multiple rtpengine instances and let Kamailio distribute sessions:

modparam("rtpengine", "rtpengine_sock",
    "udp:10.0.1.10:2223 udp:10.0.1.11:2223 udp:10.0.1.12:2223")

modparam("rtpengine", "rtpengine_disable_tout", 20)

Kamailio's rtpengine module does round-robin across healthy instances. If an instance stops responding, Kamailio marks it disabled and routes to the remaining instances after rtpengine_disable_tout seconds.

In-dialog requests (re-INVITE, UPDATE) must reach the same rtpengine instance that handled the original INVITE. Kamailio tracks this via the rtpengine_manage() function which reads the stored instance from the dialog:

# Use rtpengine_manage() for in-dialog requests
# It automatically selects the correct instance
if (has_totag()) {
    if (is_method("INVITE|UPDATE|ACK")) {
        rtpengine_manage("trust-address replace-origin");
    }
}

Capacity Planning

ConfigurationConcurrent sessionsPacket rate
Userspace only, 1 core~5,000~300K pps
Kernel module, 1 core~15,000~1M pps
Kernel module, 4 cores~50,000~3M pps
Kernel module, 16 cores~100,000+~8M pps

Memory is minimal: ~1 KB per session for the kernel forwarding table, ~50 KB per session for the userspace session record. A server with 16 GB RAM handles 100,000 concurrent sessions with memory to spare.

Bandwidth is the real constraint. Each audio session consumes ~100 Kbps bidirectional (G.711 / PCMU). 10,000 sessions = 1 Gbps throughput. Size your NIC and carrier bandwidth accordingly:

# Check current throughput
cat /proc/rtpengine/0/stats | grep bytes_forwarded
# Or via Prometheus: rtpengine_total_traffic_bytes

# Check kernel forwarding table size
cat /proc/rtpengine/0/list | wc -l

Call Recording via rtpengine

rtpengine supports inline call recording via the record-call flag. Recordings go to a local directory as PCAP files:

# rtpengine.conf
recording-dir = /var/spool/rtpengine-recordings
recording-method = pcap
recording-format = eth
# Kamailio: enable recording for specific calls
if ($avp(record_call) == "yes") {
    rtpengine_offer("trust-address replace-origin record-call=yes");
} else {
    rtpengine_offer("trust-address replace-origin record-call=no");
}

PCAP recordings include all RTP/RTCP packets. Import them into Wireshark with Telephony > VoIP Calls for playback and quality analysis. At scale, stream recordings directly to S3 using rtpengine's S3 output mode (available in enterprise builds) rather than accumulating PCAP files on the local filesystem.

rtpenginertp-proxysbcsrtpkamailiomedia-proxynat-traversal
Benchmark
BALI Pvt.Ltd
Brave BPO
Wave
SmartBrains BPO

Ready to build on carrier-grade voice?

Talk to a VoIP engineer — not a salesperson.

Schedule a Technical Call →