Skip to content

Custom domains & white-labeling

Put a school on its own web address, with its own login design, its own logo and its own name. Nothing on screen says the software came from you.

This is the feature schools pay more for. It is worth doing properly.

What the school gets

Instead of sending parents to your platform address, the school sends them to portal.theirschool.edu — their domain, their logo, their colours, their login screen. As far as parents and staff are concerned, the school built it.

For you it is the same single installation. Nothing is duplicated.

Two ways to do it

Decide this once, before your first school asks.

A — A subdomain of your platformB — The school's own domain
Looks likestmarys.yourplatform.comportal.stmarys.edu
Work per schoolNone — type the name and saveDNS, web server and SSL each time
Who does the workYou, once, for all schoolsYou and the school, per school
Ready inSecondsHours to two days, mostly waiting for DNS
Sells asIncludedPremium add-on

Set up A once, offer B as the upgrade

The wildcard setup below takes twenty minutes and then every school you ever add gets its own address instantly, with no DNS and no certificate work. Charge for B, where the school insists on its own domain.

Route A — one wildcard, every school covered

Do this once on your platform domain.

1. One DNS record

TypeHost / NameValue
A*your server's public IP

Find the IP by running curl ifconfig.me on the server.

2. Tell Nginx to accept any subdomain

nginx
server_name yourplatform.com *.yourplatform.com;

3. One wildcard certificate

bash
sudo certbot certonly --manual --preferred-challenges dns \
  -d "yourplatform.com" -d "*.yourplatform.com"

Certbot asks you to add an _acme-challenge TXT record. If your DNS is on Cloudflare, use the certbot-dns-cloudflare plugin so renewals happen automatically — a manual wildcard certificate must otherwise be renewed by hand every 90 days.

Done. From now on, set any school's custom domain to theirname.yourplatform.com and it works immediately. No DNS, no certificate, no server change.

Route B — the school's own domain

Four steps, in order. The panel walks you through the same four with your real values filled in: open a school and choose Open the Domain Setup Guide.

Step 1: DNS

The school adds one record at their domain registrar — GoDaddy, Namecheap, Cloudflare, wherever the domain lives.

OptionTypeHost / NameValue
A — root or subdomain (recommended)A@ or the subdomainYour server's public IP
B — subdomain onlyCNAMEportalYour platform hostname

DNS usually updates in 5–30 minutes, but is allowed to take up to 48 hours. Check with:

bash
nslookup portal.theirschool.edu

When that returns your server's IP, continue. Not before — the next steps fail confusingly if DNS has not caught up.

Step 2: Web server

On shared hosting (hPanel / cPanel) there is no config to edit. Attach the domain to the account instead:

  1. Open Domains → Aliases (older cPanel calls these "Parked Domains"), or Domains → Create a New Domain
  2. Add the domain and point its Document Root at the same folder as this app
  3. The DNS record from step 1 must already be live, or the panel refuses to attach it
  4. Run SSL/TLS Status → AutoSSL to issue the certificate

Some hosts require domain-ownership verification first — the school completes that at their registrar.

On Nginx, the quickest route is to add the domain to the existing server block:

nginx
server_name yourplatform.com portal.theirschool.edu;

If your Nginx block already uses server_name _; (catch-all), the domain works as soon as DNS points at the server — skip straight to SSL.

For a dedicated vhost, block order matters:

nginx
server {
    listen 80;
    server_name portal.theirschool.edu;

    root /var/www/school-erp/public;   # same root as the main app
    index index.php;

    # 1. CRITICAL: private media (student documents, certificates, ID cards)
    #    is served through Laravel with signed URLs — never as a static file.
    location ^~ /media/private/ {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # 2. Main Laravel routing
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # 3. Static caching — must come AFTER the exclusion above
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
        expires 30d;
        access_log off;
    }
}

On Apache, the simplest option is one line added to the existing VirtualHost:

apacheconf
ServerAlias portal.theirschool.edu

Then reload:

bash
sudo nginx -t && sudo systemctl reload nginx    # Nginx
sudo systemctl reload apache2                   # Apache

Panel-managed servers: read this or certificates and documents will 404

aaPanel, CyberPanel and Forge generate a vhost that already contains an image and PDF caching rule. If the location ^~ /media/private/ block is missing — or sits below that caching rule — student documents, generated ID cards and certificates return 404, while logos and ordinary images work perfectly.

It must be block number one, above everything else.

Step 3: SSL

Once DNS resolves to your server:

bash
sudo certbot --nginx -d portal.theirschool.edu     # Nginx
sudo certbot --apache -d portal.theirschool.edu    # Apache

Certbot configures the certificate and the HTTP→HTTPS redirect. Confirm renewal works:

bash
sudo certbot renew --dry-run

Step 4: Check, then set it Active

Open https://portal.theirschool.edu. You should see that school's branded login.

Then open a page containing a student photo or document — this proves private media is being served correctly. If those 404 while the rest of the page loads, go back to step 2.

Only then set Domain Status to Active (Live) and save.

The white-label part

Setting the domain is half of it. The other half is the login screen.

Each school picks a Login Design Style:

StyleLook
System Default (Classic)Warm orange marketing page with quick role tiles
Campus Pro (Institutional)Clean university-grade split screen, adopts the school's brand colour, mobile-first
Modern Split (Premium)Bold brand panel left, clean white form right
Centered Glassmorphism (Modern)Dark slate backdrop, frosted-glass centred card
Aurora Night (Immersive)Animated aurora gradients over a deep night sky
Minimal Luxe (Elegant)Ivory whitespace, serif headline, gold accent
Custom Design (Your Own)Build the layout and colours yourself

Changes apply immediately — the school does not need to log out. Each style has a Live Preview before you commit.

On the school's own domain the login uses their logo and name. On your platform address it keeps your branding.

Changing a school's slug breaks links already in circulation

The public admission link contains the slug — /apply/school-name. Parents may already have that link, and schools put it on posters and WhatsApp.

Change the slug and every one of those links dies, silently. Decide the slug when the school is created.

If it is not working

What you seeCauseFix
Domain does not load at allDNS has not propagated, or points elsewherenslookup the domain — it must return your server's IP
Your platform's login appears, not the school'sDomain Status is not Active, or the domain is typed differently in the school recordCheck for typos and set the status to Active
Certificate warning in the browserNo SSL issued for this domainRun certbot for that exact domain, or AutoSSL on shared hosting
Login page fine, but student photos and certificates 404Private media rule missing or below the caching ruleStep 2
cPanel refuses to add the domainDNS not live yet, or ownership not verifiedWait for DNS, then retry
Wildcard subdomain stopped working after 90 daysA manually issued wildcard certificate expiredAutomate renewal with the DNS plugin