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:
| Flag | Effect |
|---|---|
trust-address | Trust the SDP connection address (don't override with signaling source IP) |
replace-origin | Rewrite the SDP o= line with rtpengine's address |
replace-session-connection | Rewrite the session-level c= line |
ICE=remove | Strip ICE candidates (for SIP-to-SIP, not WebRTC) |
DTLS=passive | Force DTLS passive mode (for WebRTC endpoints) |
SRTP | Enable SRTP for this leg |
transcode-PCMU | Transcode to PCMU if needed |
record-call=yes | Enable 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:
- Receives DTLS from the WebRTC client and negotiates SRTP keys
- Decrypts SRTP from the WebRTC client
- Forwards plain RTP to the SIP endpoint
- 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
| Configuration | Concurrent sessions | Packet 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.




