Business phone numbers and phone menus for the United States and Canada. FRES(510) 288-6288[email protected]Sign in

Home › Open-source VoIP › Kamailio

Open-source VoIP

Kamailio in depth: the SIP server that routes, and does nothing else.

Kamailio is a SIP signalling server: it registers phones, authenticates them, and decides where every SIP message goes, very fast. It never touches a voice packet. That narrowness is the whole point, and it is why Kamailio sits in front of Asterisk and FreeSWITCH rather than replacing them. This guide explains the machine from the inside, then builds a working edge configuration and walks through it.

Last checked 26 October 2026 · 38 cited sources · 30 min read

A high-throughput SIP proxy

What Kamailio is, and what it is not

The project's own repository puts it in one line: "Kamailio is an open source implementation of a SIP Signaling Server."3 The home page adds the scale claim, "able to handle thousands of call setups per second", and names the roles it plays: SIP load balancer, least cost routing, SIP firewall, edge proxy, SBC, registrar and location service, presence, WebRTC and IMS.1 The features page lists the classic SIP server roles: registrar, location, proxy, application and redirect.4

In plainer terms, Kamailio does four jobs on most deployments:

  • Registrar. Phones send REGISTER. Kamailio authenticates them and stores where each one can be reached.1314
  • Location server. When a call arrives for a user, it looks up that stored contact and rewrites the request towards the phone.13
  • Proxy. It forwards requests, statelessly or with full transaction state, and handles retransmissions, forking and failover.1011
  • Load balancer. It spreads calls over a pool of PBXs, media servers or carrier gateways and stops sending to the ones that fail.17

What it does not do is media. It plays no prompts, records no voicemail, bridges no audio, runs no IVR. When audio has to be relayed (usually because a phone is behind NAT), Kamailio drives a separate RTP proxy: the rtpengine module "enables media streams to be proxied via an RTP proxy", and the only proxy it is "currently known to work with" is Sipwise rtpengine.18 The home page is explicit about the division of labour: Kamailio "can be easily used for scaling up SIP-to-PSTN gateways, PBX systems or media servers like Asterisk, FreeSWITCH or SEMS".1

Kamailio decides where SIP messages go. Asterisk or FreeSWITCH decides what happens to the call once it gets there. If your problem is a phone menu, voicemail or a conference bridge, Kamailio is not the tool. If your problem is a thousand phones, three PBXs and no single point of failure, it probably is.

The performance figures the project publishes are its own, and undated: "used as load balancer in stateless mode, Kamailio can handle over 5000 call setups per second", and "on systems with 4GB memory, Kamailio can serve a population over 300 000 online subscribers".4 Treat them as order-of-magnitude claims from the project rather than a benchmark you can reproduce. We found no independent test with a published method to check them against.

It is written "in pure C for Unix/Linux-like systems",1 supports UDP, TCP, TLS and SCTP over IPv4 and IPv6, and WebSocket for WebRTC.4 The main licence is GPLv2; the repository notes that new contributions to the core and to several main modules (auth, corex, sl, tls, tm) must be made under the BSD licence.3

From SER to Kamailio: a 25-year lineage

Kamailio is older than most of the VoIP products built on it. The project's history page and home page give the dates:12

DateEvent
2 Sep 2001SIP Express Router (SER) started at the Fraunhofer FOKUS research institute in Berlin.
19 Sep 2002SER released as open source under GPLv2.
14 Jun 2005OpenSER created as a spin-off of SER; first release 0.9.4, forked from the SER 0.9.0 branch.
28 Jul 2008"due to trademark issues, OpenSER was renamed to Kamailio".
4 Nov 2008Kamailio and SER started the SIP Router project to merge their source trees again.
11 Jan 2010Kamailio 3.0.0, the first release built from the integrated trees.
20 Dec 2012Integration complete; the project keeps the name Kamailio.

Scroll the table sideways to see every column.

The history page gives the reason for the 2005 fork as differing views on "the management and development of the open source project (policy about contributions, release cycles and development effort)".2 OpenSER's 0.9.x to 1.5.x series ran independently of SER; from 3.0.0 on, releases came out of the merged SIP Router tree.2 The repository README adds that Fraunhofer FOKUS "is no longer actively involved in the evolution of the project" and that Kamailio "is now developed and managed by its world wide community".3

The 2008 rename is also where OpenSIPS comes from, and the two projects tell that story differently. We come back to it in the comparison with OpenSIPS below.

Why the history matters in practice: you will still find "ser" and "openser" in old module names, mailing-list answers and tutorials, and configurations written for 1.x or 3.x do not run unchanged on 6.x. When you search for help, filter by version.

Architecture: processes, shared memory and a compiled script

Three design decisions explain most of Kamailio's behaviour, good and bad.

It is multi-process. Kamailio forks a set of worker processes per listening socket. The children parameter is the "Number of children to fork for the UDP interfaces (one set for each interface - ip:port)", default 8, so three UDP listeners create three times that many processes.5 TCP and TLS get their own pool, tcp_children, which defaults to the same number as children when not set.5 Modules can add helper processes of their own; the default configuration, for example, gives usrloc a timer process with timer_procs.28 Since 6.0 you can also switch UDP receiving to a multi-threaded mode, globally or per socket,29 and 6.1 added an optional multi-threaded TLS mode.30

It manages its own memory, in two kinds. The memory troubleshooting guide describes "private memory - allocated for each Kamailio process", referred to as pkg, which needs no synchronisation, and "shared memory - allocated for entire Kamailio instances", referred to as shm, where "all processes use the same zone" and a mutex guards access.6 Defaults since 4.2.0 are 8 MB of pkg per process and 64 MB of shm; you change them on the command line with -M (pkg) and -m (shm).65 Anything every worker must see lives in shm: the transaction table, the registration cache when it is kept in memory, the hash tables of the htable module ("The hash table is stored in shared memory").22

In operation this is the first thing that bites. A busy registrar or a large htable runs out of 64 MB of shm long before it runs out of CPU, and the symptom is "no more memory" errors in syslog, not a crash with a clear cause.6 Size shm for your registration count and transaction rate from day one, and watch it with kamcmd mod.stats all shm.6

The native script is compiled once. The KEMI documentation is candid about the trade-off: the interpreter "was designed to precompile kamailio.cfg at startup, doing specific optimization for fast runtime operations", and adding reload "will result in losing those optimizations".7 So a change to native routing logic means a restart. Many global and module parameters can be changed at runtime through RPC commands,7 and 6.1 added RPC commands to get and set module parameters at runtime,30 but the routing logic itself is fixed until you restart, unless you use KEMI (below).

