Development and databases

Install Laravel on cPanel hosting (Laravel 13 and 12)

Reviewed on 17 min read instalacion composer laravel

You can install Laravel on cPanel hosting using Composer and SSH access. In this guide we will create a new project, prepare its database, configure the domain so that it publishes only the public folder and get ready the pieces a production application usually needs: the scheduler, queues, sessions, cache and, if needed, Horizon and WebSockets.

If you already have an application in a repository, you do not need to create another: connect the repository with Deploy+ and follow this guide from the database section.

1. Choose the PHP version and check the terminal's

Laravel 13 needs PHP 8.3 or later, and Laravel 12 needs PHP 8.2 or later, always within what your dependencies support. The website's version is chosen in the PHP selector; for a website published with Deploy+, it is set in your repository's .htaccess, as we explain in PHP version with Deploy+.

Open SSH or the cPanel terminal and check which version the commands run:

php -v
composer --version

The terminal's php is not always your website's version: we have seen accounts where it answered with PHP 8.1 while the selector was on 8.4. If they do not match, call your version's binary by its full path, for example /opt/alt/php84/usr/bin/php artisan migrate, and run Composer with it:

/opt/alt/php84/usr/bin/php "$(command -v composer)" install

In the rest of the guide we write php for short: replace it with the full path if your terminal gives a different version. In cron and in persistent processes, always use the full path.

Laravel's requirements include Ctype, cURL, DOM, Fileinfo, Filter, Hash, Mbstring, OpenSSL, PCRE, PDO, Session, Tokenizer and XML; the chosen database also requires its PHP driver. Extensions are enabled per version: if Composer reports ext-fileinfo is missing, enable it for the version that runs the command. Do not skip the checks with --ignore-platform-reqs: installing incompatible dependencies will not make the application work.

Each version's values, such as memory_limit, max_execution_time, upload_max_filesize or max_input_vars, can also be adjusted to what your application needs: from the Limits tab on the website's page in the client area, or from Options in the cPanel selector, as explained in PHP, select version, modules and configuration. max_execution_time only limits web requests: Artisan, the scheduler and workers have no such maximum time.

2. Create the project outside the public directory

From the root of your account, create a new folder that does not exist yet:

cd ~
composer create-project laravel/laravel mi-proyecto "^13.0"
cd mi-proyecto
php artisan --version
composer check-platform-reqs

The Artisan check should identify Laravel 13; the Composer one should confirm the environment's requirements. Do not run create-project over an existing website.

If the application or an integration requires Laravel 12, use another new folder with composer create-project laravel/laravel mi-proyecto-12 "^12.0". Its official schedule keeps security fixes until 24 February 2027; see Laravel's support policy when choosing a version. For a project that already exists, use its composer.lock with composer install; do not replace it with a new installation or change major version without reviewing the upgrade.

If you prefer a starter kit (React, Vue, Svelte or Livewire), install the official installer with composer global require laravel/installer, add to your PATH the path returned by composer global config bin-dir --absolute and use laravel new otro-proyecto in a new folder. Check laravel new --help before automating options.

3. Configure the database

A new installation can start with SQLite if PHP has its driver. To use MySQL, create the database and its user from the cPanel database wizard. If you deploy with Deploy+, it can create the database and write the connection into .env when you connect the repository.

Edit .env inside the project with the real details of that database:

DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=miusuario_proyecto
DB_USERNAME=miusuario_app
DB_PASSWORD="sustituye_por_la_clave_de_la_base"

Use the full names shown by cPanel, including their prefix. Do not publish .env or commit it to the repository. In the new project, run:

php artisan config:clear
php artisan migrate

If you are deploying an application with existing data, review the migrations and keep a backup before applying them. Do not use migrate:fresh: it drops the tables.

4. Publish only the public folder

The domain must serve /home/tu_usuario/mi-proyecto/public, not /home/tu_usuario/mi-proyecto. That way the configuration files and private code stay outside the web root.

For an addon domain or subdomain, check its directory under cPanel > Domains > Manage and, if the configuration allows it, select the project's public folder. Do not move index.php to the root to avoid this step.

On the main domain, cPanel normally uses public_html. If you need to replace that directory with a link to Laravel's public, first plan what it contains, its backup and the way back; do not rename or delete public_html without checking whether it already hosts a website or other domains. With Deploy+, the Laravel preset publishes the repository's public folder in the folder you choose, which must be empty on the first deployment.

5. Prepare and check the application

In .env, set the real URL and turn off public debugging:

APP_ENV=production
APP_DEBUG=false
APP_URL=https://tudominio.com

The installation must have an APP_KEY. In a new project with no key, generate one with php artisan key:generate. Do not regenerate it in an existing application: you could make encrypted data inaccessible.

If your project uses Vite, build its assets with a Node.js version compatible with its package.json: npm install and npm run build on a new installation; npm ci and npm run build in deployments with package-lock.json. Node.js is in the Package manager and is there for building; see Node.js for building assets.

Check that storage and bootstrap/cache are writable by the application, without applying 777 permissions across the board, and run php artisan optimize.

Open the domain over HTTPS and check the home page, inner routes and features that query the database. Also check that /.env does not serve the file. If a 500 error appears, check storage/logs/laravel.log and the hosting logs; do not turn on public debugging to show the error to every visitor.

6. Schedule the scheduler

If you publish the project with Deploy+, the wizard does this for you: leave Schedule the Laravel scheduler ticked and the task is added when the first deployment finishes, as we explain in Deploy+. Check it anyway with php artisan schedule:list.

