Skip to content
Custom Software & CRM3 min read

Giving a shared FreePBX estate real multi-tenant isolation

A hosted PBX operator ran several client companies from one FreePBX install with no tenant separation, which made client self-service impossible. Here is the portal architecture that fixed it, including how extension sync and a browser dialler fit together.

FreePBXLaravel 11Vue 3WebRTCDockerRedis

FreePBX is built to run one organisation well. Hosted operators run several on one install, and the moment they do, a gap opens: the administrative interface shows every extension, every call, and every recording in the estate, with no notion of who owns what.

That is workable while the operator is the only one logging in. It stops working the moment a client asks to see their own call history, because there is no view that shows them their data and only their data.

Why not just restrict the FreePBX interface

Because the isolation has to be enforced below the interface, not by it. Hiding rows in a UI is a presentation choice, and presentation choices get bypassed by a direct URL, a stale session, or the next upgrade that resets a template.

For an operator whose clients are competitors with each other, that is not an acceptable risk profile. The isolation needs to sit in the data layer, where every query is scoped whether or not the caller remembered to scope it.

The architecture

  1. 1

    A separate portal, not a FreePBX skin

    A Laravel 11 and Vue 3 application alongside the PBX, so the tenancy model belongs to the portal rather than being retrofitted into FreePBX.

  2. 2

    Enforced tenant scoping

    Data isolation is applied at the query layer, so a tenant identifier is a required part of every read rather than a filter someone has to remember to add.

  3. 3

    Extension synchronisation

    Extensions are synced from the FreePBX user manager tables, so the PBX stays the source of truth and the portal does not drift out of step with it.

  4. 4

    Browser-based WebRTC dialler

    Agents place and control calls from the browser, with no desk phone and no softphone install on each machine.

  5. 5

    Docker behind nginx, Redis for sessions and queues

    Deployed as containers behind an nginx reverse proxy, with Redis handling sessions and background jobs.

Extension sync is where these projects usually break

The tempting design is to let the portal own extensions and push them into FreePBX. It reads cleanly and it fails badly, because any change made directly in FreePBX, which is where an operator's engineers actually work during an incident, silently diverges from the portal.

Syncing the other direction is less elegant and far more durable. FreePBX remains authoritative, engineers keep working the way they already do under pressure, and the portal reflects reality rather than asserting it.

Why WebRTC was worth the effort

A browser dialler removes the per-agent setup step entirely. No softphone install, no per-machine SIP credentials, no desk phone to provision, which for a floor with turnover is the difference between onboarding in minutes and onboarding as a scheduled task.

Result

Each tenant sees only its own extensions, call history, and recordings. Agents place calls directly from the browser, and the operator gained a self-service layer without exposing one client's data to another.

Sources and further reading

Services This Relates To

Written by KYCONNECTS Engineering. Client names are withheld under confidentiality.

Talk Through Your Requirements

We typically respond within 4–8 business hours.