Skip to content
Skip to content
NexaLink

Designing a Multi-Tenant ISP SaaS: Lessons from NexaLink

Zill E Ali
March 30, 2026 3 min read
Isometric multi-tenant SaaS architecture with glowing cyan nodes linked to isolated database cylinders

NexaLink is an ISP management platform: subscribers, plans, invoices, vouchers, and RouterOS automation for small operators who cannot justify a carrier-grade OSS. Multi-tenancy was the first architectural decision and the one everything else inherited.

The two options#

Schema-per-tenant. One database, one PostgreSQL schema per ISP. Strong isolation, easy per-tenant restore, and a satisfying mental model.

Row-level. One shared set of tables, a tenant_id on every row, enforced globally.

NexaLink ships row-level. The deciding factor was not performance — it was operations. With 200 tenants, schema-per-tenant means 200 schema migrations per release, 200 sets of connection pools, and a migration failure on tenant 147 leaving the fleet in mixed states. Row-level makes a release one migration and one rollback path.

Where schema-per-tenant wins: a handful of large tenants demanding physical separation, or per-tenant custom fields. That was not the target customer.

Making row-level safe#

The risk with row-level isolation is a forgotten where tenant_id = ?. Three layers stop it:

  1. Tenant context from middleware. Resolved from the authenticated user, never from user-supplied input.
  2. A global query scope on every tenant-owned model, applied by a base class so a new model cannot opt out by omission.
  3. Row-level security in PostgreSQL as the backstop, so even a raw query in a background job cannot cross tenants.

Then the test suite: every tenant-owned resource has a test that authenticates as tenant A and asserts a request for tenant B's record returns 404, not 403. A 403 confirms the record exists — that itself is a small leak.

FreeRADIUS in a shared platform#

The RADIUS schema was the interesting part. Subscribers from different ISPs share radcheck and radacct, and usernames collide — ali exists at three ISPs.

The fix is a realm-qualified username: ali@tenant-slug. RouterOS sends it verbatim, FreeRADIUS resolves the tenant from the realm, and accounting rows carry the tenant along for free. Retrofitting this later would have meant reissuing every subscriber credential on the platform, so it is worth deciding on day one.

radacct is the heaviest table by far. It is partitioned by month, with old partitions detached to cold storage after the billing dispute window closes.

Automating RouterOS safely#

Pushing configuration to customer routers from a SaaS is the feature operators want and the one most likely to cause a bad day.

Rules NexaLink follows:

  • Never push a full configuration. Only additive, scoped commands, each idempotent.
  • Queue every change, with a per-router serial worker. Two concurrent changes to one router is how you get a half-applied firewall.
  • Read back and verify after each change; a job is not complete until the router confirms the resulting state.
  • A dry-run mode that renders the exact commands for the operator to review. Trust is earned by showing your work.
  • A hard stop on rules affecting management access. No automation should be able to lock the operator out of their own router.

The migration I would undo#

Early on, invoices stored the plan's price as a foreign key to the plan record instead of copying the amount onto the invoice line. It worked until the first ISP raised prices — and every historical invoice silently re-priced itself. Reconciling six months of already-paid invoices against changed totals was a weekend I will not get back.

Financial records must be immutable snapshots. Copy the price, the tax rate, the plan name, and the currency onto the document at issue time. Reference the plan for reporting if you like, but never for the number the customer paid.

What I would keep#

  • Row-level tenancy with enforcement at three layers.
  • Realm-qualified RADIUS usernames from the first commit.
  • Queued, verified, idempotent router automation.
  • Immutable financial documents.
  • Boring, well-understood infrastructure — PostgreSQL, Laravel, Redis — so the interesting problems stay in the ISP domain, not the plumbing.

If you run a small ISP and this stack sounds like what you have been assembling by hand, that is exactly the gap NexaLink exists to close.

Zill E Ali

MTCNA-certified network engineer & founder of NexaLink. ISP infrastructure, MikroTik, and FTTH.

View all posts by Zill E Ali →

Related articles

Comments coming soon — powered by Giscus in Phase 2.