Otherwise, in cPanel > Cron Jobs, add a task that runs every minute with your application's PHP:

* * * * * /opt/alt/php84/usr/bin/php /home/tu_usuario/mi-proyecto/artisan schedule:run > /dev/null 2>&1

Change php84 to your version and the path to your project's; with Deploy+ it is /home/tu_usuario/deploy/nombre-repo/current/artisan. Do not use /usr/local/bin/php or plain php: they may be a different PHP version, with other extensions, and the scheduler launches scheduled tasks with the same PHP it starts with.

Before leaving it in place, run that same command by hand, without discarding the output, and check php artisan schedule:list. There are more details about cron in cron jobs in cPanel.

7. Queues

Since Laravel 11, a new project stores jobs in the database (QUEUE_CONNECTION=database). In an older project, first create the table with php artisan queue:table and migrate. What changes with your plan is who processes them.

With persistent processes (Avanza, Max and Elástico)

Create a Queue worker in Tools > Processes, with the application's folder and its PHP version. It stays running, processes each job as soon as it arrives and restarts on its own if it goes down. When you publish a release with Deploy+, it restarts on its own to pick up the new code. All the details are in persistent processes.

Without persistent processes

On any plan, the scheduler can launch a worker every minute that processes what is pending and exits. In routes/console.php:

use Illuminate\Support\Facades\Schedule;

Schedule::command('queue:work --stop-when-empty --tries=3 --max-time=300')
    ->everyMinute()
    ->withoutOverlapping(10)
    ->runInBackground();

withoutOverlapping(10) stops two workers from overlapping and releases the lock after 10 minutes if one is cut off halfway; runInBackground() lets the other scheduled tasks run on time. A job may wait up to a minute before being processed.

If you would rather not depend on the scheduler, a cron task with flock does the same without overlapping:

* * * * * cd /home/tu_usuario/mi-proyecto && /usr/bin/flock -n /home/tu_usuario/.cola-mi-proyecto.lock /opt/alt/php84/usr/bin/php artisan queue:work --stop-when-empty --max-time=300 > /dev/null 2>&1

In both cases, review failed jobs with php artisan queue:failed.

8. Sessions, cache and Redis

Where to store each thing:

  • Sessions: in the database (SESSION_DRIVER=database) on any plan; from Avanza upwards, also in persistent Redis (redis).
  • Queues: in the database (QUEUE_CONNECTION=database) on any plan; from Avanza upwards, also in persistent Redis (redis).
  • Cache: in the database (CACHE_STORE=database) or in Redis Cache, on any plan.
  • Horizon: only with persistent Redis, from Avanza upwards.

The two Redis services are not for the same thing:

  • Redis Cache, on all plans, is cache only: it starts empty when restarted and discards keys when it fills up. Use it for CACHE_STORE=redis and never for sessions or queues. Configuration in Redis as an object cache.
  • Persistent Redis, from Avanza upwards, stores its data on disk and does not discard it: it is the place for queues, sessions and Horizon. You turn it on in the Package manager and it connects through a socket in your account. Configuration in persistent Redis.

You can use both at once: the cache in Redis Cache and queues and sessions in persistent Redis. Database sessions have been the default since Laravel 11 and work on any plan; in an older project, create their table with php artisan session:table.

9. Horizon

Horizon needs queues in Redis and a running process, so it works from the Avanza plan upwards: turn on persistent Redis, set QUEUE_CONNECTION=redis, install laravel/horizon and create a persistent process of type Horizon. Adjust maxProcesses in config/horizon.php to your plan's resources. With Deploy+, Horizon restarts on its own after each release is activated; if you publish another way, run php artisan horizon:terminate once the new code is in place. The steps are in persistent Redis for queues, sessions and Horizon.

Horizon on Redis Cache is not safe: if that cache is emptied or fills up, jobs are lost.

10. Broadcasting and WebSockets

We do not offer Reverb on the hosting: it is a server that listens on its own port, and we do not support programs like that, as we explain in persistent processes. For real-time notifications and events, connect your application to a service compatible with the Pusher protocol, such as Pusher Channels or Ably, which Laravel supports out of the box. Your application only makes outbound HTTPS requests and browsers connect to the service. Comparison and configuration in WebSockets in Laravel.

What does not fit on the hosting

  • Octane: here PHP is served by LiteSpeed, and Octane needs its own application server in front (Swoole, RoadRunner or FrankenPHP).
  • Reverb or another WebSockets server of your own: use a Pusher-compatible service.
  • Node.js as a server: Node is there to build assets. Inertia SSR needs a running Node server; without it, Inertia renders in the browser as usual.
  • System services or root access. If your project needs them, tell us about your case before ordering.

Livewire, Inertia, Filament, Sanctum, Socialite and Scout work with nothing special.

Publish later changes with Deploy+

Once the project is in Git, Deploy+ publishes each release in its own folder, with shared files and releases you can go back to. A deployment script for Laravel:

composer install --no-dev --optimize-autoloader --no-interaction
php artisan migrate --force
php artisan optimize

If you build assets, add the npm lines given in the Deploy+ guide. You do not need queue:restart or horizon:terminate: after each release is activated, and also when you go back to an earlier one, Deploy+ restarts the application's persistent processes. A code rollback does not automatically undo database changes: design your migrations with that difference in mind.

The recommended script, which processes the hosting supports and the comparison with a VPS are summarised in Laravel hosting with Deploy+.

References: Laravel installation, queues and deployment recommendations.

You may also find useful

Something wrong or out of date? Tell us and we will review it.