Installation
This platform is plain PHP + MySQL/MariaDB — no Composer, no build step, no framework. It runs on shared cPanel hosting (the environment it was built for) just as well as a VPS, a dedicated server, or a local dev environment (XAMPP/MAMP/native LAMP). This guide covers all of those.
Requirements
- PHP 8.1 or later, with these extensions (all standard on virtually every host — nothing exotic):
pdo_mysql,curl,mbstring,json, plussodium(bundled with PHP since 7.2 — used to encrypt the email/SMS API keys you save in Settings). PHP 8.3 is recommended. - MySQL 5.7+ or MariaDB 10.2.1+ — the schema uses
CHECKconstraints (for ElimuPay's dual-scoped tables) that need at least these versions - A way to set environment variables OR create a
.envfile (covered below — both work) - Enough disk space for uploaded photos, generated PDFs, and documents (
storage/) — no fixed minimum, scales with your usage - Outbound connections allowed from your server — needed for license verification (a small periodic check, described in step 5); standard on virtually all hosting
You do not need: Composer, Node/npm, SSH access (though it helps), or any particular web server — Apache, LiteSpeed, and Nginx are all covered below.
1. Get the files onto your server
Upload the entire project (unzip it if you downloaded it as a zip) to your server. On cPanel, this is
usually via File Manager or FTP into a directory outside public_html (see Step 6 for why).
2. Create the database
Using phpMyAdmin, cPanel's "MySQL Databases" tool, or the mysql CLI, create an empty database and a
database user with full privileges on it. Note the database name, username, password, and host
(usually 127.0.0.1 or localhost — cPanel sometimes uses a different host for MySQL specifically;
check cPanel's MySQL page if unsure).
Don't run db/01_schema.sql manually yet — the installer (Step 4) does this for you in one click.
If you'd rather do it by hand (e.g. via phpMyAdmin's SQL tab, or mysql yourdb < db/01_schema.sql),
that's fine too — the installer detects an already-applied schema and skips straight past that step.
3. Configure .env
Copy .env.example to .env (same directory as bootstrap.php, i.e. the project root) and fill in
real values:
cp .env.example .env
At minimum, set:
- DB_HOST, DB_NAME, DB_USER, DB_PASS — from Step 2
- INSTALL_ENABLED=true — temporarily, just for the next step
Everything else (M-Pesa, SMS, ElimuPay secrets, etc.) has safe placeholder defaults and can be
configured later, once you're ready to actually use those features — the platform runs fine without
them (M-Pesa/SMS features simply won't work until credentialed; see each MODULE_*_NOTES.md for what
depends on what).
If your host doesn't support .env files (some very locked-down shared hosts don't allow files
starting with a dot, or your control panel sets environment variables its own way instead): set the
same variable names as real environment variables through your hosting control panel instead.
EnvLoader only reads .env if the file exists — if it doesn't, getenv() calls throughout the app
fall through to whatever's set at the server/control-panel level, with no code changes needed either
way.
4. Run the installer
Visit https://your-domain.example.com/install.php in a browser (adjust the path if your document
root isn't set yet — see Step 6 first if you're unsure where that URL should point).
The installer:
1. Checks PHP version, required extensions, and that storage/ is writable.
2. Checks the database connects.
3. Applies the schema (db/01_schema.sql) if it hasn't been already — one click. Tick Also load
sample data if you want two fully populated demo schools to explore (every role signs in with
Demo@2026); leave it unticked for a live installation.
4. Lets you create your Super Admin account — name, email, password. This is the one and only
account that can onboard schools and manage the platform; nothing else in the system can create it,
which is exactly why this installer exists.
When the account is created the installer lists the cron jobs to add (step 7), with commands already pointing at your folder — copy them from there.
Immediately after your Super Admin account is created: set INSTALL_ENABLED=false in .env (or
delete public/install.php entirely — either works). The installer refuses to create a second Super
Admin once one exists, but there's no reason to leave the door open at all once you're done with it.
If you ever need to run it again on a fresh database (a new staging environment, for example), the
same three steps apply: set INSTALL_ENABLED=true, visit the URL, turn it back off when done.
5. Activate your licence
Enter the licence key from your purchase (it starts with MJB-) in the installer's last step, next to
your Super Admin details — it's activated straight away and the result is shown. No key yet? Skip it:
after you sign in, Solvo takes you to System & licence, where you paste it and press Activate.
Nothing to edit in .env.
A licence is valid for one domain. Moving to a new domain? Contact support to have the key released first; activating an already-bound key on a second domain fails by design.
You can see the licence status at any time under Super Admin → System & licence, along with the database version, the email/SMS queue, server checks and the cron list. If the licence server can't be reached, Solvo keeps working for a grace period (three failed checks, 12 hours apart) before it asks for re-activation, so a short outage on our side never takes your schools offline.
(1.x installations that used SYNC_KEY in .env keep working — the key is picked up automatically.)
6. Point your web server at public/
This is the single most important configuration step. The public/ directory is the only one
meant to be reachable by a browser — everything else (core/, config/, db/, storage/,
vendor/) contains application code, credentials, or uploaded files that must never be served
directly.
.htaccess files are already in place in every non-public directory as defense-in-depth (they deny
all access outright, so even a misconfigured document root won't expose them on Apache/LiteSpeed) —
but the correct setup is still to point your document root at public/ directly, not rely on that
safety net alone.
cPanel
If this is an addon domain or subdomain, cPanel's "Domains" page lets you set the Document Root
directly to .../mojamoza/public when you create it. If it's your primary domain and you can't easily
change its document root, either:
- Move the whole project up one level and point the domain at a folder that only contains what's in
public/ today (least convenient), or
- Ask your host to set the document root for you (many will), or
- Use a subdomain for the app instead of the primary domain's root.
Apache / LiteSpeed (VPS or dedicated server)
<VirtualHost *:443>
ServerName your-domain.example.com
DocumentRoot /var/www/mojamoza/public
<Directory /var/www/mojamoza/public>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
Nginx
.htaccess files do nothing under Nginx — use this instead:
server {
listen 443 ssl;
server_name your-domain.example.com;
root /var/www/mojamoza/public;
index index.php;
# Deny the non-public directories outright, mirroring what the
# .htaccess files do on Apache.
location ~ ^/(core|config|db|storage|vendor|scripts)/ {
deny all;
return 404;
}
location ~ /\.(env|git) {
deny all;
return 404;
}
location / {
try_files $uri $uri/ =404;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.1-fpm.sock; # adjust to your PHP-FPM socket/version
}
}
Local development (XAMPP/MAMP/php -S)
Quickest option — from the project root:
php -S localhost:8000 -t public
Then visit http://localhost:8000/install.php.
7. Set up cron jobs
Six background scripts run on a schedule. The most important one is process_outbox.php (every 5
minutes): it sends queued emails and SMS and retries failures. The others handle overdue invoices, fee
reminders, attendance alerts and so on — see docs/CRON_JOBS.md for the full list and exact cron syntax for
both cPanel and a standard crontab.
8. Configure optional integrations (whenever you're ready)
None of these block getting the platform running — configure them when you actually need the feature:
- M-Pesa Daraja (fee payments, ElimuPay instalments):
MPESA_*variables in.env. Ships with Safaricom's own published sandbox test credentials forMPESA_SHORTCODE/MPESA_PASSKEY— real values needed before going to production.MPESA_CALLBACK_BASE_URLmust be a real public HTTPS URL — Safaricom cannot call back tolocalhost. - Email and SMS: sign in as Super Admin → Email & SMS engines. Pick an email engine (Resend, Mailgun, Mailchimp Transactional, SendGrid, Brevo, Postmark, Amazon SES, ZeptoMail, Elastic Email, MailerSend, SMTP or PHP mail) and an SMS gateway (Africa's Talking, Twilio, Advanta, Celcom Africa, Infobip, Vonage, Termii or any HTTP gateway), paste the keys and press Send test email (or Send test SMS). Keys are stored encrypted. Each school can optionally use its own sender name or its own engines under School Admin → Sender settings. See EMAIL_AND_SMS.md.
- Live chat: on by default for the marketing site; schools switch theirs on under Live chat → Settings. See LIVE_CHAT.md.
- Branding: upload light and dark logos and set colours under Branding & payments and Appearance; emails pick the right logo automatically. See THEMES_AND_BRANDING.md.
Post-install security checklist
- [ ]
INSTALL_ENABLED=falsein.env(orpublic/install.phpdeleted) - [ ]
.envhas real, non-CHANGE_MEvalues for everything you're actually using - [ ] Document root points at
public/, not the project root - [ ]
storage/is writable by the web server user but not itself web-reachable (verify: visitinghttps://your-domain/storage/in a browser should 403 or 404, never list files) - [ ] HTTPS is enabled (required for M-Pesa callbacks regardless; strongly recommended for everything
else — session cookies are marked
secureby default inconfig.php, which means login simply won't work at all over plain HTTP unless you deliberately change that) - [ ] Licence activated (step 5)
- [ ] An email engine is connected and Send test email succeeds
- [ ] Cron jobs are set up (step 7)
- [ ] You've signed in as your Super Admin and onboarded your first school
Troubleshooting
"This site can't be reached" / blank page — check your PHP error log (cPanel: "Errors" under
Metrics, or error_log in the same directory as the failing script). Almost always a PHP version
mismatch or a missing extension — re-run install.php's requirements check to confirm.
Installer says "Could not connect to the database" — double-check DB_HOST; on some hosts this
is a specific hostname (like localhost:3306 or a dedicated internal hostname) rather than
127.0.0.1. cPanel's MySQL Databases page usually states the correct host.
Login page loads but signing in does nothing / redirects back to login — check that
session.secure_cookie in config.php matches reality: if you're testing over plain HTTP (e.g. local
dev without HTTPS), the secure-cookie flag will prevent the session cookie from ever being set. For
local development without HTTPS, temporarily set it to false in config.php — never do this in
production.
"Installation already complete" but you don't have Super Admin credentials — either use
/forgot-password.php, or if that's not viable (e.g. the email on file is no longer accessible),
connect directly to the database and update the relevant row's email in the users table so a
reset can be sent to an address you control, then use the reset flow. There is deliberately no UI path
around the "one super admin creation" safeguard.
403 Forbidden on every URL, including ones that don't exist — your document root is pointing at
the project root rather than public/. The project root's own .htaccess denies all access as a
safety net for core/, config/, etc., and Apache cascades that deny into every subdirectory —
including public/ — unless something explicitly overrides it there. public/.htaccess does exactly
that (a Require all granted block), so this should work out of the box; if you're still seeing it,
confirm you have the current version of that file, and that your host has AllowOverride All enabled
(some very locked-down shared hosts disable .htaccess processing entirely). Either way, the properly
supported fix is still to point your document root directly at public/ — see step 6.
The platform is briefly unreachable, or won't activate — sign in as Super Admin and visit Super Admin → System & licence to see the actual activation status. Confirm the key was pasted exactly as given, and that you're checking the same domain the license was activated on — a license only works on the one domain it was first activated for. If the status page shows "Active" but something still feels wrong, the issue is unrelated to licensing; if it doesn't, contact support with your license key.
Updating an existing installation
Upload the new files over the old ones (keep your .env, config/edition.php and storage/), then
sign in as Super Admin and open System & licence → Database. If an update is waiting, press
Update the database now — Solvo applies only the changes it hasn't applied yet and records each one,
so it is safe to press more than once. With SSH you can run php scripts/migrate.php instead.
Coming from 1.x? Follow UPGRADING_FROM_1X.md.