Kamailio as a load balancer for Asterisk: a practical guide with the dispatcher module

Kamailio as a load balancer for Asterisk: a practical guide with the dispatcher module

If you’ve been working with Asterisk in production for a while, sooner or later you’ve run into the same problem: a single Asterisk server doesn’t scale well. Calls pile up, CPU spikes with transcoding, and any restart to apply a configuration change means cutting the service. The classic solution is to run several Asterisk servers in parallel and put something in front to distribute the load. That “something” is Kamailio, and the tool that makes it possible is the dispatcher module.

This tutorial shows you how to install Kamailio on Debian/Ubuntu, configure the dispatcher module and route SIP calls to different Asterisk servers based on the originating user. It’s not magic — it’s well-configured SIP routing logic.

The architecture we’re going to build

  • Kamailio acts as the SIP proxy. Endpoints (softphones, IP phones, SIP trunks) register against Kamailio, and Kamailio decides which Asterisk to send each call to.
  • Asterisk A (192.168.1.10) and Asterisk B (192.168.1.20) are the media servers. This is where the business logic lives: IVRs, queues, extensions, etc.
  • Kamailio does not touch the audio. It is a pure signalling proxy. RTP goes directly between the endpoint and Asterisk.

Kamailio’s dispatcher module maintains a set of destinations (the Asterisk servers) and distributes them according to different algorithms: round-robin, user hash, active load… In our case we’ll use user hash (algorithm 4), which guarantees that the same user always goes to the same Asterisk as long as both are available. This is important so that channel state, call parking and pickup groups remain consistent.

Prerequisites

  • A clean Debian 12 or Ubuntu 22.04/24.04 server for Kamailio
  • Two working Asterisk servers (or at least reachable over the network)
  • Root or sudo access on all servers
  • UDP port 5060 open between Kamailio and the Asterisk servers

1. Installing Kamailio

Use the official Kamailio repositories. Do not install the package from the base Debian/Ubuntu repository — it’s usually way behind on version.

# Add the official Kamailio repository (version 5.8)
curl -fsSL https://deb.kamailio.org/kamailiodebkey.gpg | gpg --dearmor -o /usr/share/keyrings/kamailio.gpg

echo "deb [signed-by=/usr/share/keyrings/kamailio.gpg] http://deb.kamailio.org/kamailio58 $(lsb_release -cs) main" \
  > /etc/apt/sources.list.d/kamailio.list

apt update
apt install -y kamailio kamailio-mysql-modules kamailio-utils-modules

In addition to the base modules, we install kamailio-utils-modules because it includes the dispatcher module, the star of this tutorial.

Check that Kamailio starts up:

systemctl enable kamailio
systemctl start kamailio
systemctl status kamailio

2. Basic Kamailio configuration

The main configuration file is /etc/kamailio/kamailio.cfg. Before editing it, adjust the global parameters in /etc/default/kamailio:

MEMORY=128
SHM_MEMORY=256
CFGFILE=/etc/kamailio/kamailio.cfg
USER=kamailio
GROUP=kamailio
DUMP_CORE=no

Now replace the contents of /etc/kamailio/kamailio.cfg with the following:

#!KAMAILIO

##############################################
# Global parameters
##############################################

debug=2
log_stderror=no
log_facility=LOG_LOCAL0

fork=yes
children=4

port=5060
listen=udp:0.0.0.0:5060

##############################################
# Modules to load
##############################################

loadmodule "tm.so"
loadmodule "sl.so"
loadmodule "rr.so"
loadmodule "pv.so"
loadmodule "maxfwd.so"
loadmodule "textops.so"
loadmodule "siputils.so"
loadmodule "xlog.so"
loadmodule "sanity.so"
loadmodule "dispatcher.so"

##############################################
# Module parameters
##############################################

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

modparam("rr", "enable_full_lr", 1)

# dispatcher - the key module
modparam("dispatcher", "list_file", "/etc/kamailio/dispatcher.list")
modparam("dispatcher", "flags", 2)
modparam("dispatcher", "dst_avp", "$avp(ds_dst)")
modparam("dispatcher", "grp_avp", "$avp(ds_grp)")
modparam("dispatcher", "cnt_avp", "$avp(ds_cnt)")
modparam("dispatcher", "sock_avp", "$avp(ds_sock)")

# Health check: probe Asterisk servers every 30 seconds
modparam("dispatcher", "ds_ping_interval", 30)
modparam("dispatcher", "ds_probing_mode", 1)
modparam("dispatcher", "ds_ping_from", "sip:kamailio@yourdomain.com")

##############################################
# Routing logic
##############################################

request_route {

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

    if (!sanity_check()) {
        exit;
    }

    if (is_method("ACK")) {
        if (t_check_trans()) { t_relay(); }
        exit;
    }

    if (is_method("CANCEL")) {
        if (t_check_trans()) { t_relay(); }
        exit;
    }

    if (is_method("INVITE|SUBSCRIBE")) {
        record_route();
    }

    if (loose_route()) {
        route(RELAY);
        exit;
    }

    if (is_method("INVITE")) {
        route(DISPATCH);
        exit;
    }

    route(RELAY);
}

