Zodiac Tech CRM Docs

cPanel deployment walkthrough

Step-by-step production install on shared cPanel hosting — subdomain, document root, upload, installer, database, permissions, cron and first login.

super-admin developer support Advanced ⏱ 45 min Updated 2026-10-06

This walkthrough installs Zodiac Tech CRM on shared cPanel hosting (the production target: https://crm.zodiactechsoft.in). It uses the browser installer — the same flow buyers use — and documents the manual artisan equivalents for hosts with Terminal access.

Who this is for

  • Super-admin / hosting admin — doing the first install.
  • Developer — reproducing a customer's install problem.

Prerequisites:

  • cPanel login that can create subdomains, databases, cron jobs and set PHP versions.
  • The release zip of the application (or the project folder from your source).
  • ~30–60 minutes.

Two supported layouts Preferred: point the (sub)domain's document root directly at the app's public/ folder. URLs then have no subfolder — https://crm.example.com/admin. Fallback: upload into a folder/subdirectory. The shipped root index.php + .htaccess bridge forward into public/, which is exactly how the local XAMPP install works (http://localhost/Lead%20Hub/). URLs then include the subfolder, including API/webhook URLs.


Step 1 — Create the subdomain

  1. cPanel → Domains → Create A New Domain (older panels: Subdomains).

  2. Enter crm for crm.zodiactechsoft.in (adjust to your domain).

  3. Document Root — type the folder where the app will live, ending in /public, for example:

    /home/YOURUSER/crm-app/public
    

    If cPanel forces public_html/crm as the root and you cannot change it, that is fine — you are now in the fallback layout; keep reading, Steps 6–7 cover it.

  4. Create.

Expected result: the subdomain resolves (it may 403/404 until files exist).

Step 2 — Set the PHP version

  1. cPanel → MultiPHP Manager → select the subdomain → PHP 8.4.
  2. cPanel → Select PHP Version → Extensions (or MultiPHP INI Editor) and confirm the required extensions are enabled: pdo_mysql, mbstring, zip, gd, curl, bcmath, fileinfo, intl if offered.

Expected result: PHP 8.4 is active for the domain. (php -v in cPanel Terminal should also say 8.4 if you have terminal.)

Step 3 — Upload the application

Everything lives in one app folder. Upload it to the folder above your document root (e.g. /home/YOURUSER/), so the layout becomes /home/YOURUSER/crm-app/….

3a — What must be in the package

Item What it is Notes
app/, bootstrap/, config/, database/, lang/, resources/, routes/ Application code, migrations, translations, views Required
public/ Web root: entry point, built assets (build/, css/, js/, docs-assets/), and the installer Required
Modules/ Runtime module folder (marketplace/module-manager installs land here) Optional — safe to omit; the app boots fine without it and creates it on first module install
vendor/ Composer dependencies Required on shared hosting (no Terminal/Composer there). With Terminal you may instead run composer install --no-dev --optimize-autoloader and skip uploading it
storage/ Skeleton runtime folders (logs/cache/framework) Required; made writable in Step 4; may be empty except .gitkeep files
artisan, composer.json, composer.lock CLI entry + dependency manifest Required
index.php, .htaccess, install.php, cron.php Root bridge files (.htaccess is a hidden dot-file!) Required for the fallback subfolder layout and for the no-Terminal cron runner
.env.example Environment template for reference Optional — the installer writes .env itself
phpunit.xml, tests/ Automated test suite Optional — exclude to slim the package on production uploads

Show hidden files before checking the upload cPanel File Manager hides dot-files by default. Enable Settings → Show Hidden Files and confirm .htaccess is present in the app root. Without it, the fallback (subfolder) layout cannot route — requests 404 no matter what else is correct.

3b — What to leave out

Exclude Why
.env Created by the installer on the server. Never ship one — it carries database credentials and keys.
CREDENTIALS.txt Never upload or share this file — it lists login credentials.
.git/, .claude/, .agents/, .gstack/ Version-control and local tooling state.
node_modules/ Not needed at runtime — the built assets already ship inside public/.
archive/, docs/, docs-internal/, scripts/, tools/, android/, demo-apk/ Internal/dev-only material — all QA/audit reports live under archive/ (2026-10-06 cleanup).
storage/logs/*, storage/framework/{cache,sessions,views}/*, bootstrap/cache/*.php Stale runtime output of the machine that built the package — clear it so the new host starts clean (keep the empty bootstrap/cache/ folder + its .gitkeep).
bash.exe.stackdump, .phpunit.result.cache Junk files.

3c — Upload mechanics

  1. cPanel → File Manager → navigate to the folder above your document root.
  2. Upload the package zip, then select it → Extract.
  3. Confirm the structure: crm-app/app/, crm-app/public/, crm-app/vendor/, crm-app/index.php, and crm-app/.htaccess (hidden files on).

Upload the zip, extract on the server Do not extract on your PC and then upload tens of thousands of vendor/ files one by one — it is many times slower. Zip → upload → Extract in File Manager is the fast path on shared hosting. If you prefer FTP, upload the zip and extract over SSH, or compress again before transfer.

If you have Terminal and prefer git/uploads without a zip:

cd ~/crm-app   # or wherever the app will live
# upload via your preferred method, then:
php -v         # confirm 8.4

Step 4 — Prepare dependencies and write permissions

With Terminal (Composer 2 available):

cd ~/crm-app
composer install --no-dev --optimize-autoloader

The post-autoload-dump scripts automatically run artisan package:discover and artisan filament:upgrade, so Filament's assets and caches are wired correctly.

Without Terminal (typical shared hosting): skip composer install — upload the vendor/ folder from your build machine as part of the zip, or via FTP/File Manager. A vendor/ built by the same PHP version line is required (PHP 8.4).

Then make the app writable. In File Manager, set:

  • storage/ — recursively, permission 775
  • bootstrap/cache/ — 775
  • public/ — 775 (writes per-tenant branding CSS into public/tenant/<id>/ on settings save)
# Terminal equivalent
chmod -R 775 storage bootstrap/cache public

Do not skip this A non-writable storage/ produces &quot;The stream or file could not be opened&quot; the moment Laravel boots — long before the installer can help.

Step 5 — Create the database

  1. cPanel → MySQL Databases.
  2. Create database YOURUSER_leadhub and a dedicated user with a strong password.
  3. Add the user to the database with ALL PRIVILEGES.

Expected result: you have four values ready for the next step: host (localhost), database name, username, password.

Bringing existing data instead of a fresh database? If you are moving hosts / restoring / installing pre-loaded data, you will import a dump rather than start empty — follow Database setup &amp; migrations → Import an existing database, skip the browser installer in Step 6, and bring the source install's .env (its APP_KEY decrypts stored credentials — a new key cannot).

Full detail (including utf8mb4 and migration options): Database setup & migrations.

Step 6 — Run the browser installer

  1. Open in a browser:

    https://crm.zodiactechsoft.in/install.php
    

    If storage/installed.lock does not exist yet, even hitting / bounces you here automatically.

  2. Step 1 — Requirements: PHP 8.4.1+, extensions, writable folders. Fix anything red.

  3. Step 2 — Database: enter the credentials from Step 5 (host, port, database, user, password). The installer tests the connection before continuing.

  4. Step 3 — Admin: create your first admin login (name, e-mail, password).

  5. Step 4 — Install: review the summary — it shows the exact APP_URL it will write to .env — and run the install. The installer writes .env and generates APP_KEY, migrates the schema, seeds base data (roles, plans, permissions, locales), creates the owner account, and writes storage/installed.lock.

APP_URL is auto-derived — there is no URL prompt The installer writes APP_URL from the address you are using to reach it (scheme + host + subfolder, e.g. https://crm.example.com or http://localhost/Lead%20Hub). Before installing, confirm the summary line matches the final URL. Any domain, subdomain, or subfolder works — but if you later move the app, edit APP_URL in .env and run php artisan optimize:clear (a cached config keeps old URLs).

Expected result: the installer writes storage/installed.lock and points you at the login page.

The installer expects an EMPTY database It migrates the schema from scratch, seeds base data, and creates your first admin. If you instead imported a dump (existing tenants/leads/users), do not run the installer against it — it would create a second admin account on top of your data. For that path: copy .env.example to .env, set DB_* to the imported database, paste the source install's APP_KEY, then run php artisan optimize:clear &amp;&amp; php artisan migrate --force (full steps).

Prefer the manual route? With Terminal: cp .env.example .env, edit the DB_* keys and APP_URL, then php artisan key:generate &amp;&amp; php artisan migrate --force and create the first user in /super-admin. The installer simply automates these.

Step 7 — Layout check (the subfolder warning)

If the document root points at public/ — nothing to do. https://crm.zodiactechsoft.in/ serves the app.

If you could NOT point the document root at public/ (the app root itself is the web root, e.g. /home/USER/public_html/crm/), the shipped root files handle it:

  • The root .htaccess re-exports the Authorization header, blocks app|bootstrap|config|database|resources|routes|storage and dotfiles, forwards static trees from public/ (build, css, js, storage, vendor, install.php, …), and routes everything else into the root index.php.
  • The root index.php requires public/index.php in a way that keeps Symfony's base-URL detection aligned with /crm — so Laravel sees /login, not /crm/login.

This mirrors the local XAMPP setup exactly (http://localhost/Lead%20Hub/ works this way). Verify after install:

https://crm.zodiactechsoft.in/crm/login      ← should load the login page

Subfolder + webhooks In the fallback layout every URL includes the subfolder — including API and webhook callback URLs you paste into Meta/WhatsApp (https://crm.zodiactechsoft.in/crm/webhook/...). If you can point the document root at public/, you avoid this class of mistakes entirely.

Step 8 — Common finishing commands (Terminal, optional)

cd ~/crm-app
php artisan storage:link          # public link for uploaded files (see note)
php artisan migrate --force       # if anything is pending
php artisan optimize:clear        # clear stale compiled config/routes/views

storage:link is recommended but no longer load-bearing for the core branding flow: logo/favicon URLs are rewritten to the symlink-free /media route (app/Support/PublicMedia.php) and call recordings stream from the private disk through a signed route — neither needs the symlink. Uploaded files that still emit public /storage/... URLs (WhatsApp template media handles, Meta creative assets) do use it, so run it unless your host forbids symlinks entirely.

Two .env tweaks the installer cannot infer (add them and run php artisan optimize:clear):

  • TRUSTED_PROXIES — if the site is behind Cloudflare or a reverse proxy (or any host that terminates TLS before PHP), set TRUSTED_PROXIES=* (or the proxy IPs, comma-separated). Without it $request->ip() is the proxy's IP — degrading login lockout, audit-log IPs, and the Super Admin IP allowlist.
  • SESSION_SECURE_COOKIE=true — once the site is on HTTPS (any Let's Encrypt/cPanel SSL cert), set this so session cookies are only sent over TLS. Pair it with APP_URL=https://... — an http:// APP_URL on an HTTPS site breaks OAuth redirects and generated URLs.

Step 9 — Add the one cron entry

cPanel → Cron Jobs → add a once-per-minute job:

* * * * * cd /home/YOURUSER/crm-app && php artisan schedule:run >> /dev/null 2>&1

On hosts where the php binary is not the 8.4 one, use the full path (cPanel shows it, e.g. /usr/local/bin/php or ea-php84):

* * * * * /usr/local/bin/ea-php84 /home/YOURUSER/crm-app/artisan schedule:run >> /dev/null 2>&1

No SSH/Terminal at all? The product ships a fallback runner:

* * * * * php /home/YOURUSER/public_html/crm/cron.php

cron.php drains queued jobs (up to 30 per run, 55 s budget) and then runs schedule:run — see Cron & queue workers.

Expected result: within a couple of minutes, queued jobs start processing and scheduled tasks appear in logs.

Step 10 — First login and go-live checks

  1. Open https://crm.zodiactechsoft.in/login (or /super-admin for the platform panel) and sign in with the owner account.
  2. Confirm: /api/v1/health returns {"status":"ok",...}, the /docs portal loads, and a test e-mail sends from Settings → SMTP.
  3. Work through the Go-live checklist before handing the system to anyone.

Common mistakes

  • Uploading the project folder so that public/ is buried and pointing the document root at the app root without the shipped bridge files — the fallback layout only works with the shipped root index.php + .htaccess present.
  • Using http:// for APP_URL on an HTTPS site — OAuth redirects and generated URLs break.
  • Running migrations via phpMyAdmin by hand instead of artisan/installer — schema stays untracked.
  • Granting the DB user access to all databases instead of just the app's.
  • Adding cron as schedule:run only and forgetting that queued jobs need the worker/cron drain too.

Troubleshooting

Symptom Likely cause
500 immediately on all pages with no install screen storage/ not writable, or wrong PHP version
Installer loops back to step 1 One requirement still red (usually an extension)
CSS/JS missing, page unstyled Assets not published — run php artisan filament:upgrade and php artisan storage:link; check public/build exists
Login redirects to /install.php storage/installed.lock missing (placeholder file got deleted)
API returns HTML 404 mod_rewrite/.htaccess ignored — ask host to allow AllowOverride All
Cron seems dead Wrong PHP path; test the command manually via Terminal

Technical details

  • Installer: public/install.php (pure PHP; writes .env, migrates, creates owner, writes storage/installed.lock).
  • Bridge files (subfolder layout): root index.php, root .htaccess; the standard layout relies on public/index.php + public/.htaccess instead.
  • Database schema: database/migrations/; base data: database/seeders/DatabaseSeeder.php.
  • Diagnostics: GET /api/v1/health.

Was this page helpful?