Client Area

Laravel on cPanel and DirectAdmin — What Works, What Doesn't, When to Move

ByDomain India Team·DomainIndia Support
14 min readPublished 22 Apr 2026Updated 22 Jun 2026463 views

In this article

  • 1What works, what doesn't — the honest table
  • 2Why long-running processes don't work on shared hosting
  • 3What about `queue:work --once` from cron?
  • 4What runs fine: the deployment guide
  • 5Path A — Build locally, upload artifact (works on every shared plan)

Laravel on cPanel and DirectAdmin — What Works, What Doesn't, When to Move

Verdict at the top: A vanilla Laravel application — controllers, Eloquent, Blade, Livewire, file-based cache and sessions, cron-driven scheduler, SMTP mail — runs perfectly well on Domain India shared cPanel or DirectAdmin hosting. We host hundreds of them. Where it breaks is the Laravel features that need long-running processes: Laravel Octane, queue workers, Horizon, Reverb websockets, and Redis. Those need a VPS. This article shows you exactly how to ship the parts that work, and tells you straight when you've crossed into VPS territory.

TL;DR
Vanilla Laravel + Blade + MySQL + cron-scheduler runs fine on shared. Octane / queue:work / Horizon / Reverb / Redis do not — they need persistent processes that CloudLinux LVE kills. Build locally, deploy via Path A (zip upload) or Path B (SSH + Composer). Move to VPS the moment your app needs real queue workers, sub-second job processing, websockets, or Redis.

What works, what doesn't — the honest table

FeatureShared (cPanel / DA)VPS
Vanilla Laravel app (web routes, controllers, Eloquent)YesYes
Blade templates, Livewire, InertiaYesYes
MySQL via cPanel/DAYesYes (you install it)
File-based cache + session driverYesYes
SMTP mail via cPanel mailYesYes
Cron-based scheduler ( *)Yes (1-minute granularity)Yes (any granularity)
LiteSpeed cache for static pagesYes (LSCache plugin)Yes (manual setup)
Composer install on servercPanel Pro and above (SSH)Yes
Queue: sync driverYesYes
Queue: php artisan queue:workNo (LVE kills long-running PHP)Yes
Laravel HorizonNo (needs queue workers + Redis)Yes
Redis cache / session / queueNo (no Redis daemon access)Yes
Laravel Octane (Swoole / RoadRunner / FrankenPHP)No (long-running daemon, blocked)Yes
Laravel Reverb (websockets)No (long-running daemon)Yes
Sub-minute scheduled tasksNo (cron is 1-minute granularity)Yes (with systemd timers)
File uploads > 100 MBLimited (upload_max_filesize)Yes (you configure)
Custom system libraries (FFmpeg, ImageMagick variants)EasyApache build onlyYes

If everything you need is in the "shared" column, you can stop reading here, deploy with Path A or B below, and ship. If two or more rows in the "shared = No" column matter to your app, get a VPS. The intermediate "I'll work around it on shared" path almost never ends well — we'll explain why.

Why long-running processes don't work on shared hosting

This is the part most Laravel tutorials skip. Shared cPanel and DirectAdmin accounts run inside a CloudLinux LVE (Lightweight Virtual Environment) — a kernel-enforced sandbox that limits CPU, memory, process count, and process lifetime per account. The defaults on our shared servers:

  • nproc (process limit): 100 simultaneous processes per account.
  • pmem (memory limit): 1–2 GB depending on plan.
  • Idle process killer: long-running PHP / Node processes outside the web server are killed after a few minutes.

A worker like php artisan queue:work is, by design, an infinite loop. It waits for jobs, processes them, then waits again — forever. CloudLinux sees this as an "abandoned" process and reaps it. You can demonstrate this on any shared account: SSH in, run php artisan queue:work --daemon, and watch it die within ~3-5 minutes. The same applies to Reverb's websocket daemon, Octane's Swoole/RoadRunner servers, and any custom long-lived script.

cPanel cron jobs do run, but they exit quickly — that's their contract with CloudLinux. The Laravel scheduler line everyone uses is:

cron
* * * * * cd /home/user/app && php artisan schedule:run >> /dev/null 2>&1

That works because schedule:run exits in ~50 ms after dispatching due tasks. It's the workers (schedule:work, queue:work, Horizon supervisors) that don't survive.

What about queue:work --once from cron?

People ask this every month. Yes, you can put * * * * * php artisan queue:work --once in cron as a workaround. It processes one job per minute. For a side project with 5 jobs per day, fine. For anything that processes faster than once a minute, or that needs concurrency, or that has any real-time requirement (password resets, payment retries, notification delivery) — this is a trap.

