A telephony platform rarely becomes unreliable because one server is too small.

It becomes unreliable because signalling, media, call control, product state and external integrations have been compressed into one mental model. A traffic spike reaches the SIP edge, creates expensive media work, opens AI streams, writes lifecycle events and triggers retries — and every layer discovers the limit at the same time.

The usual response is to add more servers.

That helps only after the architecture can answer a more important question: which servers should receive more of which work?

FreeSWITCH, Kamailio and RTPengine are often grouped together as “the telephony stack.” Operationally, they are three very different systems. A scalable platform starts by protecting that difference.

Three jobs that should not collapse into one

At a high level, the responsibilities look like this:

EXAMPLE

Kamailio
  SIP edge, routing, policy, admission and topology

FreeSWITCH
  Call execution, B2BUA logic, media applications and channel control

RTPengine
  Media anchoring, NAT traversal and RTP/SRTP boundary handling

There is overlap. FreeSWITCH can accept public SIP traffic. Kamailio can maintain dialog state. RTPengine can perform more than simple packet forwarding.

The architectural question is not what each component can do. It is which component should own a responsibility when traffic grows and failures need to remain contained.

If one public FreeSWITCH node performs edge security, registration, routing, application logic, recording, transcoding and AI media streaming, every new feature competes inside the same failure domain.

Splitting the jobs gives the platform somewhere to absorb pressure.

A call takes two paths

SIP signalling and voice media are related, but they are not the same flow.

A simplified WebRTC-to-carrier call may look like this:

EXAMPLE

SIGNALLING

browser
   |
   | SIP over WSS
   v
Kamailio
   |
   | routed SIP
   v
FreeSWITCH
   |
   | outbound SIP through edge policy
   v
Kamailio
   |
   v
carrier


MEDIA

browser SRTP
   |
   v
RTPengine
   |
   | RTP or SRTP
   v
FreeSWITCH
   |
   v
RTPengine
   |
   v
carrier RTP

The exact topology depends on where encryption terminates, whether FreeSWITCH must process the audio and which network boundaries require anchoring. Some calls may use RTPengine only at the public edge. Others may keep media anchored on both legs.

The important point is that a SIP message and an RTP packet can cross different machines, scale for different reasons and fail independently.

A successful 200 OK proves that signalling reached an answer. It does not prove that the negotiated media address is reachable, that SRTP keys were handled correctly or that audio is flowing in both directions.

That difference shapes the whole platform.

Kamailio owns the SIP edge

Kamailio is the front door.

It is good at receiving a large volume of SIP transactions, applying routing policy and deciding where a request should go. Depending on the platform, that can include:

  • transport termination for UDP, TCP, TLS and WSS;
  • source allowlists and carrier authentication;
  • registration routing;
  • rate limits and flood protection;
  • tenant, region or carrier selection;
  • number normalization and routing policy;
  • topology hiding;
  • health-aware routing toward FreeSWITCH pools;
  • calling RTPengine during SDP offer and answer processing.

Kamailio should make routing decisions from information available at the signalling boundary. It should not become the product’s call-workflow database.

There is a tempting progression where every new rule becomes another block in the SIP route script. First it is carrier selection. Then campaign policy. Then customer retries. Then billing. Eventually the edge proxy knows half of the application and deployments become dangerous.

A useful boundary is:

Kamailio decides where a SIP request may go. The application decides why the call exists and what it means to the product.

That keeps the public SIP edge fast, replaceable and easier to protect.

It also gives the platform a clean place for admission control. If no media worker or FreeSWITCH node can safely accept another call, rejecting or deferring the attempt at the edge is better than allowing it to connect into silence.

FreeSWITCH owns call execution

FreeSWITCH is where a call becomes more than routed SIP.

As a back-to-back user agent, it creates and owns channel legs. It can bridge calls, play prompts, collect DTMF, record audio, run IVRs, transfer channels and connect media to an AI service.

That makes it the natural execution worker for:

  • inbound and outbound call legs;
  • bridges and conferences;
  • announcements and voicemail playback;
  • recording;
  • DTMF handling;
  • call transfers;
  • media bugs and external audio streams;
  • codec conversion when it is genuinely required;
  • application-controlled channel variables and dialplan logic.

The word worker matters.

A FreeSWITCH node should own the runtime channels assigned to it, while the application keeps a durable record of which node owns each call. Later commands — transfer, hangup, playback, recording control — must return to that same node.

EXAMPLE

product call id
    -> FreeSWITCH node id
    -> agent leg UUID
    -> customer leg UUID