Kamailio is also, by default, transaction-stateful rather than call-stateful. The rr module documentation says it plainly: "Kamailio is basically only a transaction stateful proxy, without any dialog support build in."12 If you need to know which calls are up (to cap concurrent calls per customer, say) you load the dialog module, which exists precisely because "stateful" in tm "refers to transaction state, not dialog state".23

kamailio.cfg: three sections and a family of routes

The configuration file has three parts, in order: global parameters, then the modules section with loadmodule and modparam, then the routing blocks, "typically the biggest one".5 The routing blocks are where the logic lives, and there are several kinds, each triggered by a different event:5

BlockWhen it runsArmed by
request_route"executed for each SIP request"; the equivalent of main(). The only mandatory block.Always runs
route[NAME]A sub-route called like a function with route(NAME); can return a value read via $rc.Called explicitly
branch_route[NAME]Once for each outgoing branch of a forwarded request.t_on_branch()
failure_route[NAME]When every branch of a transaction got only negative replies (≥300). Works on the original request, not the reply.t_on_failure()
reply_routeFor every reply received from the network, run by the core before any module.Always runs if defined
onreply_route[NAME]Replies inside an active transaction, run by tm after reply_route.t_on_reply()
onsend_routeWhen a request is sent out; a limited command set.Always runs if defined
event_route[group:event]Core, module or timer events, such as xhttp:request or dispatcher:dst-down.The module that fires the event

Scroll the table sideways to see every column.

Two behaviours in that list catch out newcomers. First, the implicit action at the end of request_route is to drop the request: "To send a reply or forward the request, explicit actions (e.g., sl_send_reply(), forward(), t_relay()) must be called".5 A script that forgets to relay a method silently discards it. Second, sub-route return values follow their own rule: negative is false, positive is true, and 0 means exit. A sub-route with no explicit return returns the value of its last action, so the documentation recommends always returning explicitly.5

The preprocessor

Lines beginning with #! are not comments; they are preprocessor directives evaluated before parsing.5 The useful ones are #!define, #!ifdef / #!ifndef / #!else / #!endif, #!trydef ("add a define if not already defined"), #!redefine, and #!subst / #!substdef for search-and-replace inside strings. Kamailio predefines keywords such as KAMAILIO_6 and MOD_acc (true when that module is loaded).5 include_file pulls in another file before parsing; import_file does the same but does not fail if the file is missing.5

The cookbook lists three benefits: enabling and disabling features, conditional control where an if cannot go (global parameters and module settings), and speed, because nothing is evaluated at runtime.5 The default configuration shipped in the repository is built entirely this way: you turn on authentication, NAT handling, rtpengine or anti-flood protection with lines such as #!define WITH_AUTH and #!define WITH_NAT.28 It also does import_file "kamailio-local.cfg" so local overrides can live outside the main file.28

Our view: read that default kamailio.cfg end to end before writing your own. It runs to about 1,100 lines, ships with every release branch, and it encodes years of lessons about retransmissions, CANCEL handling and NAT that are easy to get wrong from scratch. The worked example below is a trimmed, re-purposed version of it.

KEMI: routing logic in Lua, Python or JavaScript

The native language is a domain-specific language with "initial design going back to years 2001-2002".7 It is fast, but awkward for anything that has to talk to external systems, and, as above, it cannot be reloaded. The Kamailio Embedded Interface (KEMI) framework, "added first in Kamailio v5.0.0", lets you write the routing blocks in a general-purpose language instead.7

With KEMI, kamailio.cfg keeps only the global parameters, module loading and module settings; "the routing blocks are now functions written in a KEMI supported scripting language".7 The supported languages and modules are JavaScript (app_jsdt), Lua (app_lua), Python 2 (app_python), Python 3 (app_python3, and app_python3s from 5.7), Ruby (app_ruby) and Squirrel (app_sqlang).78 Perl, .NET and Java modules exist but do not implement KEMI.7

You select the engine with cfgengine and point the module at your script. In the script, functions with fixed names take the place of the native blocks: ksr_request_route() for request_route, ksr_reply_route() for reply_route, ksr_onsend_route() for onsend_route. Branch, reply and failure routes are ordinary functions whose names you pass to KSR.tm.t_on_branch(), t_on_reply() and t_on_failure().8 Lua 5.1 to 5.4 are supported; the JavaScript engine is DukTape, embedded in the module, so it needs no external library.8 A minimal Lua routing script, adapted from the project's example:8

-- /etc/kamailio/kamailio.lua (with cfgengine "lua" in kamailio.cfg)
function ksr_request_route()
    if KSR.maxfwd.process_maxfwd(10) < 0 then
        KSR.sl.send_reply(483, "Too Many Hops")
        return
    end
    KSR.tm.t_on_failure("ksr_failure_route_one")
    if KSR.tm.t_relay() < 0 then
        KSR.sl.send_reply(500, "Server error")
    end
end

function ksr_failure_route_one()
    KSR.info("call failed on every branch\n")
end

The matching kamailio.cfg lines are loadmodule "app_lua.so", modparam("app_lua", "load", "/etc/kamailio/kamailio.lua") and cfgengine "lua".8 Module functions appear under KSR.<module>, so t_relay() becomes KSR.tm.t_relay().

The practical gain is reload. With app_lua's reload parameter on (the default), the RPC command app_lua.reload marks the script for reloading, and "the actual reload is done by every working process when the next call to lua_run function is executed".9 You change routing logic without dropping registrations or in-flight transactions.

Our view: for a team that already writes Python or Lua, KEMI is the better default for anything beyond a simple proxy. You get real data structures, tests you can run outside Kamailio, and reload without restart, which the KEMI page lists among its main benefits.7 The native language keeps two advantages: almost every example in the module documentation and on the mailing list is written in it, and it has no interpreter to embed. Learn the native language first, because you will be reading it in every module's documentation, then decide.

The modules that matter

Kamailio's core parses SIP and runs the script; almost everything else is a module. These are the ones nearly every production configuration loads, with what each one's documentation says it does:

ModuleWhat it doesNotes that matter in production
tmStateful processing of SIP transactions.10Needed for any forking, failover or accounting. "Stateful logic is costly in terms of memory and CPU."10 fr_timer defaults to 30 s with no reply, fr_inv_timer to 120 s after a provisional reply.10
slLets the server "act as a stateless UA server and generate replies to SIP requests without keeping state".11Use for replies you never need to retransmit or track, such as rejecting scanners.
registrarREGISTER processing; "The actual location database is managed by the USRLOC module."13save() stores contacts and replies 200 OK; lookup() rewrites the Request-URI and returns -1 (not found), -2 (method not supported) or -3 (error).13 Supports Path (RFC 3327).
usrlocThe user location table; exports no script functions of its own.14db_mode 0 is memory only (lost on restart), 1 write-through, 2 write-back on a timer, 3 database only, 4 load at start and then memory only. Default 0.14
auth, auth_dbGeneric digest authentication, and the database back end for it.1516auth_check() runs WWW authentication for REGISTER and proxy authentication otherwise; flag 1 also checks that the authenticated user matches From/To.16 6.1 added SHA-512 and SHA-512-256 digests.30
dispatcher"SIP load balancer functionality"; round-robin, weight-based, call-load, hashing and more.17Can run statelessly; needs tm only for active probing of failed destinations.17 6.1 added an algorithm combining round-robin with priority.3017
rtpengineControls an external Sipwise rtpengine media relay.18rtpengine_manage() works out offer, answer or delete from the message type.18 6.1 added SIPREC functions with rtpengine.30
nathelper"help with NAT traversal and reuse of TCP connections".19nat_uac_test() heuristics, fix_nated_register(), set_contact_alias() / handle_ruri_alias(), keepalive pings.19
pikeTracks request rates per source IP and reports the ones that exceed a limit.20It "does not implement any actions on blocking"; your script decides, usually by banning the IP in an htable.20
secfilterAllow and block lists for user agents, IPs, countries, domains and users; blocked destinations; SQL-injection checks.21Data is loaded into memory at start-up and reloaded by RPC.21
htableHash tables in shared memory, used from the script as $sht(table=>key).22Per-item expiry; optional replication between servers through the DMQ module.22 The usual home for caches, bans and counters.
dialogDialog (call) awareness on top of transactions.23Required for concurrent-call limits and anything that must know a call is still up.23
toposTopology hiding: strips the SIP routing headers that reveal your internal layout.24Transparent to the script; storage in a database (default), Redis or htable. REGISTER and PUBLISH are skipped.24
websocketA WebSocket (RFC 6455) server for SIP and MSRP over WebSocket (RFC 7118, RFC 7977), ws and wss.25The handshake arrives as an HTTP GET through the xhttp module.25

Scroll the table sideways to see every column.

The choice between sl and tm is the first real design decision in any script. Stateless forwarding uses almost no memory and scales furthest, which is why the dispatcher module can be used as a pure stateless balancer.17 But the moment you want "try the next server if this one does not answer", you need a transaction, because something has to remember the request and notice the timeout. The tm documentation states the rule: "Any kind of forking must be implemented transaction statefully."10

Release model and current versions

Kamailio numbers releases major.minor.patch. A new minor series (6.0, 6.1) is announced as a "major release": the 6.0.0 notes describe "a major release, meaning that it is introducing a consistent number of new features as well as improvements to existing components".29 Patch releases (6.1.1, 6.1.2, ...) are fixes, and the announcements promise compatibility: for 6.1.5, "The configuration file and database schema compatibility is preserved, which means you don't have to change anything to update."31

As of 26 October 2026, by the project's own announcements:

BranchMajor releaseLatest patch releaseStatus as announced
6.16.1.0, 18 Feb 20261306.1.5, 15 Oct 202631"the latest stable version", based on git branch 6.131
6.06.0.0, 29 Jan 2025296.0.8, 16 Sep 202632Still receiving fix releases from branch 6.032
5.8Not verified5.8.8, GitHub release published 6 Mar 202633Most recent 5.8 release we found

Scroll the table sideways to see every column.

Two cautions on those dates. The GitHub release objects do not always match the announcements: 6.1.0 is announced on kamailio.org for 18 February 2026, while its GitHub release entry was published on 3 March 2026.3033 We use the project's announcement dates. And we could not find a published support policy or end-of-life date for any Kamailio branch on kamailio.org. In practice the release history shows the two newest series receiving fix releases in parallel (6.0.6 and 6.1.1 both appeared in March 2026; 6.0.8 and 6.1.4/6.1.5 within weeks of each other),33 but that is an observed pattern, not a commitment.

What 6.x changed is worth knowing if you are upgrading from 5.x. 6.0 brought multi-threaded UDP receiving as an option, listening on a range of ports, variables in most module function parameters, CMake builds, HTTP/2 client and server support, and per-request selection of the authentication hash.29 6.1 added four modules (auth_arnacon, auth_web3, peerstate, ptimer), multi-threaded TLS, SIPREC with rtpengine, stronger hashes, and runtime get/set of module parameters over RPC.30

Our view: run the newest series (6.1 today) for new builds, and plan to move an existing 6.0 platform within a year of a new series appearing. Without a published end-of-life date, the safe assumption is that a branch stops getting fixes once two newer series exist.

Deployment patterns

1. Edge proxy and registrar in front of Asterisk or FreeSWITCH

This is the classic. Phones and trunks talk only to Kamailio. Kamailio authenticates and registers the phones, absorbs scanners and floods, fixes NAT, and hands calls to a pool of media servers through the dispatcher. The media servers do the dialplan, IVR, voicemail and conferencing, and send calls for phones back through Kamailio, which finds the phone in its location table. The PBXs never hold registrations, so you can add, drain or replace one without touching a phone. This is the pattern the worked example builds.

2. WebRTC gateway

Browsers speak SIP over secure WebSocket. The websocket module terminates the WebSocket side: the connection starts as an HTTP GET handled in event_route[xhttp:request], where the script should validate the Host and, ideally, the Origin header before calling ws_handle_handshake().25 Because "a JavaScript stack running in a browser has no way to determine the local address from which the WebSocket connection is made", the Contact, Via and Record-Route headers carry addresses that cannot be routed to, and the documented fix is to treat every WebSocket client as NATed: test with nat_uac_test(64), then use fix_nated_register(), add_contact_alias() and handle_ruri_alias().2519 On the media side, rtpengine converts between browser WebRTC media (ICE, DTLS-SRTP, RTP/SAVPF) and plain RTP for the PBX, using flags such as ICE=force and RTP/SAVPF.18

3. Carrier or trunk load balancer

The dispatcher module in stateless mode spreads traffic over SIP gateways, and active probing takes failed gateways out of rotation. The project's own configuration sample for the module is exactly this: "load balancing of VoIP calls with round robin", with REGISTER and presence not dispatched.17

4. Security front end

Even a small PBX exposed to the internet sees constant registration scanning. A Kamailio in front with pike, htable bans, secfilter and topos gives you rate limiting, block lists and topology hiding the PBX may not do as well itself.202124 The default configuration already drops user agents matching friendly|scanner|sipcli|sipvicious|VaxSIPUserAgent|pplsip.28

