Video Streaming · September 10, 2026 · Stan Reshetnyk

Scaling FreeSWITCH in Production: Clustering, Kubernetes & 10k Concurrent Calls

Scaling FreeSWITCH in Production: Clustering, Kubernetes & 10k Concurrent Calls

By Stan Reshetnyk, CTO, Trembit — protocol-level WebRTC, SIP, and media-server engineering · Last updated 18 August 2026

Part of Trembit’s FreeSWITCH in Production series — a practitioner cluster on choosing, building, scaling, and securing real-time voice and voice AI on FreeSWITCH.

A single FreeSWITCH instance scales vertically to a real but finite ceiling — bounded by the CPU cost of per-call transcoding, RTP packet throughput on the network card, and the operating system’s file-descriptor and socket limits. Past that point, production scale isn’t a bigger box; it’s an architecture: a fleet of FreeSWITCH nodes behind a dedicated SIP proxy (Kamailio or OpenSIPS) that distributes signaling and registration, with active call state deliberately kept per-node rather than shared — because media (RTP) is the part that doesn’t containerize or cluster the way a stateless HTTP service does. The honest thesis of this guide: most FreeSWITCH production failures aren’t FreeSWITCH bugs. They’re naive infrastructure decisions — treating a stateful, latency-sensitive media server like a stateless microservice — applied to a workload that punishes exactly that assumption.

Key takeaways

  • A single FreeSWITCH node’s concurrency ceiling is governed by codec/transcoding CPU cost, RTP packet-per-second throughput on the NIC, and OS-level file-descriptor/socket limits — not by an artificial software cap. Raise the ceiling with tuning (max-sessions, ulimit, kernel port ranges); past it, you shard.
  • FreeSWITCH does not cluster itself. Horizontal scale means running multiple independent nodes behind a SIP proxy — Kamailio or OpenSIPS — that distributes signaling, registration, and call routing. This is the same conclusion the FreeSWITCH vs Asterisk comparison reached at a summary level; this piece is the depth behind it.
  • Registration state and active-call/media state are different scaling problems. Registration can be centralized or proxied; in-progress call/media state is expensive and risky to share across nodes, so it’s kept node-local and recovered on failover rather than live-replicated. That single distinction shapes every clustering and HA decision below.
  • Running FreeSWITCH in Kubernetes without accounting for RTP is a common, severe failure mode. RTP needs a wide, predictable UDP port range and NAT-transparent networking, which standard ClusterIP/overlay service networking actively breaks. Production K8s deployments typically run FreeSWITCH with hostNetwork: true and a planned RTP port range, not default pod networking.
  • The title’s “10k” is an engineering-reference ceiling, not a Trembit result. Published community and operator benchmarks put a single well-tuned FreeSWITCH server in roughly the 5,000–10,000 concurrent-call range for pass-through (non-transcoded) media — and transcoding, conferencing, recording, or an AI audio fork on each call can cut that by an order of magnitude. The real number for any deployment depends entirely on codec mix, call complexity, and hardware.
  • HA failover is a deliberate configuration, not a default. With track-calls enabled on both SIP profiles and a shared database backend (ODBC/PostgreSQL) holding call-recovery state, a surviving node can resume tracked calls within a few seconds of a failure being detected — but only if you configured it that way, and it still won’t save a call whose only state was node-local.

If you own the reliability or compliance mandate rather than the config files, three sections carry the decision without the protocol-level middle: how many calls FreeSWITCH can actually handle, what actually breaks at scale, and the production-scaling checklist you can bring straight into an architecture meeting.


Why doesn’t FreeSWITCH just scale vertically forever?

Because every concurrent call consumes a specific, non-uniform set of resources, and each of those resources hits a wall at a different point. Understanding which wall you’ll hit first is the whole game — it tells you whether to tune, add hardware, or shard.

