Run FreeRADIUS against SimpliFi for PPPoE and hotspot data limits on any vendor.

SimpliFi — RADIUS (FreeRADIUS) guide

Module M2. RADIUS lets any hotspot or PPPoE server that speaks RADIUS (Ruijie, Grandstream, Cambium, OpenWrt/CoovaChilli, pfSense, Huawei, Cisco, MikroTik in RADIUS mode, …) check every customer login against SimpliFi. SimpliFi writes the FreeRADIUS tables; a FreeRADIUS server reads them straight from the SimpliFi MySQL database and writes accounting back.

 Customer ──WiFi──► Router / AP (NAS) ──UDP 1812/1813──► FreeRADIUS (VPS / on-site box)
                         │                                      │  MySQL (TCP 3306, TLS)
                         └──redirect──► SimpliFi portal          ▼
                                       (cPanel, HTTPS)  ◄──── SimpliFi database (cPanel MySQL)

1. Why cPanel cannot host RADIUS itself

  • RADIUS is UDP (1812 auth, 1813 accounting). Shared hosting only proxies HTTP(S) to PHP; you cannot open UDP ports, run a daemon (radiusd) or install packages (freeradius) without root.
  • PHP requests end after each page view — a RADIUS server must listen permanently.

So FreeRADIUS runs on any small Linux box: a $4–6/month VPS (1 vCPU / 512 MB–1 GB RAM is plenty for thousands of users), or an on-site mini PC / Raspberry Pi at the ISP. It talks to the cPanel database over the internet through cPanel's Remote MySQL.

2. What SimpliFi writes (tables)

All tables are created by the SimpliFi migrations (2026_10_19_000023_create_freeradius_tables plus 2026_10_20_020000_add_freeradius_v3_columns_to_radacct_table). RADIUS usernames and NAS addresses are globally unique (one FreeRADIUS serves every workspace); tenant_id is bookkeeping only.

Table Written by Content
radcheck SimpliFi (RadiusRepository::upsertUser) Cleartext-Password :=, Expiration := (M d Y H:i:s, RADIUS server timezone), Simultaneous-Use := (package shared users), Max-All-Session := (optional total seconds)
radreply SimpliFi Session-Timeout, Mikrotik-Rate-Limit (up/down, e.g. 512k/2048k, plus burst fields for a burst package — see §2.1), WISPr-Bandwidth-Max-Up/Down (bit/s, always the plan rate), Mikrotik-Total-Limit(+-Gigawords) and ChilliSpot-Max-Total-Octets(+-Gigawords) for data caps, Framed-IP-Address, Acct-Interim-Interval (300 s)
radusergroup SimpliFi (optional group) user → group
nas SimpliFi (upsertNas when a device is saved in RADIUS mode) nasname = device NAS IP, shortname, secret, type = other, server
radacct FreeRADIUS accounting — SimpliFi reads usage, "online" and live clients from it
radpostauth FreeRADIUS (optional) login log

Which reply attributes a device honours depends on the vendor (see docs/VENDORS.md): MikroTik uses Mikrotik-*; CoovaChilli ChilliSpot-* + WISPr; Grandstream and Cambium honour Session-Timeout and WISPr bandwidth; data caps are only enforced by MikroTik and CoovaChilli.

Revoking a session deletes the user's rows, so the next login is refused. A client that is still connected keeps going until its Session-Timeout — sending a RADIUS Disconnect-Request (CoA, UDP 3799) to the NAS is not possible from cPanel.

2.1 Burst and the fair-use policy (reduced speed)

Burst. A package with burst switched on renders the full RouterOS form into Mikrotik-Rate-Limit, for example 512k/2048k 1024k/4096k 768k/3072k 8/8 (rate, burst rate, burst threshold, burst time in seconds; rx = customer upload, tx = download). WISPr-Bandwidth-Max-Up/Down always keep the plan rate, because WISPr has no burst fields. Only MikroTik-compatible gear reads the burst fields; everything else uses WISPr and gets the plan rate (docs/VENDORS.md, note 5).

Fair use. When a session on a "continue at reduced speed" package reaches its allowance, App\Services\Hotspot\FupEnforcer calls the driver, which asks RadiusRepository::applyRateLimit() to rewrite the user's reply rows:

  • Mikrotik-Rate-Limit and WISPr-Bandwidth-Max-Up/Down become the reduced rate (no burst — the allowance that paid for it is spent);
  • Mikrotik-Total-Limit(+-Gigawords) and ChilliSpot-Max-Total-Octets(+-Gigawords) are deleted — the customer is meant to stay online, so the NAS must not cut them off at the cap it was given earlier;
  • Cleartext-Password, Expiration, Simultaneous-Use, Session-Timeout, Framed-IP-Address and Acct-Interim-Interval are untouched: the session keeps its time.