Emergency calls

A proxy in front of a PBX now sits on the 911 path. Routing, caller location and failover for emergency calls are your design responsibility, not Kamailio's. Test 911 routing explicitly whenever you change the dispatcher or trunk logic. Our emergency calling guide covers what the rules require of a business phone line in the US and Canada.

Worked example: registrar plus dispatcher, with rtpengine for NAT

The goal: phones on the internet, many behind home or office NAT, register to sip.example.com on a Kamailio server at 203.0.113.10. Every call a phone makes goes to one of two media servers (Asterisk or FreeSWITCH, it does not matter) at 192.0.2.21 and 192.0.2.22, round-robin, with automatic failover. Calls the media servers send to a phone come back through Kamailio and are delivered to the phone's registered contact. rtpengine relays audio for any call that involves a NATed phone. The addresses come from the blocks reserved for documentation, TEST-NET-1 and TEST-NET-3.37

The script is a trimmed derivative of the 6.1 default configuration,28 with the dispatcher logic from the dispatcher module's own sample.17 The parts below go into one kamailio.cfg in the order shown. It assumes a MySQL database with Kamailio's schema, subscribers added with kamctl or kamcli as the default configuration describes,28 and rtpengine running on the same host with its control socket on 127.0.0.1:2223, which is the default configuration's own suggestion (rtpengine --listen-ng=127.0.0.1:2223).28 Test it in a lab before you trust it with traffic; it is a teaching configuration, not a hardened one.

Part 1: defines and global parameters

#!KAMAILIO
#
# Edge registrar + dispatcher to two media servers, rtpengine for NAT.
# Written against Kamailio 6.1.x. Addresses are documentation ranges.

#!ifdef WITH_DEBUG
#!define DBGLEVEL 3
#!else
#!define DBGLEVEL 2
#!endif

#!trydef DBURL "mysql://kamailio:[email protected]/kamailio"
#!define DS_MEDIA "1"

# message (transaction) flags and branch flags
#!define FLT_NATS 5
#!define FLB_NATB 6
#!define FLB_NATSIPPING 7

####### Global parameters #######
debug=DBGLEVEL
log_stderror=no
log_facility=LOG_LOCAL0
children=8
auto_aliases=no
listen=udp:203.0.113.10:5060
listen=tcp:203.0.113.10:5060
alias="sip.example.com"
  • #!KAMAILIO marks the file as Kamailio syntax.28
  • The #!ifdef WITH_DEBUG block sets the log level at preprocessing time; start Kamailio with that symbol defined and you get level 3 without editing the file.5
  • #!trydef DBURL defines the database URL only if nothing defined it earlier, so a local include can override it.5 Change the password.
  • DS_MEDIA "1" names dispatcher set 1 so the routing logic reads as intent rather than a magic number.
  • The flag numbers follow the default configuration: FLT_NATS is a per-message flag ("this request came from behind NAT"), the FLB_ flags are per-branch and are stored with the registration.28
  • children=8 is the default worker count per UDP socket.5 listen binds UDP and TCP on the public address. alias tells Kamailio that sip.example.com is itself, which is what makes uri==myself and from_uri==myself work.5

Part 2: modules and their parameters

####### Modules #######
loadmodule "db_mysql.so"
loadmodule "jsonrpcs.so"
loadmodule "kex.so"
loadmodule "corex.so"
loadmodule "tm.so"
loadmodule "tmx.so"
loadmodule "sl.so"
loadmodule "rr.so"
loadmodule "pv.so"
loadmodule "maxfwd.so"
loadmodule "usrloc.so"
loadmodule "registrar.so"
loadmodule "textops.so"
loadmodule "siputils.so"
loadmodule "xlog.so"
loadmodule "sanity.so"
loadmodule "ctl.so"
loadmodule "auth.so"
loadmodule "auth_db.so"
loadmodule "nathelper.so"
loadmodule "rtpengine.so"
loadmodule "dispatcher.so"
loadmodule "htable.so"
loadmodule "pike.so"

modparam("tm", "failure_reply_mode", 3)
modparam("tm", "fr_timer", 5000)
modparam("tm", "fr_inv_timer", 120000)

modparam("rr", "enable_full_lr", 0)
modparam("rr", "append_fromtag", 0)

modparam("registrar", "method_filtering", 1)
modparam("registrar", "max_expires", 3600)
modparam("usrloc", "db_url", DBURL)
modparam("usrloc", "db_mode", 2)
modparam("usrloc", "nat_bflag", FLB_NATB)

modparam("auth_db", "db_url", DBURL)
modparam("auth_db", "calculate_ha1", yes)
modparam("auth_db", "password_column", "password")
modparam("auth_db", "load_credentials", "")

modparam("nathelper|registrar", "received_avp", "$avp(RECEIVED)")
modparam("nathelper", "natping_interval", 30)
modparam("nathelper", "ping_nated_only", 1)
modparam("nathelper", "sipping_bflag", FLB_NATSIPPING)
modparam("nathelper", "sipping_from", "sip:[email protected]")

modparam("rtpengine", "rtpengine_sock", "udp:127.0.0.1:2223")

modparam("dispatcher", "list_file", "/etc/kamailio/dispatcher.list")
modparam("dispatcher", "flags", 2)
modparam("dispatcher", "ds_ping_interval", 15)
modparam("dispatcher", "ds_probing_mode", 1)
modparam("dispatcher", "ds_ping_from", "sip:[email protected]")

modparam("pike", "sampling_time_unit", 2)
modparam("pike", "reqs_density_per_unit", 16)
modparam("pike", "remove_latency", 4)
modparam("htable", "htable", "ipban=>size=8;autoexpire=300;")
  • The module list is the default configuration's core set plus auth, nathelper, rtpengine, dispatcher, htable and pike.28
  • tm. failure_reply_mode 3 auto-discards branches from a previous serial-forking leg, as in the default.28 fr_timer is cut from the 30-second default to 5 seconds10 so that a dead media server is abandoned quickly; the dispatcher sample uses 2 seconds for the same reason.17 This timer only fires when there is no reply at all, so ringing phones are governed by fr_inv_timer (120 s).10
  • usrloc. db_mode 2 is write-back: changes go to memory and are flushed to the database on a timer, so registrations survive a restart with little latency cost.14 nat_bflag tells usrloc which branch flag marks a NATed contact.28
  • auth_db. calculate_ha1 yes with password_column "password" means plaintext passwords in the subscriber table, the default configuration's setting.28 For production, store HA1 hashes instead.
  • received_avp. Must be identical for nathelper and registrar, because fix_nated_register() stores the real source address in that AVP and save() writes it into the location table's received column.19
  • nathelper pinging. Every 30 seconds Kamailio sends keepalives to NATed contacts only, as a SIP request (OPTIONS by default) for contacts carrying the FLB_NATSIPPING branch flag instead of a dummy UDP packet, so the NAT binding stays open.1928
  • rtpengine_sock is the control socket of the rtpengine daemon; the default is disabled.18
  • dispatcher. flags 2 turns on failover: the other destinations in the set are kept in XAVPs for ds_next_dst().17 ds_probing_mode 1 tests all destinations, and a failed keepalive to an active one moves it to the trying state.17 Probes are SIP OPTIONS by default.17 ds_ping_interval must be non-zero or failed gateways are never re-probed (the default is 0).17
  • pike and htable. Copied from the default anti-flood settings, whose comment describes them as blocking "if more than 16 requests in 2 seconds and ban for 300 seconds"; the 300 seconds is the htable's autoexpire.28