Per call, a FreeSWITCH node is paying for:

  • RTP packet processing. Voice RTP typically sends a packet every 20 ms — 50 packets per second per direction, per stream. A few thousand concurrent calls is hundreds of thousands of packets per second the kernel and NIC have to move. Packet-per-second throughput, not raw bandwidth, is often the real ceiling.
  • Codec transcoding — if you’re not passing through. Bridging two legs on the same codec (pass-through) is cheap. Transcoding between codecs — G.711 to Opus, or anything to G.729 — is CPU-bound signal processing on every frame, and it’s the single largest swing factor in how many calls a box holds. This is why the same server can carry an order of magnitude more pass-through calls than transcoded ones.
  • Session and channel memory, plus the event and state-machine overhead FreeSWITCH runs per leg.
  • ESL / event volume, if an external application (a dashboard, a monitor, or a voice-AI pipeline) is subscribed to call events — covered in its own section below.

Stack those up and you meet the vertical limits in a predictable order: the NIC’s packet-per-second ceiling, the kernel’s socket and file-descriptor limits (each call is multiple open descriptors — SIP, RTP, RTCP, timers — and the default ulimit on a stock box is far too low for thousands of calls), and eventually CPU saturation from transcoding. Tuning buys real headroom here: raising max-sessions, lifting file-descriptor limits, widening the kernel’s ephemeral port range, and pinning FreeSWITCH away from the noisy default configuration.

But two things tuning can’t fix. First, a single box is a single point of failure — no amount of vertical headroom survives that box rebooting. Second, past the packet and descriptor ceilings, the returns don’t just diminish, they stop: you can’t buy a NIC that makes the split-brain-on-reboot problem go away. As the comparison piece put it plainly — “past a single box, neither engine clusters itself — that’s what the SIP proxy in front is for.” That’s the pivot the rest of this guide builds on.


How do you cluster FreeSWITCH horizontally?

You don’t turn FreeSWITCH into a cluster — because it has no native shared-nothing or shared-state cluster mode the way some databases do. Horizontal scale means running multiple independent FreeSWITCH nodes and solving distribution and coordination at a layer above them. The architecture that works, and the one nearly every high-volume VoIP shop converges on, is a SIP proxy in front of a fleet of media nodes.

The reason this works — and the reason it’s not optional — comes down to one distinction that runs through this entire topic:

Signaling/registration scaling is relatively easy. Media/call-state scaling is hard. SIP registration is small, frequent, and cacheable — it can be centralized in a database or handled by the proxy, and a stateless-ish signaling tier scales the way web infrastructure does. RTP media is the opposite: it’s a continuous, latency-sensitive, stateful stream tied to a specific node for the life of the call. You can’t move an in-progress media session to another node mid-call without dropping audio, and replicating live call state across the fleet in real time is expensive and fragile. So the design principle is: distribute signaling freely; pin media; recover call state on failure rather than sharing it live.

That gives you the standard shape:

  1. A SIP proxy layer (Kamailio or OpenSIPS) is the stable signaling edge. Clients register and send calls to the proxy, not to any specific FreeSWITCH node.
  2. A fleet of FreeSWITCH media nodes does the actual call processing and RTP anchoring. Nodes can be added, drained, or replaced without re-pointing every client, because clients only ever knew the proxy.
  3. A shared database backend holds the state that legitimately needs sharing — registration location, and (for HA) call-recovery data — while live media stays node-local.

Shared-state options are real but should be adopted with clear eyes about what they cost. FreeSWITCH can back its core state on ODBC/PostgreSQL, and registration can be centralized so any node (or the proxy) can locate an endpoint. What you should not do is reach for “just share all the state” as a reflex — sharing registration is cheap and sensible; trying to share live call state across the fleet is where teams build themselves a distributed-systems problem far harder than the telephony one they started with.

What should be shared across nodes, and what shouldn’t?