Without that mapping, horizontal scaling creates an awkward failure mode: the API accepts a command, sends it to a healthy FreeSWITCH server and receives -ERR No such channel because the call lives somewhere else.

Broadcasting commands to every media node is not service discovery. It is a race condition with extra network traffic.

Treat FreeSWITCH nodes as stateful for the duration of a call. New calls can be distributed across the pool, but an active call has affinity to its current worker.

RTPengine owns the media boundary

RTPengine solves a different class of problem: endpoints often cannot send media directly to the address advertised by the other side.

Browsers use ICE, DTLS and SRTP. Carriers commonly use plain RTP and may expect symmetric media behavior. Private FreeSWITCH nodes should not need public interfaces for every deployment. NAT devices rewrite addresses. Firewalls care about port ranges. SDP may contain an address that is valid only inside another network.

RTPengine can anchor the media so both endpoints send packets to an address the platform controls. Depending on configuration, it can also handle boundaries such as:

  • RTP to SRTP;
  • DTLS-SRTP for WebRTC;
  • ICE processing;
  • IPv4 to IPv6;
  • public to private network traversal;
  • media source validation;
  • media statistics and timeout handling.

It should not become invisible just because it forwards packets well.

An RTPengine instance owns live media sessions. If it disappears, routing the next SIP request to another instance does not magically move the existing RTP stream. New-session failover and active-session continuity are different guarantees.

That is why the control layer needs deterministic selection and session affinity. The offer, answer, re-INVITE and delete operations for one dialog must reach the RTPengine instance that owns its media state.

Media relays also have their own capacity dimensions:

EXAMPLE

network throughput
packet rate
allocated ports
active media sessions
encryption work
recording or transcoding features

Those are not interchangeable with FreeSWITCH channel count or Kamailio transaction rate.

Scale by failure domain

Once responsibilities are clear, each layer can scale for the pressure it actually receives.

Kamailio scales for signalling pressure

Additional Kamailio nodes can accept more SIP and WSS traffic, distribute transactions and isolate public network noise from the rest of the platform.

The shared state depends on the use case. Stateless routing is easy to distribute. Registrations, dialog-aware features and coordinated rate limits may require a shared database, replicated location service or consistent ownership strategy.

The goal is not to make every edge node share everything. It is to share only the state required for correct routing.

FreeSWITCH scales by call ownership

FreeSWITCH capacity is affected by the media applications enabled on its calls. A node forwarding compatible codecs has a different profile from one transcoding, recording, running answering-machine detection and streaming audio to AI services.

Place new calls using the metrics that describe that work, not only a generic channel count.

A practical scheduler may consider:

  • active channel legs;
  • calls by media profile;
  • CPU and run-queue pressure;
  • transcoding activity;
  • AI stream count;
  • recent originate latency;
  • node drain and health state.

Once assigned, the call remains on that node until it ends.

RTPengine scales by media-session ownership

RTPengine nodes should be selected before the SDP is rewritten and kept consistent for the dialog lifetime.

Their load is primarily packet and bandwidth work, with additional cost for encryption, recording or other enabled processing. Network-interface pressure may become relevant before application CPU looks alarming.

Scaling the relay pool therefore needs media metrics, not API metrics.

The application scales for orchestration

The application backend owns product lifecycle, tenant policy, external AI integrations and the mapping between durable call records and runtime resources.

It needs its own partitioning rules:

  • which worker processes events for a call;
  • how duplicate events are handled;
  • how commands reach the correct FreeSWITCH node;
  • how a call becomes terminal if an originate fails asynchronously;
  • how retries avoid creating a second call;
  • how downstream AI capacity is reserved.

Separating the telephony components does not remove distributed-system problems. It gives them names.

Failover protects the next call first

Telephony failover is frequently described too generously.

If a Kamailio node fails, a load balancer or DNS strategy may route a new request to another edge node. If a FreeSWITCH node fails, new calls can be assigned elsewhere. If an RTPengine node fails, new media sessions can use another relay.

An active call is harder.

A live FreeSWITCH channel contains runtime state and media processing that another node does not automatically possess. A live RTPengine session has packet-routing and cryptographic state tied to that instance. Moving either transparently is a much bigger design than detecting a failed health check.

The honest availability model is usually:

EXAMPLE

edge failure
  new transactions reroute

FreeSWITCH failure
  calls on that node may end
  new calls use healthy nodes

RTPengine failure
  media sessions on that node may fail
  new sessions use healthy nodes

application failure
  active media may continue temporarily
  product commands and lifecycle updates may pause