Part 3: the dispatcher list

# /etc/kamailio/dispatcher.list
# setid  destination            flags  priority  attributes
1        sip:192.0.2.21:5060    0      0         duid=media-a
1        sip:192.0.2.22:5060    0      0         duid=media-b

Each line is setid destination flags priority attributes.17 Both servers are in set 1, flags 0 (active), equal priority. The duid attribute is a unique ID that the call-load algorithm (10) requires; with round-robin it simply labels the server.17 After editing the file, the module's dispatcher.reload RPC command loads it without a restart.17

Part 4: request_route

####### Routing logic #######
request_route {
    route(REQINIT);
    route(NATDETECT);

    if (is_method("CANCEL")) {
        if (t_check_trans()) {
            route(RELAY);
        }
        exit;
    }

    if (!is_method("ACK")) {
        if (t_precheck_trans()) {
            t_check_trans();
            exit;
        }
        t_check_trans();
    }

    route(WITHINDLG);

    # --- initial requests only from here on ---
    remove_hf("Route");
    if (is_method("INVITE|SUBSCRIBE")) {
        record_route();
    }

    # A media server is calling one of our registered phones.
    if (ds_is_from_list(DS_MEDIA)) {
        route(LOCATION);
    }

    route(AUTH);
    route(REGISTRAR);

    if ($rU == $null) {
        sl_send_reply("484", "Address Incomplete");
        exit;
    }

    # Every authenticated call goes to a media server.
    route(TOMEDIA);
}
  • route(REQINIT) runs the sanity, flood and scanner checks before anything else.
  • route(NATDETECT) decides early whether the sender is behind NAT, because both registration and call handling depend on it.
  • CANCEL is relayed only if it matches a transaction we know (t_check_trans()), then the script exits.28
  • Retransmissions. t_precheck_trans() catches a retransmission of a request whose transaction is still being created by another process; t_check_trans() absorbs retransmissions of existing transactions.28 This block is copied verbatim from the default configuration for good reason: get it wrong and every retransmitted INVITE becomes a second call.
  • route(WITHINDLG) handles requests that already belong to a dialog (they have a To tag) and exits; everything after it is an initial request.
  • remove_hf("Route") removes any Route headers a client preloaded, and record_route() keeps Kamailio in the signalling path for the life of the call, so the BYE comes back through it.2812
  • ds_is_from_list(DS_MEDIA) is true when the source IP and port match a destination in set 1 (with only the group given, protocol is ignored).17 Those requests are trusted: they skip authentication and go straight to the location lookup. This is the design decision of the whole script: phones must authenticate, media servers are trusted by address. Never put an address in the dispatcher list that you do not control.
  • Everything else must authenticate (route(AUTH)). REGISTER is then handled and exits inside route(REGISTRAR). A request with no user part gets 484.28 Whatever remains is an authenticated call from a phone, and it goes to a media server.

Part 5: the sub-routes

route[REQINIT] {
    force_rport();

    if (src_ip != myself && !ds_is_from_list(DS_MEDIA)) {
        if ($sht(ipban=>$si) != $null) {
            exit;
        }
        if (!pike_check_req()) {
            xalert("pike blocking $rm from $fu (IP:$si:$sp)\n");
            $sht(ipban=>$si) = 1;
            exit;
        }
    }

    if ($ua =~ "friendly|scanner|sipcli|sipvicious|VaxSIPUserAgent|pplsip") {
        exit;
    }

    if (!mf_process_maxfwd_header("10")) {
        sl_send_reply("483", "Too Many Hops");
        exit;
    }

    if (is_method("OPTIONS") && uri == myself && $rU == $null) {
        sl_send_reply("200", "Keepalive");
        exit;
    }

    if (!sanity_check("17895", "7")) {
        xlog("Malformed SIP request from $si:$sp\n");
        exit;
    }
}

route[AUTH] {
    if (from_uri != myself) {
        sl_send_reply("403", "Not relaying");
        exit;
    }
    if (!auth_check("$fd", "subscriber", "1")) {
        auth_challenge("$fd", "0");
        exit;
    }
    if (!is_method("REGISTER")) {
        consume_credentials();
    }
}

route[REGISTRAR] {
    if (!is_method("REGISTER")) return;

    if (isflagset(FLT_NATS)) {
        setbflag(FLB_NATB);
        setbflag(FLB_NATSIPPING);
    }
    if (!save("location")) {
        sl_reply_error();
    }
    exit;
}

route[LOCATION] {
    if (!lookup("location")) {
        $var(rc) = $rc;
        t_newtran();
        switch ($var(rc)) {
            case -1:
            case -3:
                send_reply("404", "Not Found");
                exit;
            case -2:
                send_reply("405", "Method Not Allowed");
                exit;
        }
    }
    route(RELAY);
    exit;
}

route[TOMEDIA] {
    # 4 = round-robin; with flags=2 the other server is kept for failover
    if (!ds_select_dst(DS_MEDIA, "4")) {
        send_reply("503", "No media server available");
        exit;
    }
    t_on_failure("MEDIA_FAILOVER");
    route(RELAY);
    exit;
}

route[RELAY] {
    if (is_method("INVITE|BYE|UPDATE")) {
        if (!t_is_set("branch_route")) t_on_branch("MANAGE_BRANCH");
    }
    if (is_method("INVITE|UPDATE")) {
        if (!t_is_set("onreply_route")) t_on_reply("MANAGE_REPLY");
    }
    if (is_method("INVITE")) {
        if (!t_is_set("failure_route")) t_on_failure("MANAGE_FAILURE");
    }
    if (!t_relay()) {
        sl_reply_error();
    }
    exit;
}