A direct answer, because it’s the most useful mental model in this whole piece:

  • Registration / location state → shareable. Centralize it (database-backed) or let the SIP proxy own it. Any node needs to be able to find where an endpoint currently is.
  • Active call / media (RTP) state → node-local by design. Don’t live-replicate it. Recover it on failover via FreeSWITCH’s call-recovery mechanism (below), not by streaming it between nodes.
  • Dialplan and configuration → identical across nodes. This is a config-management problem (every node ships the same dialplan and modules), not a runtime clustering problem. Solve it with your deployment tooling, not with FreeSWITCH.

Get that three-way split right on paper before you write a line of orchestration, and most of the hard clustering decisions resolve themselves.


How do you load-balance SIP across a FreeSWITCH fleet?

This is where Kamailio or OpenSIPS earns dedicated depth. A SIP proxy is a fast, purpose-built piece of signaling infrastructure that sits at the edge and does four jobs a FreeSWITCH node shouldn’t be doing itself at scale: it terminates client registration, routes and dispatches calls across the fleet, distributes load, and presents a stable signaling address so the media nodes behind it are free to come and go. Kamailio’s dispatcher module is the classic mechanism for spreading calls across a defined set of backend nodes (its counterpart in the OpenSIPS world does the same job).

The critical thing to be precise about — because getting it wrong produces a design that quietly doesn’t work — is the split of responsibilities:

  • The SIP proxy handles signaling only. It routes SIP (INVITE, REGISTER, the call-setup dialogue). It decides which FreeSWITCH node a call lands on.
  • The SIP proxy does not handle media. Once the proxy has dispatched the call, RTP flows directly between the client and the chosen FreeSWITCH node — it does not tunnel back through the proxy. Kamailio and OpenSIPS are not media servers; they don’t anchor RTP or transcode.

There’s an honest, more complex variant worth naming rather than hiding: in some topologies you do want media to traverse a controlled point — for NAT traversal, for topology hiding, or for a session-border function — and for that you add an RTP relay component (an RTP proxy such as RTPengine alongside Kamailio). That’s a deliberate choice with its own capacity cost (now you’re paying to relay packets you could have sent directly), not the default. Reach for it when a requirement demands it, not reflexively.

The operational payoff is what makes all this worth the added moving parts: with a proxy in front, you can scale FreeSWITCH nodes independently, drain a node for maintenance, and do rolling deployments without dropping the signaling layer. The proxy is the seam that lets the media tier be cattle instead of pets. That’s the difference between “we have to schedule downtime to patch the phone system” and “we roll nodes one at a time and nobody notices.”


Can you run FreeSWITCH in Docker and Kubernetes?

Yes — and this is where more scaling projects go wrong than anywhere else, because the request usually arrives as “just containerize it like everything else we run,” and FreeSWITCH is emphatically not like everything else you run. It’s a stateful, latency-sensitive, wide-UDP-port-range media server wearing a “just another service” costume. Containerize it as if it were a stateless HTTP app and the media path breaks in ways that pass every health check and fail every real call.

Naive Kubernetes FreeSWITCH pod networking breaks RTP vs a host-networking production pattern with a dedicated port range

