# Laravel .env file, APP_KEY and config caching

> Laravel loads .env through phpdotenv, lets real environment variables win, and stops reading .env after config:cache. APP_KEY rotation and Crunu included.

URL: https://crunu.com/environment-variables/laravel
Language: en
Updated: 2026-10-30

Laravel reads its `.env` file with the phpdotenv library each time the application starts to handle a request or an Artisan command, and real environment variables set on the server or in the system take precedence over the file. Configuration files in `config/` turn those variables into settings with `env()`, and the rest of the app reads settings with `config()`. In production, `php artisan config:cache` freezes that result into one cached file, after which Laravel doesn't read `.env` at all. If `APP_ENV` is provided from outside, Laravel loads `.env.[APP_ENV]` instead of `.env` when that file exists. This page follows the [Laravel 13.x configuration documentation](https://laravel.com/framework/docs/13.x/configuration), checked October 30, 2026.

## Which file Laravel loads

| Situation                                                                                | What Laravel reads                                                            |
| ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| `APP_ENV` set outside the app, or `--env` passed to Artisan, and `.env.[APP_ENV]` exists | `.env.[APP_ENV]`                                                              |
| otherwise                                                                                | `.env`                                                                        |
| configuration cached with `config:cache`                                                 | no file; the cached configuration plus real environment variables for `env()` |

*Figure: Diagram of how Laravel loads environment variables. Env files are read in this order of priority, and the first one wins: .env.\[APP_ENV] and .env. Values are read from the environment when the app starts. There is no public prefix: nothing reaches the browser unless your code sends it.*

The first row surprises people who expect files to stack. `.env.staging` replaces `.env`, it doesn't override a few keys on top of it. Everything the staging app needs has to be in that one file.

Above any file sits the real environment. A variable set by the web server, the container runtime or a process manager overrides the same name in `.env`. That's also why a value exported by mistake on a server can shadow the file for months without anyone noticing.

## What env() returns, and what it doesn't

All values in `.env` are strings, with a few reserved words. `env()` turns `true` and `(true)` into a boolean, `false` and `(false)` into false, `empty` into an empty string and `null` into PHP null. Quote values with spaces: `APP_NAME="My Application"`.

`env()` has a second argument for a default, returned when the variable doesn't exist. Laravel's own config files use it everywhere, for example `'debug' => (bool) env('APP_DEBUG', false)`. That particular one deserves care: the documentation says `APP_DEBUG` must always be false in production, because true risks showing sensitive configuration values to your users.

## config:cache changes the rules

Caching is recommended for every production deploy. `php artisan config:cache` combines all configuration files into one file that loads faster. From then on:

- **`.env` is not loaded**, neither during requests nor during Artisan commands.
- **`env()` returns only external, system-level variables.** A call to `env('STRIPE_SECRET')` in a controller or a job returns `null` in production unless the server's real environment has it.
- **Settings keep the values from the moment of caching.** Changing an environment variable later does nothing until you cache again or run `config:clear`.

The rule that follows is short: call `env()` only inside files in `config/`, and use `config()` everywhere else. Then caching can't break anything.

There's a quieter consequence. The cache is a file with the resolved configuration, so every secret that went through `env()` in a config file is now written in it in plain text. Treat that file like a `.env`: keep it out of images you publish, out of backups that many people can read, and out of the repository.

## APP_KEY and rotation

`APP_KEY` drives Laravel's encrypter. The [encryption documentation](https://laravel.com/framework/docs/13.x/encryption) says to generate it with `php artisan key:generate`. Every cookie Laravel sets is encrypted with it, including the session cookie, so changing the key logs out every user, and data you encrypted with the old key can no longer be decrypted.

Laravel supports graceful rotation. Put the new key in `APP_KEY` and the previous ones in `APP_PREVIOUS_KEYS`, comma-separated. Laravel always encrypts with the current key and, when decrypting, tries the current key first and then each previous key. Keep old keys there until the data and cookies that used them have been re-encrypted or expired.

## Laravel's own encrypted .env

`php artisan env:encrypt` encrypts `.env` into `.env.encrypted`, which you can commit. The key is printed once, and you keep it in a password manager or pass it as `LARAVEL_ENV_ENCRYPTION_KEY` to `php artisan env:decrypt`, which writes the plain `.env` back to disk. The `--readable` option keeps variable names visible and encrypts only values, which makes pull requests easier to review. The default cipher is AES-256-CBC.

That's a reasonable setup for a small team. Its limits are the usual ones for a shared file key: everyone with `LARAVEL_ENV_ENCRYPTION_KEY` can decrypt every committed version, there is one key for the whole file, and the decrypted `.env` ends up on disk on every machine that runs the app.