route[WITHINDLG] {
    if (!has_totag()) return;

    if (loose_route()) {
        if (!isdsturiset()) {
            handle_ruri_alias();
        }
        if (is_method("ACK")) {
            route(NATMANAGE);
        }
        route(RELAY);
        exit;
    }
    if (is_method("ACK")) {
        if (t_check_trans()) {
            route(RELAY);
        }
        exit;
    }
    sl_send_reply("404", "Not here");
    exit;
}

REQINIT. force_rport() makes replies go back to the port the request actually came from, which is what NAT needs (RFC 3581).5 Pike only applies to sources that are not ourselves and not the media servers; the default configuration warns to "exclude checking trusted peers, such as pstn gateways".28 An IP already in the ipban table is dropped silently; a new flooder is logged and banned. Pike's own return codes distinguish a known flooder (-1) from a new one (-2), and on an internal error it deliberately returns true so it never blocks by mistake.20 Scanner user agents are dropped without a reply. mf_process_maxfwd_header("10") stops loops. OPTIONS keepalives addressed to the server itself get a stateless 200. sanity_check("17895", "7") applies the default configuration's set of RFC sanity checks.2826

AUTH. The first test refuses any request whose From domain is not ours: this server is not an open relay. Then auth_check("$fd", "subscriber", "1") challenges with the From domain as realm, looks the user up in the subscriber table, and, with flag 1, insists that the authenticated username matches the From (and for REGISTER, the To) user, so one phone cannot place calls as another.16 On failure, auth_challenge() sends a 401 for REGISTER and a 407 for anything else, and the script exits.15 consume_credentials() removes the credentials this server used before the request is forwarded, so they are not revealed to the media server.15

REGISTRAR. If NATDETECT flagged the request, the NAT branch flag and the SIP-ping flag are set before save("location"). The registrar stores branch flags with the contact,13 so on every later call to this phone Kamailio knows it is behind NAT without guessing again. save() sends the 200 OK itself.13

LOCATION. lookup("location") replaces the Request-URI with the phone's registered contact. On failure the return code is kept, a transaction is created so the reply is sent statefully, and the code is mapped to 404 or 405 as the registrar documentation does.1328

TOMEDIA. ds_select_dst(DS_MEDIA, "4") picks the next server round-robin and sets it as the destination URI ($du), leaving the Request-URI unchanged, so the media server still sees which number was dialled.17 If every server is inactive, the caller gets 503. Otherwise the failover route is armed and the request relayed.

RELAY. Arms the branch, reply and failure routes for the methods that need NAT handling, unless something armed them already. That t_is_set() check is what lets TOMEDIA's MEDIA_FAILOVER survive. Then t_relay() forwards statefully.2810

WITHINDLG. A request with a To tag (has_totag(), from siputils, which also provides is_first_hop(), is_request() and is_reply())27 belongs to an existing dialog. In-dialog requests follow the Route set recorded at call setup (loose_route()). If no destination was set, handle_ruri_alias() recovers the phone's real NAT address from the ;alias= parameter that set_contact_alias() added earlier.19 An ACK goes through NATMANAGE so rtpengine sees any SDP in it. A stray ACK with no transaction is dropped, and anything else gets 404.28

Part 6: NAT handling, rtpengine and failover

route[NATDETECT] {
    if (nat_uac_test("19")) {
        if (is_method("REGISTER")) {
            fix_nated_register();
        } else {
            if (is_first_hop()) {
                set_contact_alias();
            }
        }
        setflag(FLT_NATS);
    }
}

route[NATMANAGE] {
    if (is_request()) {
        if (has_totag()) {
            if (check_route_param("nat=yes")) {
                setbflag(FLB_NATB);
            }
        }
    }
    if (!(isflagset(FLT_NATS) || isbflagset(FLB_NATB))) return;

    if (nat_uac_test("8")) {
        rtpengine_manage("SIP-source-address replace-origin replace-session-connection");
    } else {
        rtpengine_manage("replace-origin replace-session-connection");
    }

    if (is_request()) {
        if (!has_totag()) {
            if (t_is_branch_route()) {
                add_rr_param(";nat=yes");
            }
        }
    }
    if (is_reply()) {
        if (isbflagset(FLB_NATB)) {
            if (is_first_hop()) {
                set_contact_alias();
            }
        }
    }
}

branch_route[MANAGE_BRANCH] {
    route(NATMANAGE);
}

onreply_route[MANAGE_REPLY] {
    if (status =~ "[12][0-9][0-9]") {
        route(NATMANAGE);
    }
}

failure_route[MANAGE_FAILURE] {
    route(NATMANAGE);
    if (t_is_canceled()) exit;
}

failure_route[MEDIA_FAILOVER] {
    route(NATMANAGE);
    if (t_is_canceled()) exit;

    # next media server only on a 500/503 or a local timeout
    if (t_check_status("500|503")
            || (t_branch_timeout() && !t_branch_replied())) {
        ds_mark_dst("ip");
        if (ds_next_dst()) {
            t_on_failure("MEDIA_FAILOVER");
            route(RELAY);
            exit;
        }
    }
}

NATDETECT. nat_uac_test("19") is the flags 1 + 2 + 16: a private (RFC 1918 or RFC 6598) address in the Contact, a Via address that differs from the packet's source, or a source port that differs from the Via port.19 For a REGISTER, fix_nated_register() records the true source so the registrar stores it.19 For other requests from the first hop, set_contact_alias() adds ;alias=ip~port~transport to the Contact without rewriting the message buffer.19

NATMANAGE. This is the default configuration's rtpengine block.28 For in-dialog requests, the nat=yes Record-Route parameter (added on the first branch) restores the NAT flag. If neither party is NATed, it returns and media flows directly. Otherwise rtpengine_manage() does the right thing for the message in hand: an offer for an INVITE with SDP, an answer for the 1xx/2xx reply or an ACK with SDP, and a delete on BYE, CANCEL, a final failure reply or in a failure route.18 replace-origin and replace-session-connection rewrite the SDP o= and session-level c= lines to rtpengine's address.18 When the SDP itself contains a private address (nat_uac_test("8")), SIP-source-address tells rtpengine to ignore the SDP address and use the signalling source instead.1918 t_is_branch_route() comes from tmx.38

The reply and failure routes. The branch route runs NATMANAGE on each outgoing branch; the reply route runs it on provisional and success replies, which carry the SDP answer; MANAGE_FAILURE runs it so the rtpengine session is deleted when a call fails.28 All three are taken from the default configuration.