Here’s precisely why naive containerization breaks, and what the production pattern does about it:

  • RTP and standard Kubernetes networking don’t mix. ClusterIP and overlay pod networking are built for a handful of well-known TCP ports and a NAT layer that’s invisible to HTTP. RTP is the opposite: it needs a wide, predictable range of UDP ports open and reachable, and both SIP and RTP carry IP/port information inside the payload (in the SDP negotiated at call setup) that has to stay valid for the life of the call. Put an overlay network’s NAT on top of a media protocol that already has to solve NAT traversal, and you get NAT-inside-NAT — the SDP advertises an address the far end can’t route to, and audio goes nowhere even though signaling succeeded.
  • hostNetwork: true is the common production pattern, not a hack. Running the FreeSWITCH pod on the node’s own network stack (hostNetwork in the Kubernetes pod spec) lets it bind directly to the host’s interface and ports, sidestepping the overlay-NAT problem entirely. Operator community guidance is consistent on this: for a media server, host networking is the pragmatic default, precisely because bridged/overlay networking can’t feasibly forward the large UDP port range SIP/RTP needs. The trade-off is real, though: a host-networked pod sits outside the cluster’s normal network isolation, so Kubernetes NetworkPolicy no longer fences it, and because it binds the node’s own ports directly you’re effectively pinning one media pod per node for a given RTP port range — you lose the density and the network segmentation you’d get from a stateless service, in exchange for media that actually works.
  • RTP port-range planning is mandatory. FreeSWITCH needs a defined UDP range for RTP — the documented default sits in the high-16384-and-up range — and that range has to be open, consistent, and sized for your concurrency (each call consumes RTP/RTCP ports). Kubernetes NodePort and dynamic/ephemeral pod IPs are actively hostile here: a protocol whose negotiated media address must remain valid for a call’s full duration does not tolerate the ephemeral, re-assignable networking that makes stateless pods convenient. Plan the range, open it, and keep it stable per node.
  • What Kubernetes still buys you, done right. This is not “K8s is bad for FreeSWITCH” — that’s the wrong takeaway. Kubernetes gives you self-healing (a crashed node’s pod restarts), declarative fleet management, and easy horizontal scaling of the stateless-ish SIP-signaling/proxy tier. The honest framing is that the proxy tier is cloud-native-friendly, while the media-node tier needs more careful, less “default” treatment than a typical web service — host networking, planned ports, and an awareness that pods carrying live calls can’t be rescheduled as freely as stateless ones.

Why does a naive Kubernetes deployment of FreeSWITCH usually fail?

Because standard pod networking breaks RTP. FreeSWITCH needs a wide, predictable range of UDP ports and NAT-transparent reachability so the media addresses it negotiates at call setup stay valid; Kubernetes ClusterIP/overlay networking adds a NAT layer and doesn’t feasibly forward that port range, so signaling succeeds (SIP is a small, well-behaved dialogue) while audio silently fails. The fix is to run the FreeSWITCH media pods with host networking (hostNetwork: true) and a planned, stable RTP port range — and to treat the stateless SIP-signaling tier and the stateful media tier as two different kinds of workload, not one.

See also: teams doing this specifically to bridge browser (WebRTC) callers into a FreeSWITCH fleet hit an additional layer of NAT and ICE considerations — that’s its own topic, covered in Connecting FreeSWITCH to WebRTC.


What does FreeSWITCH high availability and failover actually look like?

FreeSWITCH HA failover: a shared DB backend lets a surviving node resume tracked calls after a node failure

Real FreeSWITCH HA is a specific, documented mechanism (SignalWire’s FreeSWITCH documentation) — not “just run two servers.” It rests on two pieces working together:

  1. track-calls enabled on your SIP profiles. With call tracking on, FreeSWITCH persists call state (session variables, similar to CDR data) to its core database every time a channel changes state. If a call has two legs, track-calls must be enabled on both profiles the call uses. There’s a minor performance cost — you’re writing to the DB on every state change — and that cost is the price of recoverability.
  2. A shared database backend both nodes can read. To recover calls on a second server, the call-recovery data has to live somewhere both machines see — ODBC or PostgreSQL, with the DSN configured in the SIP profiles and in switch.conf.xml. One non-obvious operational detail that trips people up: the recovery query selects by hostname, so the involved nodes are configured to share the identity the recovery logic keys on.

With both in place, when a node fails and the failure is detected (via a health-check / IP-failover layer such as a keepalived-style virtual-IP setup), a surviving node reads the shared call-recovery state and resumes the tracked callsSignalWire’s FreeSWITCH documentation and community guidance typically describe this as happening within a few seconds of failure detection (commonly cited around a 3–5 second window), with a short audio drop rather than a full call teardown.