Honest limitation. A NAS reads radreply when it authenticates a user. Shared cPanel hosting cannot send a RADIUS Disconnect-Request / CoA (UDP 3799) to force a re-authentication, so the reduced rate takes effect at the customer's next re-authentication — immediately when their device reconnects, or when Session-Timeout elapses. Until then they continue at the full rate, which means a customer can overshoot the allowance for the rest of their current session. There is no way around this from cPanel; the ways around it are to run the FreeRADIUS box itself with a CoA sender, or to use MikroTik in RADIUS or Cloud Sync mode, where SimpliFi also kicks the live session over cloud sync and the re-authentication happens at once (≤ 20 s).

Keep Acct-Interim-Interval short enough (the default 300 s) that hotspot:usage notices the allowance being crossed promptly: SimpliFi can only act on the counters accounting gives it.

2.2 PPPoE subscribers: usage from accounting

A PPPoE subscriber on a RADIUS-mode device is counted from radacct as well — the NAS writes acctinputoctets (the customer's upload), acctoutputoctets (download) and acctsessiontime for the PPPoE username exactly as it does for a hotspot login, and pppoe:usage (every 5 minutes) sums them.

  • Accounting decides how fresh the numbers are. Between interim updates SimpliFi sees nothing new, so Acct-Interim-Interval (SimpliFi sends 300 s; simplifi.radius.interim_interval) is the real resolution of a PPPoE allowance. A NAS configured with a long interval — or with accounting switched off — can let a subscriber overshoot by whatever they download in between: with accounting off there is no PPPoE usage at all on a RADIUS-mode device, and fair use cannot fire. Enable accounting (/radius set accounting=yes on MikroTik) and keep the interval at a few minutes.
  • Counting per period. usage() sums every accounting session ever stored for the username, so SimpliFi records the value it reads at the start of each allowance period as a baseline and only counts what grows on top of it (pppoe_accounts.meta.counters). Trimming radacct therefore never hands a customer free data: a sum that drops is treated as a counter restart, and the usage already counted stays on the account.
  • The reduced rate applies at the next re-authentication. applyFupToPppoe() rewrites the same reply rows as above for the PPPoE username, and the live PPP session is deliberately not disconnected: a PPPoE plan is a monthly subscription, so forcing a re-dial just to apply a throttle is exactly what fair use is meant to avoid (and cPanel cannot send a CoA either). The customer continues at the full rate until their session re-dials. An ISP who wants it immediate should run the router in Cloud Sync or Direct API mode, where SimpliFi switches the PPP profile itself.
  • A "stop" package never disconnects a PPPoE subscriber mid-month; only "continue at reduced speed" plans are enforced here (docs/MIKROTIK.md §"PPPoE usage").

3. Install FreeRADIUS on Ubuntu (22.04 / 24.04) or Debian 12

The quick way, from a copy of this repository on the server:

sudo bash deploy/freeradius/install-ubuntu.sh
sudo nano /etc/freeradius/3.0/simplifi.env      # database credentials (see §4)
sudo systemctl restart freeradius
sudo freeradius -XC                              # "Configuration appears to be OK"

What the script does (do it by hand if you prefer):

  1. apt-get install freeradius freeradius-mysql freeradius-utils default-mysql-client
  2. Copies deploy/freeradius/mods-available/sql and …/sqlcounter to /etc/freeradius/3.0/mods-available/ and links them into mods-enabled/. sql reads the database credentials from the environment ($ENV{SIMPLIFI_DB_*}), sets read_clients = yes / client_table = "nas", and includes the stock MySQL queries.conf.
  3. Copies deploy/freeradius/sites-available/simplifi (auth 1812 + acct 1813; PAP, CHAP, MS-CHAP; -sql, expiration, noresetcounter; accounting + session via SQL) and replaces the stock default / inner-tunnel sites.
  4. Installs /etc/freeradius/3.0/simplifi.env (from simplifi.env.example, mode 640, group freerad) and the systemd drop-in freeradius.service.d/simplifi.conf (EnvironmentFile=).
  5. Installs simplifi-nas-reload.sh + /etc/cron.d/simplifi-radius: FreeRADIUS loads clients from the nas table only at start-up, so the script checks the table every minute and restarts FreeRADIUS when a device was added or changed. Alternative without restarts: sites-available/simplifi-dynamic-clients (per-packet SQL client lookup; instructions inside).
  6. Opens UDP 1812/1813 in ufw if present. Also open them in your VPS provider's firewall.

On-site box (no public IP): run the same install on a mini PC in the ISP's network; routers then use its LAN IP as RADIUS server and the box reaches cPanel MySQL outbound.

4. cPanel: allow the RADIUS server into MySQL

  1. cPanel › MySQL Databases — create a dedicated user (e.g. cpuser_radius) with a long random password and add it to the SimpliFi database. Grant SELECT, INSERT, UPDATE, DELETE (FreeRADIUS needs INSERT/UPDATE on radacct, INSERT on radpostauth, SELECT on the rest; cPanel grants privileges per database, not per table).
  2. cPanel › Remote MySQL — add the VPS's public IP (only that IP; never %).
  3. The MySQL host in simplifi.env is the cPanel server's hostname or shared IP, not localhost. Port 3306 must be reachable from the VPS (some hosts open it only on request).
  4. Prefer TLS: uncomment the tls { … } block in mods-available/sql when the host supports it.
  5. Time zones: FreeRADIUS writes accounting times with MySQL's NOW()/FROM_UNIXTIME(); keep the MySQL server, the FreeRADIUS box and RADIUS_TIMEZONE (SimpliFi .env, default UTC — Ghana is UTC+0) aligned so Expiration and "online" detection are correct.

Security note: this MySQL user can technically read the whole SimpliFi database. Keep the password secret, restrict Remote MySQL to the RADIUS server's IP, and keep the VPS patched.

5. SimpliFi settings

In the SimpliFi .env on cPanel:

RADIUS_SERVER_HOST=radius.example.com   # shown to ISPs on every RADIUS device page
RADIUS_AUTH_PORT=1812
RADIUS_ACCT_PORT=1813
RADIUS_INTERIM_INTERVAL=300              # Acct-Interim-Interval sent to devices
RADIUS_TIMEZONE=UTC                      # timezone of the FreeRADIUS server (Expiration)
RADIUS_STALE_MINUTES=30                  # open sessions without updates are "offline" after this

A platform setting radius.server_host (if the platform admin sets one) overrides RADIUS_SERVER_HOST. Then php artisan config:cache.

6. Add a RADIUS device in SimpliFi

Routers & Controllers › Add Device → pick the vendor → connection type RADIUS → enter the NAS IP (the public IP the router's RADIUS packets come from; for an on-site FreeRADIUS, the router's LAN IP) and optionally a NAS identifier. Leave the secret blank to generate one. Saving writes the nas row; the device page shows the server, ports and (on click) the shared secret, plus vendor-specific steps. Two devices cannot share a NAS IP — FreeRADIUS identifies clients by source IP (behind one NAT, use one device entry for the site).

7. Testing

# On the RADIUS box (localhost client "testing123" from the stock clients.conf):
radtest <username> <password> 127.0.0.1 0 testing123
#   → "Received Access-Accept … Session-Timeout = …, Mikrotik-Rate-Limit = …"

# Debug mode (stop the service first) shows every SQL query and reply:
sudo systemctl stop freeradius && sudo freeradius -X

# From another machine, as a NAS registered in SimpliFi:
echo "User-Name=<username>,User-Password=<password>" | radclient -x <radius-ip>:1812 auth <nas-secret>

# Accounting round trip:
echo "User-Name=<username>,Acct-Status-Type=Start,Acct-Session-Id=test1,NAS-IP-Address=<nas-ip>" \
  | radclient -x <radius-ip>:1813 acct <nas-secret>

Get a username/password by selling a voucher or creating a manual session in SimpliFi. "Test connection" on a RADIUS device page re-registers the NAS and shows when the last accounting packet from that NAS arrived. Access-Reject usually means: wrong secret (check the NAS IP matches the packet source), the user expired (Expiration), or Simultaneous-Use reached (stale radacct rows — the NAS should send Accounting-On after reboot).

8. Operations

  • Back up radacct with the rest of the database; prune old rows (e.g. > 12 months) periodically. Pruning is safe for PPPoE allowances: SimpliFi keeps what it already counted (§2.2).
  • journalctl -u freeradius for errors; freeradius -XC after any config change.
  • Upgrading FreeRADIUS keeps /etc/freeradius/3.0/* (answer "keep your version" for our files).

Ready to try it on your network?

Start your 14-day free trial today. Set up in minutes, no credit card, cancel any time.

Install SimpliFi App

One tap, no app store. Faster, full screen, works on weak signal.