Common patterns where this falls over:

  • Failed-payment retries. Customer's card declines, Razorpay webhook fires, you queue a retry job. With cron-driven queue:work --once, your customer's retry is delayed up to 60 seconds. Half of them won't wait.
  • Email confirmations. Account signup → queued welcome mail → 30-60 second delay before delivery. Good enough for marketing, not for OTP/password-reset.
  • Order processing. Inventory check, payment capture, fulfillment trigger — all queued. Adds 60s minimum to perceived order latency.
  • Webhook fan-out. Stripe/Razorpay/Shopify webhook arrives, you queue 4 follow-up jobs. Cron only processes 1 per minute, so the 4-job pipeline takes 4 minutes.

If any of these describe your app, you've already crossed into VPS territory. Don't fight the cron-once pattern.

What runs fine: the deployment guide

The original deployment guide here was good — keeping it (lightly tightened). Two paths.

Path A — Build locally, upload artifact (works on every shared plan)

  1. Build the production artifact on your dev machine:

```bash

composer install --no-dev --optimize-autoloader

npm run build # Vite build

php artisan config:cache

php artisan route:cache

php artisan view:cache

php artisan event:cache

```

  1. Zip the project, excluding:

- .git/

- node_modules/

- .env (never upload your dev .env)

- tests/

- Contents of storage/logs/ and bootstrap/cache/ (keep folders, clear contents)

  1. Upload via cPanel File Manager:

- Upload the zip to /home/YOURUSER/laravel-app/ (deliberately outside public_html).

- Use "Extract".

- Move only the contents of laravel-app/public/ into public_html/.

- Edit public_html/index.php — change the two require __DIR__.'/../...' lines to point to ../laravel-app/bootstrap/app.php and ../laravel-app/vendor/autoload.php.

The point: your app code, .env, vendor/, models, and routes all live outside the public document root. Only public/ is web-reachable.

  1. Set permissions:

```

storage/ 755 (or 775 if your server needs group-write)

bootstrap/cache/ 755

```

Never chmod 777 on shared hosting. It's a real security risk, not a shortcut.

  1. Create production `.env` in /home/YOURUSER/laravel-app/ (not in public_html/):

```

APP_ENV=production

APP_DEBUG=false

APP_URL=https://yourdomain.com

DB_CONNECTION=mysql

DB_HOST=localhost

DB_DATABASE=yourcpaneluser_dbname

DB_USERNAME=yourcpaneluser_dbuser

DB_PASSWORD=yourdbpassword

CACHE_DRIVER=file

SESSION_DRIVER=file

QUEUE_CONNECTION=sync

MAIL_MAILER=smtp

```

Note the cache/session/queue drivers — file-based for shared. QUEUE_CONNECTION=sync means jobs run synchronously inside the request that dispatched them. Fine for low-traffic apps.

  1. Generate app key, migrate, link storage:

```bash

php artisan key:generate

php artisan migrate --force

php artisan storage:link

```

--force is required in APP_ENV=production to suppress the confirmation prompt. If your plan has cPanel Terminal, run these from there. Otherwise SSH (Pro+) or run them from a temporary script.

Path B — SSH + Composer on server (cPanel Pro and above, all DirectAdmin plans with SSH)

If your plan includes SSH, this is far cleaner:

bash
ssh [email protected]
cd ~
git clone https://github.com/you/yourapp.git laravel-app
cd laravel-app
composer install --no-dev --optimize-autoloader
cp .env.example .env
nano .env           # fill in production values (see Path A step 5)
php artisan key:generate
php artisan migrate --force
php artisan storage:link
php artisan optimize

Then in cPanel → Domains, set the document root for your domain to /home/yourcpaneluser/laravel-app/public/. Or symlink:

bash
rm -rf ~/public_html
ln -s ~/laravel-app/public ~/public_html

For redeploys, the loop becomes:

bash
cd ~/laravel-app
git pull
composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan optimize

The scheduler — works fine, with one caveat

The Laravel scheduler is the one long-running-ish thing that does work on shared, because it's invoked from cron and exits quickly. In cPanel → Cron Jobs:

cron
* * * * * cd /home/yourcpaneluser/laravel-app && php artisan schedule:run >> /dev/null 2>&1

The caveat: 1-minute granularity. ->everyTenSeconds(), ->everyThirtySeconds() look like they should work, but they require the new schedule:work daemon — which is a long-running process and gets killed by LVE within minutes. On shared, your effective minimum interval is 1 minute. Plan accordingly.

Mail — use SMTP, not mail()

Laravel's default MAIL_MAILER=mail uses PHP's mail() function, which has terrible deliverability on shared hosting (often goes straight to spam). Configure SMTP via your cPanel mailbox:

MAIL_MAILER=smtp
MAIL_HOST=mail.yourdomain.com
MAIL_PORT=465
[email protected]
MAIL_PASSWORD=yourmailpassword
MAIL_ENCRYPTION=ssl
[email protected]
MAIL_FROM_NAME="Your App Name"

For volume above ~500 emails/day, switch to SendGrid, Mailgun, AWS SES, or Postmark — providers built for transactional email deliverability. Our Email deliverability deep-dive covers SPF/DKIM/DMARC setup.

Performance on shared — what to actually expect

People assume Laravel on shared is slow. On LiteSpeed-powered cPanel (which is what we run on most servers) with php artisan optimize cached, a typical Laravel + MySQL page renders in 80-180 ms server-side at our region. That's fast enough for sites doing tens of thousands of pageviews per day.

What kills the perception:

  1. Forgetting `php artisan optimize` after deploy. Without route/config/view caches, you pay a 100 ms penalty per request just bootstrapping the framework.
  2. N+1 query bloat. Eloquent's lazy loading makes this trivial to introduce. Install Laravel Debugbar in dev, watch query counts. Eager-load relationships with with().
  3. `APP_DEBUG=true` in production. Enables stack-trace generation on every error and disables several caches. Set to false always.
  4. Vite build leaving dev assets. npm run build produces fingerprinted, minified assets. npm run dev produces dev HMR assets. Make sure the deploy uses the build output.

If after all four, you're still seeing 800 ms+ response times — your app is doing too much per request, or you've outgrown shared.

When to move to a VPS

Concrete signals. If two or more are true, upgrade now:

  • You need real queue workers (Horizon, Sidekiq-pattern dispatch, anything more than --once cron polling).
  • You need Redis for cache, session, or queue (any of the three).
  • You need Laravel Octane for performance (Swoole/RoadRunner/FrankenPHP).
  • You need Laravel Reverb for websockets / live-update features.
  • You see "resource limit reached" or LVE-fault notices in cPanel.
  • Your nproc headroom is constantly low (the cPanel resource graphs show you near the 100-process ceiling).
  • You need scheduled jobs more frequent than every 60 seconds.
  • Your app is consistently above ~5,000 pageviews per day with response times trending up.
  • You need to install custom system libraries (FFmpeg variants, ImageMagick with non-default delegates, ML/Python interop).
  • Your team uses Forge / Vapor / Envoyer-style deploy and wants to keep that workflow.

The cheapest VPS path for Laravel: Domain India VPS Starter at ₹553/month (1 vCPU, 2 GB DDR4 RAM, 64 GB NVMe, 2 TB bandwidth). Enough headroom for vanilla Laravel + Redis + 1-2 queue workers + Horizon. Step up to VPS Basic (₹1,105/month) when your queue volume or memory pressure exceeds Starter.

A skeleton VPS Laravel setup post-upgrade:

  • Nginx + PHP 8.3 FPM (or Caddy if you prefer auto-SSL).
  • MySQL 8 or PostgreSQL.
  • Redis for cache + session + queue.
  • Supervisord watching php artisan queue:work (or Horizon).
  • Cron entry running schedule:run once a minute (still the right pattern even on VPS).
  • Let's Encrypt via certbot or Caddy auto-SSL.
  • Sentry / Bugsnag for error tracking (these don't work meaningfully on shared either — same long-running-process limitation).

If you want managed-feeling Laravel hosting without becoming a sysadmin, Domain India PaaS (in beta) gives you Forge-like deploy + auto-SSL on top of containers, with Redis and Postgres add-ons.

Common errors and what they actually mean

`ErrorException: file_put_contents(...): failed to open stream: Permission denied` — most often storage/logs/laravel.log or bootstrap/cache/. Fix: chmod 755 (or 775) on storage/ and bootstrap/cache/ recursively, and ensure ownership is your cPanel user.

`No application encryption key has been specified` — you forgot php artisan key:generate, or your .env is missing entirely. Run the command, refresh.

`SQLSTATE[42000]: Specified key was too long; max key length is 1000 bytes` — old MySQL with utf8mb4 and a Laravel migration trying to index a long string column. Fix: in app/Providers/AppServiceProvider.php's boot():

php
use Illuminate\Support\Facades\Schema;
Schema::defaultStringLength(191);

Re-run migrations.

`The stream or file "/home/.../storage/logs/laravel.log" could not be opened in append mode: Failed to open stream: Permission denied` — same as the first error, more verbose. Same fix.

`HTTP 500` with no useful pageAPP_DEBUG=false is hiding the error. Tail storage/logs/laravel.log. If empty, check cPanel → Errors. If still nothing, set APP_DEBUG=true temporarily, refresh once, read the trace, set back to false.

`Class "Redis" not found` — your .env has CACHE_DRIVER=redis or SESSION_DRIVER=redis, but the PHP redis extension isn't loaded (and Redis the server isn't accessible). On shared, switch to file. On VPS, enable the extension via your package manager.

