# PHP

A PHP app deploys from its repository with no `Dockerfile`. Nimbus Land recognises it from a root `composer.json` and serves it with Apache.

## Quick start

A stock `laravel new` project deploys as is. In the console, create an app from your repository with the deploy wizard. From the CLI, run this in the project directory:

```sh
nimbus deploy
```

Check these first:

- **`composer.json` is at the root** of the app (or choose the app directory; see [repositories with several apps](apps.md#repositories-with-several-apps)).
- **Anything the app must keep is not in the app directory.** Files written at run time are lost on the next release. See [Where your app can write](#where-your-app-can-write).
- **Laravel: accept the suggested Postgres database and release command** in the wizard. See [Laravel](#laravel).

## Apache by default

Unless you commit a `Procfile`, Nimbus Land serves the app with Apache and configures it to treat requests that reached the platform over HTTPS as HTTPS, so your app builds `https://` URLs. A committed `Procfile` always wins, for example:

```text
web: heroku-php-nginx -C nginx.conf public/
```

See [what every app needs](apps.md#what-every-app-needs).

## Laravel

A stock `laravel new` project deploys as is:

- **Document root.** Apache serves `public/` (`Nimbus: no Procfile; serving public/ with Apache` in the log). Nothing above it (`.env`, `composer.json`, `routes/`) is reachable.
- **`APP_KEY`** is generated on the first deploy when absent and stored on the app, so sessions and encrypted values survive redeploys. It is stored as a plain variable: mark it secret on the **Resources** tab once the first deploy succeeds. A key committed in `.env` doesn't count, because the platform's variable takes precedence.
- **`APP_URL`** is set to `https://<app>.nimbusapps.ca` (or your verified custom domain) when unset. When you add a custom domain on the **Access** tab, you can move `APP_URL` to it. See [Custom domains](custom-domains.md).
- **Environment.** The wizard suggests `APP_ENV=production`, `APP_DEBUG=false` and `LOG_CHANNEL=stderr`. The last one sends Laravel's logs to the app's logs instead of a file inside the instance.
- **Front-end assets.** If `package.json` has a `build` script, the build installs Node dependencies with your lockfile's package manager (`npm ci`, `yarn install` or `pnpm install`), runs `build` with Node 22, and puts `public/build/` in the image. Only `build` runs. Without a lockfile, one is generated for the build.
- **Database.** The wizard suggests Postgres, pre-checked, because a stock project keeps sessions, cache and queues in the database. Attaching it sets `DB_URL`, `DB_HOST`, `DB_PORT`, `DB_DATABASE`, `DB_USERNAME`, `DB_PASSWORD` and `DB_SSLMODE`, and the wizard adds `DB_CONNECTION=pgsql`.
- **Migrations.** The wizard suggests `php artisan migrate --force` as the release command (see [release command](apps.md#release-command)). To run it once: `nimbus run php artisan migrate --force`, or **Run a command** on the **Monitoring** tab.
- **Queue workers.** In **Background workers** on the **Resources** tab, add **Queue worker** (`php artisan queue:work`) or **Scheduler** (`php artisan schedule:work`). See [commands, workers and one-off runs](apps.md#commands-workers-and-one-off-runs).

## Symfony

Symfony apps are also served from `public/`, and need `symfony/apache-pack` for routes other than `/` to work under Apache.

## Plain PHP

For PHP without Laravel or Symfony, and no `Procfile`, Apache serves:

- the repository root, if it has an `index.php` or `index.html`;
- otherwise the one folder among `public/`, `www/`, `web/`, `htdocs/` and `public_html/` that has one, so nothing outside it is reachable;
- otherwise the root, with a build log warning. Serving the root exposes every file in the repository, so add a `Procfile` (`web: heroku-php-apache2 site/`).

## Where your app can write

The app directory (`/workspace/src`) and `/tmp` are writable, but each instance has its own copy, reset on every release and restart. Everything else is read-only, so writing to `/var/www/uploads` fails with `Permission denied`; build paths from `__DIR__`. Keep files that must last (uploads, a SQLite database) on a [volume](volumes.md) mounted outside the document root, such as `/workspace/src/storage`, or in [object storage](object-storage.md).

## Troubleshooting

For problems that aren't specific to PHP, such as an app that never answers, see [Apps troubleshooting](apps.md#troubleshooting).

### Page styles or scripts blocked

The page loads over HTTPS but has no styles or JavaScript, the browser console reports *Mixed Content*, and the app's **Overview** shows **Page loads styles or scripts over HTTP** (see [Page check](page-check.md)).

Nimbus Land handles HTTPS at its edge and forwards requests to your app over HTTP with `X-Forwarded-Proto: https`. A framework that doesn't trust that header builds `http://` URLs. Only Nimbus Land's edge can reach your app, so trusting every proxy is safe:

- **PHP without a `Procfile`** (Laravel, Symfony, WordPress, plain PHP): deploy again.
- **Laravel with its own `Procfile`**: Laravel 11 and later, `$middleware->trustProxies(at: '*')` in `bootstrap/app.php`; Laravel 10, `protected $proxies = '*';` in `app/Http/Middleware/TrustProxies.php`.
- **Symfony**: `framework.trusted_proxies: '127.0.0.1,REMOTE_ADDR'` and `trusted_headers: ['x-forwarded-for', 'x-forwarded-proto']`.
- **A hard-coded `http://` URL** (a CDN, a theme setting): change it to `https://`.

Then choose **Check again** on the Overview.

### `Permission denied` writing a file

The path is outside the app directory and `/tmp`. See [Where your app can write](#where-your-app-can-write).

### Routes other than `/` return 404 (Symfony)

Add `symfony/apache-pack`. See [Symfony](#symfony).

### Database certificate errors

Attaching Postgres sets `DATABASE_CA_CERT`. PHP's libpq connects in `prefer` mode by default, which works as is. To verify the certificate, write `DATABASE_CA_CERT` to a file at start-up and use `sslmode=verify-full sslrootcert=<path>`.