MEDIA_FAILOVER. Modelled on the dispatcher sample.17 If the caller hung up, stop. If the server answered 500 or 503, or never answered at all (t_branch_timeout() without t_branch_replied()), mark it inactive and probing with ds_mark_dst("ip"), so it leaves rotation until a probe succeeds,17 then take the next server from the XAVP list with ds_next_dst(), re-arm the same failure route and relay again.17 A 486 Busy or 404 from the media server is a real answer and goes back to the phone.

What this example leaves out

  • TLS. Add the tls module and listen=tls:...; register phones over TLS on 5061 in production.
  • Always relaying media. If the media servers sit on a private network the phones cannot reach, rtpengine must relay every call, not only NATed ones. The default configuration's WITH_RTPRELAY option removes the early return in NATMANAGE for exactly this case.28
  • Topology hiding and call limits. Load topos to hide internal addresses, and dialog to cap concurrent calls per account.2423
  • The media server side. Asterisk or FreeSWITCH must accept unauthenticated SIP from Kamailio's address only, and send calls for phones to sip:<extension>@203.0.113.10. That configuration is outside this page.

Before restarting, kamailio -c -f /etc/kamailio/kamailio.cfg checks the file for syntax errors.5 Once running, the dispatcher.list RPC command shows the state of both media servers.17

Kamailio and OpenSIPS: shared origins, different paths

OpenSIPS and Kamailio share a codebase up to 2008, and their configuration languages and many module names still look alike. How they split depends on who you ask. Kamailio's history page says that after the rename the person who had registered the openser.org domain "created a fork named opensips shortly after OpenSER to Kamailio renaming and few days later redirected openser.org to his new project".2 OpenSIPS describes itself as having "started as a fork of Fokus Fraunhofer SIP Express Router (SER) project" and says it "wants to be a more open project, not only from license point of view, but more open as project management, especially for external contributions".34 Its first release series, 1.4.x, came out on 4 August 2008,36 a week after the Kamailio rename.2 We are not going to adjudicate a 2008 dispute; for a buyer in 2026 what matters is how the projects differ now.

KamailioOpenSIPS
LicenceGPLv2 main licence, some parts BSD3GPL34
Stewardship"developed and managed by its world wide community"3"maintained by OpenSIPS Solutions" with core SER developers34
ScriptingNative DSL, or KEMI in Lua, Python, JavaScript, Ruby, Squirrel7A C-like custom scripting language35
Current stable6.1 (6.1.5, 15 Oct 2026) and 6.0 (6.0.8)31324.0 and 3.6 LTS36
Published support datesNone found4.0 supported until Dec 2027; 3.6 LTS until 21 May 202836
Tooling it highlightsRPC control (kamcmd, kamctl), DMQ replication for htable622menuconfig script generator, MI and Event interfaces, the OpenSIPS-CP web panel35
Roles it listsProxy, registrar, location, redirect, application server4The same, plus "SIP back-to-back User Agent" among its features35

Scroll the table sideways to see every column.

OpenSIPS publishes a supported-until date for each branch and labels long-term-support releases; its versions page shows 3.6 (released 23 July 2025) as LTS with support to May 2028, and 4.1 in development with a beta planned for May 2027.36 If your organisation needs a written support horizon to sign off a platform, that is a concrete advantage.

Our view: either will do the job in this guide; the configuration above would port to OpenSIPS with module-level changes, not a redesign. Pick Kamailio if your team wants to write routing logic in Python or Lua and reload it live, or if you value a community-governed project. Pick OpenSIPS if published LTS dates, a built-in B2BUA or its web control panel matter more to you. In either case, the deciding factor is usually the people: which project's documentation, mailing list and consultants your team already knows.

When Kamailio is the right tool, and when it is not

Kamailio earns its place when you have scale, redundancy or exposure: more phones than one PBX should carry, more than one media server, several carriers, WebRTC clients, or a public SIP port that is being scanned around the clock. In all of those cases a dedicated signalling layer that can register, authenticate, balance and block at thousands of call setups per second1 is worth the extra machine.

It is the wrong tool when the problem is call features. An auto attendant, a voicemail box, hold music, a queue: these are media-server jobs, and Kamailio has no media path of its own.18 A single-site business with one PBX and a handful of phones gains little from putting a Kamailio in front, and gains another system to patch, monitor and understand at 2 a.m.

And it is a programming environment, not a product. There is no admin screen in the box, the native language is unforgiving, and the defaults assume you have read the module documentation. Budget for an engineer who can read a SIP trace. Teams that do not have one are usually better served by a hosted platform or a packaged PBX, and there is no shame in that.

Further reading

Longer research pieces on this topic, each with its own source list and a downloadable PDF.

Sources

