Deploying SimpliFi on cPanel
This guide installs SimpliFi on ordinary cPanel shared hosting without SSH: upload one zip, run the web installer, add one cron job. It also covers wildcard subdomains and SSL, payment webhooks, SMS, the first router, upgrades and backups.
Everything here works on a VPS with cPanel too. Without cPanel, the same ideas apply: PHP 8.2+, MySQL/MariaDB, a web server whose document root is
public/, one cron entry.
1. What you need
| Item | Notes |
|---|---|
| cPanel hosting with PHP 8.2 or newer | "Select PHP Version" (CloudLinux) or "MultiPHP Manager" |
| PHP extensions | pdo_mysql, mbstring, openssl, curl, gd, zip, fileinfo, bcmath, json, tokenizer, ctype, xml (+ intl recommended) |
| MySQL 5.7+/8 or MariaDB 10.3+ | one database + one user |
| A domain | e.g. simplifi.henapp.org (the base domain) |
Wildcard DNS *.base-domain |
only for subdomain mode (each ISP gets name.base-domain) |
| SSL | AutoSSL / Let's Encrypt (see §6) |
| Cron | one entry, every minute |
You build the zip on your own computer (Windows): Node 18+, Composer 2 and PHP 8.2 must be installed there.
2. Build the upload package (on your computer)
From the project folder, in PowerShell:
.\package-for-cpanel.ps1
The script:
- runs
npm run build(compiled CSS/JS go topublic/build, so the server needs no Node); - copies the app to a temporary staging folder without
node_modules,.git,tests,storagelogs / caches / sessions, local.env,database/database.sqliteand old zips; - runs
composer install --no-dev --optimize-autoloaderin the staging folder —vendor/is shipped because most shared plans have no SSH / Composer; - writes an installer-ready
.envfrom.env.example(production, fresh temporaryAPP_KEY, file sessions/cache,SIMPLIFI_INSTALLER=true, path mode until you choose); - zips everything as
dist\simplifi-cpanel-YYYYMMDD-HHmm.zip(forward-slash paths, safe for Linuxunzipand cPanel File Manager).
Options: -SkipBuild (reuse the current public/build), -SkipComposer (reuse vendor/ — only
if it was installed with --no-dev), -OutputDir C:\builds, -KeepStaging.
3. Create the database
cPanel › MySQL® Databases:
- Create New Database: e.g.
simplifi→ becomescpuser_simplifi. - Add New User: e.g.
simplifiwith a long generated password →cpuser_simplifi. - Add User To Database → tick ALL PRIVILEGES.
Keep the three values (database, user, password) for the installer. The host is localhost.
4. Upload and point the domain
- cPanel › File Manager → your home folder (not
public_html) → createsimplifi/. - Upload the zip into
simplifi/and Extract it. You should seeapp/,public/,vendor/,.env,artisan… insidesimplifi/. - Point the domain's document root to
simplifi/public:- Addon / sub domain: cPanel › Domains › Manage › New Document Root =
simplifi/public. - Primary domain whose root you cannot change (
public_html): either extract SimpliFi intopublic_htmland copycpanel.htaccesstopublic_html/.htaccess(it forwards everything topublic/and blocks.env,vendor/,storage/…), or replacepublic_htmlby a symlink tosimplifi/publicif your host allows it.
- Addon / sub domain: cPanel › Domains › Manage › New Document Root =
- File permissions: folders
755, files644;storage/andbootstrap/cache/must be writable by PHP (they are on standard cPanel/suPHP/LSAPI setups).
PHP version and extensions
cPanel › Select PHP Version (or MultiPHP Manager): choose 8.2+ for the domain, then the
Extensions tab: tick everything listed in §1. Recommended options: memory_limit 256M,
max_execution_time 120, upload_max_filesize 16M, post_max_size 20M.
5. Run the web installer
Open https://your-domain/ — every page redirects to /install until the platform is
installed.
- Requirements — PHP version, extensions and writable folders (fix anything red, then Check again).
- Database — host
localhost, port3306, database, user, password → Test connection. The password is never shown again on screen. - Application — platform name, main URL (with
https://), base domain, and how ISPs get their address:- Subdomains (recommended):
henapp.simplifi.henapp.org— needs §6. - Paths:
simplifi.henapp.org/henapp— no DNS work; good for a first test. Support e-mail / phone / WhatsApp appear on the website and in messages. Optional SMTP: create a mailbox (cPanel › Email Accounts, e.g.no-reply@your-domain) and use its Connect Devices settings (SSL, port 465).
- Subdomains (recommended):
- Super admin — your platform owner account (10+ characters, upper/lower case + numbers).
- Install — creates the tables (
migrate --force), saves platform settings, creates the super admin, triesstorage:link, writes a new.envatomically with a fresh APP_KEY, production settings and database sessions / cache / queue, setsSIMPLIFI_INSTALLER=false, writesstorage/app/installed.lockand clears caches. - Finish shows the cron command and next steps.
After installation /install answers 404. To run it again (fresh server, broken .env):
set SIMPLIFI_INSTALLER=true in .env and delete storage/app/installed.lock. Re-running
keeps existing data (migrations only add what is missing) and updates the super admin with the same
e-mail.
No symlinks allowed? Fine: uploaded logos and files are streamed through
/media/...automatically whenpublic/storagedoes not exist.
6. Wildcard subdomains and SSL (subdomain mode)
Each ISP workspace lives on name.base-domain, e.g. henapp.simplifi.henapp.org.
- DNS: add
*.simplifi.henapp.org→ same IP assimplifi.henapp.org(A record), or a CNAME to it. With cPanel DNS: Zone Editor › + A Record name*. - cPanel › Domains › Create A New Domain:
*.simplifi.henapp.orgwith document root the samesimplifi/publicfolder (untick "share document root" only if your cPanel asks for a separate one — it must be the same folder). - SSL:
- AutoSSL issues a certificate for each name it can validate. Wildcard certificates need
DNS validation: cPanel's Let's Encrypt plugin can issue
*.domainonly when the DNS zone is hosted on the same cPanel/WHM server (or its DNS cluster) — see https://docs.cpanel.net/knowledge-base/third-party/the-lets-encrypt-plugin/. - DNS hosted elsewhere (e.g. Cloudflare): either move the zone to cPanel, use Cloudflare's proxy
with an Origin Certificate for
*.domain, or ask your host for a wildcard certificate. Behind Cloudflare (or any reverse proxy) tick "This site is behind Cloudflare or another reverse proxy" in the installer's Application step, or setTRUSTED_PROXIES=*in.env: otherwise every visitor shares the proxy's IP for rate limits and activity logs. Leave it unset when visitors reach the server directly. - Until the wildcard SSL works you can run in path mode (
TENANCY_MODE=pathin.env).
- AutoSSL issues a certificate for each name it can validate. Wildcard certificates need
DNS validation: cPanel's Let's Encrypt plugin can issue
- Custom domains for ISPs (Settings › Domain in each workspace): the ISP adds a CNAME to the
base domain (or an A record to the server IP) and the TXT record
simplifi-verify=…on_simplifi.their-domain. You add their domain as an Alias (cPanel › Domains, same document root) so Apache serves it, and AutoSSL covers it. The workspace shows "Verified" once DNS points to you; links then switch to the custom domain.
SESSION_DOMAIN stays empty: each host keeps its own login session.
7. Cron job and queue (required)
cPanel › Cron Jobs › Once Per Minute (* * * * *):
/usr/local/bin/php /home/CPANELUSER/simplifi/artisan schedule:run >> /dev/null 2>&1
(The installer's Finish page prints the exact line for your server; use ea-php82 paths such as
/opt/cpanel/ea-php82/root/usr/bin/php if your host documents them. Always the php CLI — never
lsphp, php-cgi or php-fpm: started from cron those run in CGI mode and, with cPanel's
register_argc_argv=Off, silently run artisan's default list command every minute instead of
schedule:run.) The installer's requirements page also checks that proc_open is not in
disable_functions: every scheduled task is started through it, so without it cron "runs" and nothing
happens.
The scheduler runs everything — no daemons are needed:
- the queue (
queue:work --stop-when-empty --max-time=50, every minute): welcome SMS, campaigns, notifications; - router monitoring and MikroTik cloud-sync housekeeping;
- hotspot expiry and reminders, PPPoE invoices and suspensions;
- payment reconciliation (pending MoMo prompts);
- platform billing (daily 06:00).
If cron stops, payments still complete when customers return to the pay page, but reminders, expiries and queued SMS wait until it runs again.
8. Payments, SMS and webhooks
Sign in as super admin (https://base-domain/login).
- Platform gateways (Super admin › Payment gateways): Henapp's own Paystack/Hubtel account, used for ISP invoices, SMS credits and platform-split collections.
- ISP gateways (each workspace › Settings › Payments).
- Webhook URLs are shown next to every gateway, in the form
https://base-domain/webhooks/payments/{driver}/{gateway id}:- Paystack: Dashboard › Settings › API Keys & Webhooks › Webhook URL.
- Hubtel: callbacks are unsigned hints that trigger a status check — ask Hubtel to enable Direct Receive Money and to whitelist your server's public IP for the status API.
- MTN MoMo: payments are confirmed by polling; tick "Send our callback URL" only when the API user's callback host is this server.
- Return URL after hosted checkout:
/webhooks/payments/return/{payment}(automatic). See Payments for keys, test numbers and troubleshooting.
- SMS (Super admin › SMS): paste the smsfactory.henapp.org (Zenoph) API key and sender ID.
.envmust haveSMS_DRIVER=zenoph(the installer sets it). ISPs can buy credits or use their own key under Settings › SMS. - WhatsApp (per workspace): webhook
https://base-domain/webhooks/whatsapp/{workspace id}.
9. First MikroTik router
- Create a workspace (
/signup, or Super admin › Tenants › New). - In the workspace: Devices › Routers › Add router › MikroTik › Cloud Sync.
- Paste the generated one-line script into the router terminal (Winbox › New Terminal).
- The router appears online within a minute. No public IP, port forwarding or VPN needed.
Details: MikroTik setup. Other brands: Vendors, RADIUS (FreeRADIUS on a small VPS reading this database through cPanel Remote MySQL).
10. Upgrading
- Build a new zip (
package-for-cpanel.ps1). - Back up first (§11).
- Upload and extract it over the existing folder, but do not overwrite:
.env(your configuration and APP_KEY — losing the key makes encrypted gateway/SMS keys unreadable),storage/(uploads, logs,installed.lock). The simplest way: delete.envand thestorage/folder from the zip before uploading (7-Zip › open the zip › delete), or extract tosimplifi-new/and move folders across.
- Run the database changes. Without SSH, use a temporary cron line (runs once, then delete it):
With SSH:/usr/local/bin/php /home/CPANELUSER/simplifi/artisan migrate --force && /usr/local/bin/php /home/CPANELUSER/simplifi/artisan optimize:clearphp artisan migrate --force && php artisan optimize:clear. - Patch zips (only changed files) work the same way; always include
public/buildwhen CSS/JS changed.
11. Backups
- Database: cPanel › Backup › Download a MySQL Database Backup (daily for busy platforms),
or a cron
mysqldumpto a folder outsidepublic. - Files:
storage/app/public(logos, uploads) and.env— keep a copy of.envsomewhere safe: theAPP_KEYinside it decrypts saved gateway and SMS keys. - Test a restore on a staging subdomain now and then.
12. Security checklist
APP_DEBUG=false,APP_ENV=production(set by the installer).- Document root is
public/(orcpanel.htaccessis in place):https://your-domain/.envmust return 403/404. - HTTPS everywhere;
public/.htaccesssends security headers (no-sniff, frame-origin, referrer policy, HSTS on HTTPS). - Strong, unique super-admin password; remove unused staff accounts.
- Keep PHP updated through cPanel; keep at least weekly backups.
13. Installable apps (PWA) and Google Play
SimpliFi is a Progressive Web App: the admin (/admin), the captive portal (/portal) and the
platform site each have a web manifest, icons and a service worker (public/sw.js). On Android
Chrome an Install banner appears automatically; on iPhone users see "Share › Add to Home
Screen" instructions. Workspace icons are generated from the ISP's logo (Settings › General).
To publish an Android app on Google Play, wrap the PWA as a Trusted Web Activity with PWABuilder (docs: https://docs.pwabuilder.com/#/builder/android):
- The site must be live on HTTPS. Open https://www.pwabuilder.com/, enter the URL to package —
e.g.
https://henapp.simplifi.henapp.org/adminfor one ISP's admin app (or their custom domain), or the base domain for a platform-wide app. - Package for stores › Android › Google Play. Check the package ID (e.g.
org.henapp.simplifi.henapp), app name, colours and icons (taken from the manifest); create a new signing key. - Download the zip: it contains the
.aab(upload to Play Console),signing.keystore+signing-key-info.txt(keep them safe — needed for every update) andassetlinks.json. - Upload
assetlinks.jsonto/.well-known/assetlinks.jsonon the same host as the start URL: put it inpublic/.well-known/assetlinks.json(all subdomains share the folder; for several apps on different hosts, merge their entries into one JSON array). - After the first Play upload, Google re-signs the app: copy the SHA-256 fingerprint from
Play Console › Setup › App integrity › App signing into
sha256_cert_fingerprintsinassetlinks.jsonand upload it again — the browser address bar then disappears. - Updates: generate a new package with a higher version code and the same signing key. Web changes appear in the app immediately; no store update is needed for them.
Background: https://web.dev/articles/using-a-pwa-in-your-android-app.
14. Troubleshooting
| Symptom | Fix |
|---|---|
Every page goes to /install after installing |
storage/app/installed.lock missing or SIMPLIFI_INSTALLER=true with no lock: re-run the installer or set it to false. |
| 500 error, blank page | Check storage/logs/laravel.log; make storage/ and bootstrap/cache/ writable; PHP version 8.2+. |
| CSS missing / unstyled pages | public/build was not uploaded — rebuild the package without -SkipBuild. |
"Workspace not found" on name.domain |
Wildcard DNS / subdomain missing, or SIMPLIFI_BASE_DOMAIN wrong in .env. |
| Logos do not show | public/storage link missing is fine (served via /media); check storage/app/public permissions. |
| SMS not delivered | Super admin › SMS › Test; SMS_DRIVER=zenoph; sender ID approved. |
| Payments stay pending | Cron not running (reconciliation), or webhook URL not set at the gateway. |
| Router offline | See MikroTik setup troubleshooting; check the router can reach https://base-domain. |