What HA does not magically solve is as important as what it does:

  • A call whose state was only node-local — mid-transcode, or relying on in-memory state that never hit the shared DB — can still drop. Failover is call recovery, not zero-impact live migration.
  • Split-brain is a real risk if failure detection is sloppy: two nodes both believing they own the same call or registration. HA is only as good as the health-checking and fencing around it (see the failure-modes section).
  • The mechanism recovers tracked calls. If track-calls wasn’t on, or the DB was unreachable at the moment of failure, there’s nothing to recover from.

A note on where the SignalWire docs live now: the specific deep-link URLs for FreeSWITCH’s “High Availability” and performance-tuning pages currently 301-redirect to the docs homepage (developer.signalwire.com/freeswitch/) after a site restructure — I confirmed this while writing. Cite the current live page you land on, not an old indexed deep path, and re-verify the exact mechanism wording before publishing any specific number.


Registration scaling vs. media scaling — why they’re different problems

This theme runs through everything above, and it deserves its own statement because it’s the single most useful lens for the whole topic: the thing that overwhelms your registrar and the thing that overwhelms your media nodes are different failures, and solving one does nothing for the other.

Registration is a signaling-layer capacity problem. SIP endpoints re-register periodically, and the pathological case is a registration storm — a mass simultaneous re-registration event after a network blip, a proxy restart, or a fleet of clients all rebooting at once. Thousands of endpoints hitting the registrar in the same second can knock over the registrar or the database behind it while not a single call is in progress. This is a genuine, common outage cause, and it’s entirely distinct from “too many calls.” You defend against it at the signaling tier: a proxy built for registration load, sane re-registration intervals (with jitter so clients don’t synchronize), and a registrar/database provisioned for the peak burst, not the average.

Media is a per-node resource problem. Active-call capacity is bounded by the RTP/transcoding/descriptor limits covered earlier, and it lives on the media nodes. You defend against it by sharding across the fleet and pinning media per node.

The practical consequence: you have to load-test and provision the two independently. A test that ramps concurrent calls tells you nothing about how your registrar behaves when 10,000 clients re-register in the same two seconds. Teams that only test call volume get blindsided by the registration storm — the outage that arrives with zero active calls on the graph.


How do you scale the ESL / event-socket layer under load?

FreeSWITCH’s Event Socket Layer (ESL) is how external applications control and observe calls — originate, bridge, play, hang up, and subscribe to events. At small scale it’s invisible. Under load, and especially under integration complexity, ESL becomes its own resource to manage, with three things to watch:

  • Event volume. Every channel state change can emit events. A busy node with many subscribers is doing real work just fanning events out; a subscriber that asks for every event on a high-concurrency node is buying a firehose.
  • Listener / connection count per node. Each ESL consumer (monitor, dashboard, dialer, AI pipeline) is a connection with its own lifecycle.
  • Back-pressure discipline. This is the one that bites: a slow or misbehaving ESL consumer — a monitoring tool that stalls, or a pipeline holding sessions open — can back-pressure onto the call-processing path if the architecture lets it. The operational rule is that nothing hanging off ESL should ever be able to slow down the calls themselves.

There’s a cross-link worth making explicit here, because it changes the sizing math. A voice-AI deployment hooks into ESL for every call — that’s exactly how the voice AI agent on FreeSWITCH architecture attaches an STT→LLM→TTS pipeline to a live leg. An AI-in-the-media-path system is not sized like a pure telephony switch: each call now carries a persistent control connection and a streamed audio fork out to an inference service, and that per-session overhead (plus the failure mode where a slow model call stalls a session) has to be in your capacity model from the start. If your FreeSWITCH fleet carries real-time AI, size the ESL and audio-fork layer deliberately — the concurrency ceiling for an AI-augmented node is a different, lower number than the same box doing plain bridging.

That ESL-per-call design (inbound vs. outbound mode, async handling, avoiding blocking the media threads) is the subject of the voice-agent build guide above — this section is only the scaling angle on it.