`pcntl_fork() has been disabled for security reasons` — Horizon or queue workers tried to fork. Shared hosting disables pcntl_*. You need a VPS for this workload.

`Maximum execution time of 30 seconds exceeded` — a request took longer than max_execution_time. Either optimize the request, raise the limit via .user.ini (cPanel allows you to set up to 300s), or move heavy work to queues. On shared, queues mean cron-based queue:work --once, which has the limitations described above.

`Class 'finfo' not found`fileinfo PHP extension not enabled. cPanel → Select PHP Version → Extensions → check fileinfo.

`Allowed memory size of 134217728 bytes exhausted` — PHP memory_limit too low. Add to .user.ini in your project root: memory_limit = 512M. Most shared plans allow up to 512 MB, some up to 1 GB.

`bootstrap/cache directory must be present and writable` — same permission issue as the first errors.

Frequently asked questions

Q Does Octane really not work at all on shared?

Correct. Swoole and RoadRunner both run as long-lived daemons binding ports — neither permitted on shared. FrankenPHP is the same story. Octane is a VPS-and-above feature.

Q Can I run Horizon if I keep my queue volume tiny?

No, regardless of volume. Horizon is a supervisor process that manages queue workers; the supervisor itself is the long-running thing that gets killed. If you only have a few jobs and 60-second latency is acceptable, use cron-based queue:work --once.

Q What about Laravel Vapor — can I use that with Domain India?

Vapor deploys to AWS Lambda; it's a serverless setup, not hosting on someone else's server. You can absolutely use Vapor while keeping your domain registration with us; we just won't be your hosting provider for that app.

Q Forge or DIY VPS?

Forge buys you provisioning + deploy automation, ~$19/month on top of your VPS cost. If your team is more than one person, Forge usually pays for itself in saved sysadmin time. If you're solo and like learning Linux, DIY VPS is cheaper. We have customers on both paths.

Q I'm running Laravel 11/12 — anything different?

The shared-hosting story is identical. Laravel 11 dropped some default service providers; Laravel 12 changed pagination internals. Neither affects what runs on shared vs VPS. The threshold is workload, not Laravel version.

Q Inertia.js + Vue/React — works on shared?

Yes. Inertia is just a thin protocol over standard Laravel responses; the heavy lifting is client-side. SSR for Inertia (php artisan inertia:start-ssr) does need a long-running Node process — that's the part that needs a VPS.

Q Livewire — works on shared?

Yes, perfectly. Livewire is request/response over AJAX, no long-lived server process. Even Livewire's polling features work fine.

Q Laravel Pulse?

Pulse needs Redis for performant ingestion. On shared with the file driver, it technically runs but is unusable in practice. VPS-and-above for Pulse.

Q Can I use cPanel's Setup Node.js App for Laravel websockets workarounds?

Don't try. cPanel's Setup Node.js App is genuinely useful for actual Node apps, but stuffing a websocket bridge in there to talk to a Laravel backend is the kind of architecture you'll regret in 6 months. If you need websockets, get a VPS.

Q Laravel breeze / jetstream / fortify — does the auth scaffolding affect shared compatibility?

No. All three are pure web-request scaffolding. Two-factor auth via TOTP works fine. Two-factor via SMS depends on your SMS provider's API, which is a normal HTTP call.

Bottom line

Vanilla Laravel on shared cPanel/DirectAdmin is a perfectly legitimate production setup for sites that don't need real-time, queue-heavy, or cache-heavy patterns. Most marketing sites, brochureware, small SaaS dashboards, internal tools, and content-driven apps fit comfortably in this envelope. We host hundreds of them and they work.

The trap is forcing the modern Laravel feature set — Octane, Horizon, Reverb, Redis-backed queues — onto shared and burning a week of your life on workarounds. The right answer is to recognise the threshold early and move to a ₹553/month VPS, where everything you wanted to do just works.

If you're not sure which side of the line your app sits on, send your composer.json and a description of your features to [email protected] — we'll tell you straight whether shared is enough.

Need a VPS for queue workers, Octane, or Reverb? Domain India VPS Starter ₹553/month — root access, Redis-ready, sized for production Laravel. Get a VPS plan

Was this article helpful?

Your feedback helps us improve our documentation

Still need help? Submit a support ticket