Guides
PHP and Laravel
Running a Laravel app — website, queue worker and scheduler — and plain PHP sites on Meghly.
What runs
- PHP 8.5, 8.4, 8.3 and 8.2 (the panel warns on 8.2). The version comes from
config.platform.phporrequire.phpincomposer.json. - Laravel 10, 11, 12 — recognised by the
artisanfile orlaravel/framework. - Plain PHP sites — with a
composer.jsonorindex.php. With apublic/index.php, the document root ispublic, otherwise the repository's root. - Extensions:
pdo_mysql,pdo_pgsql,zip,bcmath,opcacheand PHP's common ones are there already. Withext-pdo_sqlsrv(orext-sqlsrv) incomposer.json, SQL Server's driver is installed too.
Preparing Laravel
Do not commit .env
Every setting comes from the panel's variables, and Laravel reads them at run time. Meghly does not run config:cache, so a changed variable takes effect after a restart.
Running behind HTTPS
The app runs behind Meghly's router. Unless Laravel is told to trust the proxy, links and redirects become http://:
->withMiddleware(function (Middleware $middleware) {
$middleware->trustProxies(at: '*');
})In Laravel 10: protected $proxies = '*'; in app/Http/Middleware/TrustProxies.php.
Health check
Meghly expects a 2xx on /up. Laravel 11 and later have it already; in Laravel 10 add it:
Route::get('/up', fn () => response('OK'));Front-end (Vite)
With a package.json, the build runs npm ci and npm run build, and the result goes to public/build.
Variables
Set at least these in Environment:
APP_KEY=base64:... # on your PC: php artisan key:generate --show
APP_ENV=production
APP_DEBUG=false
APP_URL=https://shop.example.com
LOG_CHANNEL=stderr- Set
APP_KEYonce and never change it — changing it breaks encrypted data and sessions. - With
LOG_CHANNEL=stderrthe log shows on the app's Logs page.
A linked database brings DB_CONNECTION, DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD by itself — no need to touch config/database.php. A linked Redis brings REDIS_HOST, REDIS_PASSWORD … and REDIS_CLIENT=phpredis.
To send mail (port 25 is closed), use SMTP:
MAIL_MAILER=smtp
MAIL_HOST=<your mail server>
MAIL_PORT=587
MAIL_USERNAME=...
MAIL_PASSWORD=...What Meghly runs
| Process | What runs |
|---|---|
web | Apache + PHP, port 8080, health /up |
worker | php artisan queue:work --sleep=3 --tries=3 --max-time=3600 |
scheduler (cron) | php artisan schedule:run every minute |
| release | php artisan migrate --force before every deploy |
If you do not use queues, turn worker off in Processes. After every deploy the worker moves to the new version by itself. With Laravel Reverb, WebSockets work on the app's HTTPS address.
The storage folder
storage/ is a volume — uploads, cache, sessions and log files survive deploys.
php artisan storage:link cannot run while the app runs (the file system is read-only). Two ways:
- Run
php artisan storage:link --relativeon your PC and commit thepublic/storagesymlink to Git. (A symlink may not survive a zip upload.) - Or keep files in S3-style storage (
FILESYSTEM_DISK=s3).
Bringing old data
Export from the old server:
mysqldump --single-transaction --routines --triggers --no-tablespaces mydb > mydb.sqlThen in the panel, Databases → the database → Import SQL dump. The import runs inside your database — a dump naming another database (USE other_db) will not work.
When something goes wrong
| What you see | Cause and fix |
|---|---|
500, and No application encryption key in the log | Set APP_KEY. |
http:// links or redirects, mixed content | trustProxies(at: '*') and https:// in APP_URL. |
| Stops at the health check (Laravel 10) | Add the /up route. |
Permission denied … bootstrap/cache | Do not commit bootstrap/cache; the build makes it. |
Release: could not find driver (SQL Server) | Add ext-pdo_sqlsrv to composer.json. |
| Queued jobs do not run | Check worker is on and QUEUE_CONNECTION is right. |
| The scheduler does not run | Check the scheduler cron is on in Processes. |