How do you observe and monitor a FreeSWITCH cluster in production?

You can’t operate a fleet you can’t see one call at a time, so observability is architecture, not an afterthought. The point isn’t a specific product — it’s instrumenting the right categories of signal, at both tiers, so the failure modes in the next section are visible before they page you:

  • Per-node call/session counts — how many active sessions each media node carries, so you see imbalance or a node approaching its ceiling.
  • CPU and RTP throughput per node — CPU tracks transcoding load; RTP packets-per-second tracks the NIC ceiling. Watch both, because they saturate independently.
  • Registration counts and rate — the early-warning signal for a registration storm, and a distinct metric from call volume.
  • File-descriptor and socket usage — descriptor exhaustion is a silent killer; graph headroom against the limit.
  • Event-socket connection health — listener count and consumer lag, so a stalled ESL consumer surfaces before it back-pressures calls.
  • SIP-proxy layer metrics — the proxy’s own routing decisions, dispatch distribution, and error/timeout rates. The proxy is your signaling edge; its health is the fleet’s health.

Keep the instrumentation architectural and vendor-neutral. Any competent metrics-and-logs stack can carry these signals; what matters is that you’re watching both the signaling tier and the media tier, because — per the previous section — they fail for different reasons and a dashboard that only shows call volume will miss half of what takes you down.


What actually breaks FreeSWITCH deployments at scale — the real operational failure modes?

This is the section that separates “read the docs” from “operated the thing.” None of these are exotic; all of them are well-understood failure classes in the FreeSWITCH and broader VoIP-infrastructure operator community, and every one of them is invisible in a naive day-one load test. They’re presented here as documented failure classes, not as war stories attributed to any client.

  • Registration storms overwhelming the registrar/database. The outage that arrives with zero active calls, covered above. Mass simultaneous re-registration hammers the registrar or its database. Mitigation lives at the signaling tier: jittered re-registration intervals, a proxy provisioned for burst, a database sized for peak.
  • File-descriptor and socket exhaustion. A node handling more concurrent RTP streams than its ulimit was tuned for runs out of descriptors and starts failing calls in confusing, partial ways. The default OS limits on a stock box are far below what thousands of calls need — this is a tuning omission that only shows up under real concurrency.
  • RTP port-range exhaustion. A too-narrow configured UDP range — especially in a containerized or host-networked deployment where someone capped the range without doing the math — runs out of ports under load, and new calls can’t establish media even though CPU and signaling are fine. Size the range to your concurrency target with headroom.
  • Split-brain in a naive HA setup. Two nodes both believing they own a call or a registration, because failure detection or fencing was sloppy. This corrupts state and produces duplicated or misrouted calls — HA that isn’t carefully fenced is sometimes worse than no HA.
  • The “it worked in the load test but not in production” gap. The most common and most humbling. Synthetic load tests rarely reproduce real registration-storm patterns, real codec mixes (a lab test is usually one codec, pass-through; production is a messy blend with transcoding), or real network jitter and NAT diversity. A box that held 5,000 clean pass-through calls in a lab can fall over at a fraction of that on a real, transcoded, NAT-diverse, storm-prone network. Test against realistic conditions, or production will test them for you.

The through-line: at scale, FreeSWITCH rarely fails because “FreeSWITCH can’t do it.” It fails because an infrastructure assumption — descriptor limits, port ranges, storm behavior, fencing — went unexamined until load found it.


So how many concurrent calls can FreeSWITCH actually handle?

There is no single universal number, and anyone who quotes you one without a hardware and workload spec is quoting a benchmark from a machine that isn’t yours. The honest answer is a function of four variables:

  • Codec and transcoding. Pass-through (both legs on the same codec, no conversion) is cheap; transcoding is CPU-bound and the single biggest swing factor. The same box can carry an order of magnitude more pass-through calls than transcoded ones.
  • Hardware — core count and, critically, NIC packet-per-second throughput, not just bandwidth.
  • Call complexity — simple bridging is cheap; conferencing, recording, and an AI audio fork on each call each add real per-session cost.
  • Tuningmax-sessions, file-descriptor limits, kernel port ranges, and keeping FreeSWITCH off the noisy defaults.

