DEV Community

Cover image for Building a Multi-Tenant PBX on FreeSWITCH: The Parts That Actually Bite
Jack Morris
Jack Morris

Posted on

Building a Multi-Tenant PBX on FreeSWITCH: The Parts That Actually Bite

Most businesses are fine on a hosted PBX plan right up until they are not. The usual breaking point is multi-tenancy. A growing operator or MSP wants to run many customers off one platform, each isolated and each billed separately, and most hosted PBX solution providers simply do not sell that. That is the point where custom PBX development on FreeSWITCH starts to make sense.

I have built a few of these now, and multi-tenancy is where the real work hides. The single-tenant demo comes together fast. Turning it into a platform that safely serves fifty companies is a different exercise. Here are the parts that bit me, and how to handle them.

Tenants are domains, and context is everything

FreeSWITCH models a tenant as a SIP domain. FusionPBX, which sits on top, maps each domain to a tenant in its UI, but underneath it is still FreeSWITCH domains and the directory.

The mistake that will burn you is dialplan context. If every tenant shares the default context, extension 1001 at Acme and 1001 at Globex live in the same namespace, and calls leak across tenants. The fix is to scope the context to the domain.

<!-- Each tenant gets its own context, named for its domain -->
<context name="acme.pbx.example.com">
  <extension name="internal">
    <condition field="destination_number" expression="^(1[0-9]{3})$">
      <action application="bridge" data="user/$1@acme.pbx.example.com"/>
    </condition>
  </extension>
</context>
Enter fullscreen mode Exit fullscreen mode

Route each registration into its own domain context and a tenant physically cannot dial another tenant's extensions. Get this wrong once and you will find out the embarrassing way, usually from a customer.

The core database will surprise you

FreeSWITCH ships with an SQLite core database. It is fine on your laptop. Under a few thousand registrations with churn, SQLite locking becomes the thing that wakes you at 2am.

Move the core DB to PostgreSQL before you scale, not after.

<!-- switch.conf.xml -->
<param name="core-db-dsn"
       value="pgsql://hostaddr=127.0.0.1 dbname=freeswitch user=fs password=secret" />
Enter fullscreen mode Exit fullscreen mode

Registrations, dialog state, and the internal tables all move off the single-writer SQLite file and onto something built for concurrency. This one change removes a whole category of "why did registrations stall" incidents.

One noisy tenant should not sink the rest

On a shared platform, capacity is shared. Without limits, a single tenant running a dialer or getting hit with junk traffic can eat all your channels and degrade everyone else. mod_limit handles this with a per-tenant cap keyed on the domain.

<!-- Cap each tenant to 30 concurrent outbound calls -->
<action application="limit"
        data="hash ${domain_name} outbound 30 !NORMAL_TEMPORARY_FAILURE"/>
Enter fullscreen mode Exit fullscreen mode

Because the realm is ${domain_name}, the counter is per tenant. Acme hitting its ceiling does not touch Globex. Set the number per tenant if your plans differ, which they usually do.

Billing lives or dies on clean CDRs

For an operator this is the part that actually pays the bills, and it is the most underestimated. Every call needs a record tagged with the tenant it belongs to, or rating falls apart.

FreeSWITCH already carries domain_name and the SIP host on the channel, so tag your CDRs with it and key rating on the domain. Whether you push mod_json_cdr into a queue or write straight to a database, the rule is the same: no untagged calls, and reconcile against the switch rather than trusting the app layer alone. CDR drift is quiet, and it costs real money before anyone notices.

Why this ends up as custom work
None of the above is exotic once you have done it. But it is also not something a hosted plan exposes, and no provider portal has a checkbox for it. Domain-scoped routing, a real core database, per-tenant limits, and tenant-clean CDRs are what separate a demo from a platform an operator can run a business on. That gap is basically the whole reason PBX development services exist as something you hire for instead of subscribe to.

I do a fair amount of this kind of custom PBX work, mostly multi-tenant builds for operators and MSPs. If you want the less code-heavy version, the tradeoffs and when it is worth building rather than renting, I got into that side separately.

If you are running FreeSWITCH multi-tenant in production, I am curious what bit you that is not on this list. There is always one more.

Top comments (0)