Every regulatory and pricing claim on this page links to the primary source it came from. Figures were checked on the date shown at the top of the page; rules and prices change, so verify before relying on one.

  1. Kamailio project, home page: project description, roles, development timeline and latest release (6.1.5, 15 Oct 2026). Retrieved 26 Oct 2026
    https://www.kamailio.org/w/
  2. Kamailio project, "History": SER at Fraunhofer FOKUS, the 2005 OpenSER fork, the 2008 rename and the SER merger, and its account of the OpenSIPS fork. Retrieved 26 Oct 2026
    https://www.kamailio.org/w/history/
  3. kamailio/kamailio repository on GitHub, README: "SIP Signaling Server", project history, licence rules for new contributions. Retrieved 26 Oct 2026
    https://github.com/kamailio/kamailio
  4. Kamailio project, "Features": server roles, transports, database back ends, and the project's throughput and subscriber-capacity claims. Retrieved 26 Oct 2026
    https://www.kamailio.org/w/features/
  5. Kamailio Wiki, Core Cookbook 6.1.x: configuration file structure, routing blocks, preprocessor directives, children/tcp_children, shm_mem_size, force_rport, command-line options. Retrieved 26 Oct 2026
    https://www.kamailio.org/wikidocs/cookbooks/6.1.x/core/
  6. Kamailio Wiki, "Troubleshooting Memory": private (pkg) and shared (shm) memory, default sizes, -M/-m options, memory statistics commands. Retrieved 26 Oct 2026
    https://www.kamailio.org/wikidocs/tutorials/troubleshooting/memory/
  7. Kamailio, KEMI Framework documentation, introduction and overview: native DSL limits, no reload, KEMI added in v5.0.0, supported languages and modules. Retrieved 26 Oct 2026
    https://www.kamailio.org/docs/tutorials/devel/kamailio-kemi-framework/
  8. Kamailio, KEMI Framework documentation, "KEMI Interpreters": cfgengine identifiers, ksr_request_route and other callback names, Lua and Python examples. Retrieved 26 Oct 2026
    https://www.kamailio.org/docs/tutorials/devel/kamailio-kemi-framework/kemi/
  9. Kamailio module documentation (6.1.x), app_lua: reload parameter and app_lua.reload RPC command. Retrieved 26 Oct 2026
    https://www.kamailio.org/docs/modules/6.1.x/modules/app_lua.html
  10. Kamailio module documentation (6.1.x), tm: stateful transaction processing, fr_timer and fr_inv_timer defaults, t_relay and failure-route functions. Retrieved 26 Oct 2026
    https://www.kamailio.org/docs/modules/6.1.x/modules/tm.html
  11. Kamailio module documentation (6.1.x), sl: stateless replies. Retrieved 26 Oct 2026
    https://www.kamailio.org/docs/modules/6.1.x/modules/sl.html
  12. Kamailio module documentation (6.1.x), rr: record routing; "basically only a transaction stateful proxy". Retrieved 26 Oct 2026
    https://www.kamailio.org/docs/modules/6.1.x/modules/rr.html
  13. Kamailio module documentation (6.1.x), registrar: save() and lookup() behaviour and return codes, Path support. Retrieved 26 Oct 2026
    https://www.kamailio.org/docs/modules/6.1.x/modules/registrar.html
  14. Kamailio module documentation (6.1.x), usrloc: location table and the five db_mode values. Retrieved 26 Oct 2026
    https://www.kamailio.org/docs/modules/6.1.x/modules/usrloc.html
  15. Kamailio module documentation (6.1.x), auth: generic authentication functions. Retrieved 26 Oct 2026
    https://www.kamailio.org/docs/modules/6.1.x/modules/auth.html
  16. Kamailio module documentation (6.1.x), auth_db: database authentication and auth_check() flags. Retrieved 26 Oct 2026
    https://www.kamailio.org/docs/modules/6.1.x/modules/auth_db.html
  17. Kamailio module documentation (6.1.x), dispatcher: algorithms, flags, probing parameters, list file format, ds_select_dst, ds_next_dst, ds_mark_dst, ds_is_from_list, sample configuration. Retrieved 26 Oct 2026
    https://www.kamailio.org/docs/modules/6.1.x/modules/dispatcher.html
  18. Kamailio module documentation (6.1.x), rtpengine: rtpengine_sock, rtpengine_manage() behaviour, SDP and WebRTC flags. Retrieved 26 Oct 2026
    https://www.kamailio.org/docs/modules/6.1.x/modules/rtpengine.html
  19. Kamailio module documentation (6.1.x), nathelper: nat_uac_test flags, fix_nated_register, set_contact_alias, handle_ruri_alias. Retrieved 26 Oct 2026
    https://www.kamailio.org/docs/modules/6.1.x/modules/nathelper.html
  20. Kamailio module documentation (6.1.x), pike: flood detection, parameters and pike_check_req() return codes. Retrieved 26 Oct 2026
    https://www.kamailio.org/docs/modules/6.1.x/modules/pike.html
  21. Kamailio module documentation (6.1.x), secfilter: allow/block lists and SQL-injection checks. Retrieved 26 Oct 2026
    https://www.kamailio.org/docs/modules/6.1.x/modules/secfilter.html
  22. Kamailio module documentation (6.1.x), htable: shared-memory hash tables, expiry, DMQ replication. Retrieved 26 Oct 2026
    https://www.kamailio.org/docs/modules/6.1.x/modules/htable.html
  23. Kamailio module documentation (6.1.x), dialog: dialog awareness versus transaction state. Retrieved 26 Oct 2026
    https://www.kamailio.org/docs/modules/6.1.x/modules/dialog.html
  24. Kamailio module documentation (6.1.x), topos: topology hiding and storage back ends. Retrieved 26 Oct 2026
    https://www.kamailio.org/docs/modules/6.1.x/modules/topos.html
  25. Kamailio module documentation (6.1.x), websocket: RFC 6455 server, handshake via xhttp, SIP routing for WebSocket clients. Retrieved 26 Oct 2026
    https://www.kamailio.org/docs/modules/6.1.x/modules/websocket.html
  26. Kamailio module documentation (6.1.x), sanity: RFC sanity checks on incoming requests. Retrieved 26 Oct 2026
    https://www.kamailio.org/docs/modules/6.1.x/modules/sanity.html
  27. Kamailio module documentation (6.1.x), siputils: has_totag, is_request, is_reply, is_first_hop. Retrieved 26 Oct 2026
    https://www.kamailio.org/docs/modules/6.1.x/modules/siputils.html
  28. kamailio/kamailio repository, etc/kamailio.cfg on branch 6.1: the default configuration script the worked example is derived from. Retrieved 26 Oct 2026
    https://github.com/kamailio/kamailio/blob/6.1/etc/kamailio.cfg
  29. Kamailio project, "Kamailio v6.0.0 Release Notes" (29 January 2025). 29 January 2025
    https://www.kamailio.org/w/kamailio-v6-0-0-release-notes/
  30. Kamailio project, "Kamailio v6.1.0 Release Notes" (18 February 2026): new modules and features. 18 February 2026
    https://www.kamailio.org/w/kamailio-v6-1-0-release-notes/
  31. Kamailio project, "Kamailio v6.1.5 Released". 15 October 2026
    https://www.kamailio.org/w/2026/10/kamailio-v6-1-5-released/
  32. Kamailio project, "Kamailio v6.0.8 Released". 16 September 2026
    https://www.kamailio.org/w/2026/09/kamailio-v6-0-8-released/
  33. kamailio/kamailio repository, GitHub releases list with publication dates (6.0.2 to 6.1.5, including 5.8.8). Read via the GitHub API, 26 Oct 2026
    https://github.com/kamailio/kamailio/releases
  34. OpenSIPS/opensips repository on GitHub, README: origin as a SER fork, project aims, maintainer. Retrieved 26 Oct 2026
    https://github.com/OpenSIPS/opensips
  35. OpenSIPS project, "About": scripting language, menuconfig, MI and Event interfaces, OpenSIPS-CP, feature list. Retrieved 26 Oct 2026
    https://opensips.org/About/About
  36. OpenSIPS project, "Available Versions": supported releases, LTS labels, release and supported-until dates, 1.4.x release date. Retrieved 26 Oct 2026
    https://opensips.org/About/AvailableVersions
  37. IETF RFC 5737, "IPv4 Address Blocks Reserved for Documentation" (TEST-NET-1 and TEST-NET-3). January 2010
    https://www.rfc-editor.org/rfc/rfc5737
  38. Kamailio module documentation (6.1.x), tmx: t_is_branch_route() and other transaction helpers. Retrieved 26 Oct 2026
    https://www.kamailio.org/docs/modules/6.1.x/modules/tmx.html