With that framing, here’s what the published range looks like — explicitly as what the platform can do per external benchmarks, never as a Trembit result: community and operator writeups put a single well-tuned FreeSWITCH server on strong hardware in roughly the 5,000–10,000 concurrent-call range for pass-through media, with named operator guides — such as Sunil Kumar Nayak’s writeup on tuning FreeSWITCH for 5,000+ concurrent calls — describing the tuning (raising max-sessions toward 10,000-plus with headroom, lifting ulimit, dedicating the box to media by moving SIP registration to a Kamailio edge) required to approach the top of that range. The moment you add transcoding, conferencing, recording, or AI inference per call, expect that figure to drop substantially — potentially by an order of magnitude. So the “10k” in this article’s title is a ceiling for an ideal, tuned, pass-through workload, not a promise for yours.

Where does Trembit sit in this? We operate FreeSWITCH in production, and we’re deliberately precise about what that means. On a HIPAA/GDPR healthcare communications platform we build for, FreeSWITCH runs in production as the SIP proxy and PSTN-bridge layer — connecting WebRTC and mobile clients with phone users and with hospital SIP conferencing systems, so a call can span a browser, a mobile app, the public phone network, and an in-hospital conference bridge inside one compliant session. That’s a real production deployment and a real reliability mandate. For related healthcare-platform context, our healthcare video-translation case study covers a different subsystem on that same class of platform — the in-call translation, which runs on a separate Mediasoup SFU — so read it for the compliance-and-real-time-media environment we work in, not as a walk-through of the FreeSWITCH SIP-proxy role itself. What that production experience gives us is architecture and failure-mode judgment, which is what this whole guide is built from. What it does not give us — and what we won’t manufacture — is a headline “we ran N thousand concurrent calls” number, because we don’t attach client scale figures to marketing. The credibility here is the architecture, stated confidently; the number is whatever your codec mix and hardware actually deliver under an honest load test.

See also: scaling inside a compliance boundary (on-prem, data residency, keeping voice data in the firewall) adds its own HA and capacity considerations — covered in FreeSWITCH for HIPAA/GDPR-Compliant Voice.


A production-scaling checklist: what to decide before you scale FreeSWITCH

Checklist for scaling FreeSWITCH: registration-vs-media state, proxy placement, container networking, HA, load testing, ESL

Bring this into the architecture meeting. If you can’t answer these confidently, that’s your next work — not more hardware:

  1. Have you separated registration-state scaling from media-state scaling in the design? They fail differently and must be provisioned and load-tested independently.
  2. Is there a dedicated SIP proxy (Kamailio/OpenSIPS) in front of the fleet — or is a single FreeSWITCH node still your signaling edge? A single node as the edge is a single point of failure and a ceiling you’ll hit.
  3. If containerized: is FreeSWITCH running with host networking and a planned RTP port range — or default cluster networking? Default pod networking breaks RTP; this is the most common K8s failure.
  4. Is HA configured with track-calls on both profiles and a shared DB backend — or is failover untested? Two servers without call-recovery configuration is not HA.
  5. Have you load-tested against a realistic registration-storm and a realistic codec mix — not just raw pass-through call count? The lab number and the production number diverge exactly here.
  6. Is the ESL/event layer sized for everything actually listening to it (monitoring, dashboards, AI pipelines) — with back-pressure isolation so a slow consumer can’t stall calls?
  7. Have you set the OS limits — file descriptors (ulimit), max-sessions, kernel port range — to your concurrency target with headroom, rather than shipping stock defaults?