route[DISPATCH] {
    # User hash (algorithm 4): same user always goes to the same Asterisk
    if (!ds_select_dst(1, 4)) {
        xlog("L_ERR", "No destinations available in group 1\n");
        sl_send_reply("503", "Service Unavailable");
        exit;
    }

    xlog("L_INFO", "Routing call from $fu to $du via dispatcher\n");

    t_on_failure("MANAGE_FAILURE");
    route(RELAY);
}

route[RELAY] {
    if (!t_relay()) {
        sl_reply_error();
    }
    exit;
}

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

    if (t_check_status("408|5[0-9][0-9]|6[0-9][0-9]")) {
        ds_mark_dst("ip");
        if (!ds_next_dst()) {
            xlog("L_ERR", "No more destinations. Failing call.\n");
            t_reply("503", "All destinations failed");
            exit;
        }
        route(RELAY);
    }
}

3. The dispatcher.list file: where you define your Asterisk servers

This is the most important file for load balancing. Create /etc/kamailio/dispatcher.list:

# /etc/kamailio/dispatcher.list
# Format: setid  uri  flags  priority  attrs  description

1  sip:192.168.1.10:5060  0  0  weight=50  Asterisk-A
1  sip:192.168.1.20:5060  0  0  weight=50  Asterisk-B

The group number (1) is what you pass to ds_select_dst(1, 4). You can have multiple groups for different purposes: one for inbound calls, one for outbound, one for premium users, etc.

Algorithm 4 hashes the Request-URI (the called user) — same destination always lands on the same Asterisk. Algorithm 7 hashes the Call-ID (more uniform distribution). Algorithm 0 is plain round-robin.

After modifying the file, reload without restarting Kamailio:

kamcmd dispatcher.reload

4. Configuring Asterisk to accept calls from Kamailio

In PJSIP (/etc/asterisk/pjsip.conf), create an endpoint for Kamailio:

[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0:5060

[kamailio]
type=endpoint
context=from-kamailio
disallow=all
allow=alaw,ulaw
direct_media=no
trust_id_inbound=yes
from_domain=yourdomain.com

[kamailio]
type=identify
endpoint=kamailio
match=192.168.1.1  ; Your Kamailio IP

[kamailio]
type=aor
max_contacts=1

The direct_media=no parameter is critical. It tells Asterisk not to negotiate direct media between endpoints, since Kamailio won’t be in the RTP path. Set it to yes with NATted clients and audio will break.

In the dialplan (/etc/asterisk/extensions.conf):

[from-kamailio]
exten => _.,1,NoOp(Call received from Kamailio for EXTEN_PLACEHOLDER)
 same => n,Dial(PJSIP/EXTEN_PLACEHOLDER,30)
 same => n,Hangup()

(Replace EXTEN_PLACEHOLDER with ${EXTEN} in the actual file.)

5. Verifying the dispatcher is working

kamcmd dispatcher.list

Sample output:

DISPATCHER SETS[1]:
SET[0]:
 ID: 1
 DESTS[2]:
  DEST[0]: URI=sip:192.168.1.10:5060 FLAGS=AP PRIORITY=0
  DEST[1]: URI=sip:192.168.1.20:5060 FLAGS=AP PRIORITY=0

The FLAGS field tells you everything:

  • A (Active): destination is up and receiving calls
  • P (Probing): Kamailio is sending periodic OPTIONS pings
  • I (Inactive): destination is not responding, marked as down
  • D (Disabled): manually disabled

To put an Asterisk server into maintenance without dropping live calls:

# Disable
kamcmd dispatcher.set_state ip 1 sip:192.168.1.20:5060

# Re-enable
kamcmd dispatcher.set_state ap 1 sip:192.168.1.20:5060

6. Debugging and logs

tail -f /var/log/syslog | grep kamailio

For raw SIP inspection, sngrep is your best friend:

apt install -y sngrep
sngrep -d eth0 port 5060

Adjust the debug level on the fly (no restart needed):

kamcmd cfg.set_now_int core debug 4  # verbose
kamcmd cfg.set_now_int core debug 2  # back to normal

Conclusions and next steps

With this setup you have a functional SIP proxy that load balances calls across multiple Asterisk servers using user hash, with automatic failure detection and transparent failover. It’s the foundation for a serious telephony architecture.

What’s not covered here but is the natural next step:

  • User registration: registrar module + MySQL database so endpoints register against Kamailio.
  • Authentication: auth and auth_db modules for endpoint authentication.
  • NAT traversal: nathelper + RTPengine or rtpproxy for clients behind NAT.
  • TLS and SRTP: encrypt both signalling and audio.

The dispatcher is just one of Kamailio’s many modules, but it’s the one that gives you the most leverage in a multi-Asterisk environment. Master it, and you’ll have the base to build whatever you need on top.

Inicia la conversación

¡Inicia la conversación!

Sé el primero en compartir tu opinión. Tu comentario puede ayudar a otros.

¿Sabías que con una cuenta gratuita puedes reaccionar, recibir respuestas y mostrar tu nombre verificado? · Iniciar sesión