## Run Laravel with values from Crunu

`crunu run` decrypts a Crunu environment in memory and starts your command with its variables in the process environment. Laravel treats them as external variables, which outrank `.env`, so no file has to exist. Each stage has its own Crunu environment and its own key, and a person with access to development never receives production values.

1. Put `APP_KEY`, `APP_ENV`, `APP_DEBUG`, database credentials and API keys into Crunu environments such as `laravel-app/development` and `laravel-app/production`.
2. Develop with `crunu run --env laravel-app/development -- php artisan serve --no-reload`, and run other Artisan commands the same way.
3. On a Linux server with the agent, cache the configuration with `crunu run --env laravel-app/production -- php artisan config:cache` during the deploy.
4. Run queue workers under `crunu run --env laravel-app/production --restart on-change -- php artisan queue:work`.

Why `--no-reload` in step 2? When a `.env` file exists, `php artisan serve` watches it and restarts its PHP server on changes. To let the restarted server read the file fresh, it starts that server with a filtered environment: every variable in `$_ENV` that isn't on a short passthrough list, such as `APP_ENV` and `PATH`, is removed, as the [ServeCommand source](https://github.com/laravel/framework/blob/13.x/src/Illuminate/Foundation/Console/ServeCommand.php) shows. Depending on the `variables_order` setting in your `php.ini`, that can include the values `crunu run` passed in. `--no-reload` keeps the whole environment, and so does deleting `.env` once its values live in Crunu.

Step 3 is a choice with a cost. Caching under `crunu run` gives you the speed of a cached configuration, but the cached file then holds the production values on the server's disk, as described above. If that matters more than the speed gain, skip `config:cache` and let PHP read the environment on each boot. Either way, re-run the cache step after any value changes, or the app keeps the old ones.

For long-running processes such as queue workers, `--restart on-change` restarts the worker when a value changes, so a rotated API key reaches it without a deploy.

The web side needs more thought. Under PHP-FPM, the [`clear_env` directive](https://www.php.net/manual/en/install.fpm.configuration.php) defaults to yes: FPM clears the environment of its worker processes and adds back only the variables listed with `env[...]` in the pool configuration. Starting FPM under `crunu run` therefore doesn't hand your variables to the PHP code by itself. You either list each variable in the pool with `env[NAME] = $NAME`, set `clear_env = no` for that pool, or use `config:cache` from step 3 so the web workers read the cached configuration instead of the environment. Laravel Octane and `php artisan serve` run as one long-lived command and inherit the environment of `crunu run` directly.

## Related reading

- [Rails](https://crunu.com/environment-variables/rails) for encrypted credentials with a master key.
- [Rotate a Laravel APP_KEY](https://crunu.com/blog/rotate-laravel-app-key) and the [Laravel APP_KEY generator](https://crunu.com/tools/secret-generator/laravel-app-key).
- [Docker Compose](https://crunu.com/environment-variables/docker-compose) for Laravel in containers.
- [How to share .env files with your team](https://crunu.com/guides/share-env-files-with-a-team).

## FAQ

### Does a server environment variable override the value in .env?

Yes. Laravel's documentation says any variable in .env can be overridden by external environment variables, such as server-level or system-level ones.

### Why does env() return null in production?

Because the configuration is cached. After php artisan config:cache, Laravel doesn't load .env during requests or Artisan commands. Call env() only inside config files and read values with config() everywhere else.

### Is .env.encrypted a good place for production secrets?

It keeps an encrypted copy in Git, decrypted with LARAVEL_ENV_ENCRYPTION_KEY. Everyone who has that key can read every committed version, so treat the key like the values themselves and rotate both when someone leaves.

### How do I rotate APP_KEY without logging users out?

Set the new key in APP_KEY and list the previous keys, comma-separated, in APP_PREVIOUS_KEYS. Laravel encrypts with the new key and tries the old ones when decrypting.

## Sources

- [Laravel 13.x: Configuration](https://laravel.com/framework/docs/13.x/configuration) (accessed 2026-10-30)
- [Laravel 13.x: Encryption](https://laravel.com/framework/docs/13.x/encryption) (accessed 2026-10-30)
- [Laravel framework source: ServeCommand (13.x)](https://github.com/laravel/framework/blob/13.x/src/Illuminate/Foundation/Console/ServeCommand.php) (accessed 2026-10-30)
- [PHP manual: FPM configuration, clear_env](https://www.php.net/manual/en/install.fpm.configuration.php) (accessed 2026-10-30)