In our engineering judgment, most FreeSWITCH scaling failures aren’t FreeSWITCH problems — they’re infrastructure decisions made before anyone stress-tested the registration layer or the RTP path under NAT. If you’re scaling a FreeSWITCH deployment and want a second set of eyes before that assumption surfaces under load, that’s an architecture-review conversation — the kind our real-time voice AI team runs on media-server architectures that have to hold at concurrency rather than collapse under load.


Frequently asked questions about scaling FreeSWITCH

Can FreeSWITCH run in Kubernetes? Yes, but not with default pod networking. RTP media needs a wide, predictable UDP port range and NAT-transparent reachability, which standard ClusterIP/overlay networking breaks — signaling succeeds while audio silently fails. The production pattern is to run FreeSWITCH media pods with hostNetwork: true and a planned, stable RTP port range, and to treat the stateless SIP-signaling tier and the stateful media tier as two different kinds of workload.

Does FreeSWITCH support native clustering? No. FreeSWITCH has no built-in cluster mode. Horizontal scale means running multiple independent nodes behind a SIP proxy (Kamailio or OpenSIPS) that distributes signaling and registration, with active call/media state kept per-node and recovered on failover rather than shared live.

How many concurrent calls can FreeSWITCH handle? It depends on codec (pass-through vs. transcoded), hardware (cores and NIC packet-per-second throughput), call complexity (conferencing/recording/AI add cost), and tuning — there’s no universal number. Published community and operator benchmarks put a single well-tuned server in roughly the 5,000–10,000 concurrent-call range for pass-through media; transcoding or per-call AI can cut that by an order of magnitude. Treat any flat figure without a hardware and workload spec as meaningless, and load-test your own workload.

Do I need Kamailio or OpenSIPS in front of FreeSWITCH to scale? Past a single box, effectively yes. FreeSWITCH doesn’t cluster itself, so a SIP proxy provides the stable signaling edge, distributes calls across the fleet, and lets you add, drain, and roll nodes without re-pointing clients. The proxy handles signaling only — RTP media still flows directly to the chosen FreeSWITCH node (unless you deliberately add an RTP relay).

What causes FreeSWITCH to fail under load? The recurring culprits are infrastructure assumptions, not engine bugs: registration storms overwhelming the registrar/database, file-descriptor and socket exhaustion from untuned OS limits, RTP port-range exhaustion, split-brain in a poorly fenced HA setup, and the gap between a clean lab load test and a real transcoded, NAT-diverse, storm-prone production network.

How does FreeSWITCH handle failover? Through call recovery: with track-calls enabled on both SIP profiles and a shared database backend (ODBC/PostgreSQL) holding call-recovery state, a surviving node can resume tracked calls within a few seconds of a failure being detected. It’s a deliberate configuration, not default behavior, and it recovers tracked calls — a call whose state was only node-local can still drop.


Scaling a media server is a different problem than scaling a web service: the failure modes are different, and most of them only show up under real production load — a registration storm with no calls on the graph, RTP dying inside an overlay network, a “highly available” pair that was never actually configured to recover a call. If you’re taking a FreeSWITCH deployment from a working prototype to a production-scale, highly available system — or inheriting one that’s already under load and misbehaving — Trembit reviews and rescues real-time media architectures before the next incident forces the issue. Bring the specific decision to a free 30-minute call with an engineer: the clustering topology you’re weighing, the Kubernetes RTP path you’re not sure about, the failover you’ve never tested, or the concurrency ceiling you’re stuck at — and we’ll pressure-test it. No deck, no pitch. Start with our real-time voice AI and WebRTC development and architecture-review teams.

Part of Trembit’s FreeSWITCH in Production series — a practitioner cluster on choosing, building, scaling, and securing real-time voice and voice AI on FreeSWITCH.

Stan Reshetnyk
Written by Stan Reshetnyk CTO

Related Articles

Ready to start?

Let Us Work Together

Tell us about your project and we'll get back within 24 hours.

Get in Touch