That may sound less impressive than “automatic failover,” but it is operationally useful. It defines the blast radius, recovery behavior and customer impact before an incident writes the definition for you.

Draining is equally important. A node being removed from service should stop receiving new calls while existing calls finish. In telephony, graceful shutdown may take minutes because the workload is a conversation, not an HTTP request.

Admission control is product behavior

A queue is often helpful in a web application. In live voice, an invisible queue can become dead air.

The platform has several independent admission limits:

  • carrier calls per second and concurrent channels;
  • available FreeSWITCH execution capacity;
  • RTPengine packet, bandwidth and port capacity;
  • STT, TTS and LLM session limits;
  • database and event-processing lag;
  • tenant or campaign policy.

Starting a call before those resources are available moves the failure into the customer experience. The SIP call may answer while the AI stream is still waiting, the TTS service is saturated or the control plane is several seconds behind.

Admission should happen before expensive work whenever possible.

For an outbound platform, that may mean pacing originates. For an inbound platform, it may mean routing to a fallback destination, playing a controlled announcement or returning a clear failure response.

The right degraded behavior is a product decision. The architecture’s job is to make that decision enforceable.

Observability needs shared identity

Separate components are useful only if one call can still be reconstructed across them.

I want a correlation chain that begins with the product call ID and reaches every runtime identifier:

EXAMPLE

product call id
SIP Call-ID
Kamailio transaction and dialog identifiers
FreeSWITCH node and channel UUIDs
RTPengine session tags
carrier reference
AI session identifiers

Each layer should expose metrics for its own responsibility.

For Kamailio:

  • requests and responses by method and response class;
  • transaction latency;
  • registrations and WSS connections;
  • routing decisions;
  • rate-limit and admission rejections;
  • upstream health and failover attempts.

For FreeSWITCH:

  • active channels and bridged calls;
  • originate and answer latency;
  • calls by media application;
  • command failures;
  • event queue age;
  • recording, transcoding and AI-stream activity;
  • node drain state.

For RTPengine:

  • active sessions and streams;
  • packet and byte rates;
  • packet loss and jitter where available;
  • allocated ports;
  • interface errors;
  • session timeouts;
  • encryption or processing load.

For the application:

  • call lifecycle transitions;
  • commands by result;
  • event processing lag;
  • calls missing terminal outcomes;
  • agent and customer ownership consistency;
  • downstream AI availability;
  • admission decisions and their reason.

A single “telephony healthy” badge cannot represent all of this honestly. A better dashboard shows which boundary is healthy and which class of calls is affected.

What the business actually buys

This architecture is not valuable because three open-source systems look impressive in a diagram.

It is valuable because responsibilities become independently measurable and replaceable.

That can create practical business options:

  • carriers can be changed without rebuilding call orchestration;
  • WebRTC access can evolve without exposing every FreeSWITCH node publicly;
  • media capacity can grow separately from API capacity;
  • tenant and regional routing policies can live at a controlled edge;
  • expensive call features can be scheduled onto appropriate workers;
  • incidents can affect one pool or region instead of the whole platform;
  • infrastructure cost can be tied to signalling, media and application workloads separately.

There is a tradeoff. Owning the stack also means owning SIP security, routing policy, media quality, upgrades, capacity planning, on-call response and the awkward edge cases between components.

The business case should compare more than per-minute vendor pricing. It should include:

EXAMPLE

vendor spend avoided
+ control over orchestration and data
+ ability to optimize high-volume workloads
- engineering and operations cost
- incident-response responsibility
- capacity held for redundancy

For a small or rapidly changing product, a managed platform may still be the sensible choice. For a mature, high-volume voice operation, infrastructure ownership can become a strategic capability — provided the team is actually prepared to operate it.

The practical design rule

A reliable high-load telephony platform does not need every component to survive every failure.

It needs every component to have a clear job, a measurable limit and an honest recovery path.

Kamailio should protect and route the SIP edge. FreeSWITCH should execute calls on an explicitly owned worker. RTPengine should anchor media at the network boundary. The application should own durable product state and orchestration.

Then scale tests become sharper:

  • Can the edge admit and route the intended start rate?
  • Can FreeSWITCH execute the real media profile?
  • Can RTPengine carry the resulting packet load?
  • Can the application process lifecycle events without falling behind?
  • Does overload produce controlled rejection instead of connected silence?
  • Does one failed node have a known blast radius?

That is what “high load” should mean in an architecture discussion.

Not one impressive concurrent-call number. A platform whose pieces know what they own — and fail without taking every other piece with them.