LARAVEL
July 9, 2026

How to Manage Environment Variables in Laravel Production

6 min read
Author
CloudStick Team
WordPress Engineer
Share this article
How to Manage Environment Variables in Laravel Production
CloudStick
Env Variables

Keep .env out of git, permanently

The .env file never belongs in version control, and Laravel enforces this by shipping .env in .gitignore from the very first commit. The reason is blunt: that file contains your database password, APP_KEY, mail credentials, and every API secret the app touches. A repository is copied — to laptops, CI runners, forks, and backup services — and a secret that has been copied is a secret you no longer control.

What does belong in git is .env.example: every key the app needs, with safe placeholder values. It documents the contract without leaking anything, and a new environment starts as a copy of it. If a real .env has ever been committed, deleting the file in a later commit is not enough — the history still holds it. Rotate every credential it contained and treat them as public.

Treat the production .env like a server credential, not like application code: it lives on the server, it is backed up like a secret, and it travels through git never — the same discipline you apply to an SSH private key.

The production .env baseline

Three lines separate a production .env from a development one, and getting any of them wrong is either a security hole or a performance problem:

APP_ENV=production
APP_DEBUG=false
APP_KEY=base64:GENERATED_BY_ARTISAN_KEY_GENERATE
APP_URL=https://example.com
DB_CONNECTION=mysql
DB_DATABASE=myapp
DB_USERNAME=myapp
DB_PASSWORD=long-random-generated-password
CACHE_STORE=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis

APP_DEBUG=false is the one that bites hardest when forgotten: with debug on, any unhandled exception renders a stack trace that can include database credentials, request payloads, and environment values — served to whoever triggered the error. APP_KEY encrypts sessions, cookies, and anything you pass through Laravel's encrypter; generate it once with php artisan key:generate and then guard it, because rotating it later invalidates every session and makes previously encrypted data unreadable unless you re-encrypt it deliberately.

Cache the config — and respect the env() rule

Production Laravel should always run with a cached configuration — php artisan config:cache compiles every config file plus the .env values they reference into one file, skipping the .env parse on every request. But caching changes the rules: once the config is cached, env() returns null everywhere except inside the config files themselves, because the .env file is simply never read again.

// config/services.php — correct: env() read at cache time
'stripe' => ['secret' => env('STRIPE_SECRET')],
// app code — correct: read through config()
$secret = config('services.stripe.secret');
// app code — breaks silently once config is cached
$secret = env('STRIPE_SECRET'); // null in production

The workflow that follows from this: every env value flows through a config file, app code only ever calls config(), and any edit to .env is followed by php artisan config:cache to rebuild the compiled copy. Editing .env and wondering why nothing changed is one of the most common Laravel production mysteries — the answer is almost always a stale config cache.

Lock down .env file permissions

On the server, .env should be readable by exactly one identity: the system user PHP-FPM runs the site as. Mode 600 and correct ownership close it to every other account on the machine:

$ chown appuser:appuser /home/appuser/apps/myapp/.env
$ chmod 600 /home/appuser/apps/myapp/.env
$ ls -l /home/appuser/apps/myapp/.env
-rw------- 1 appuser appuser 1204 Jul 9 10:02 .env

Isolation between sites matters just as much on a shared server: if every site runs as the same user, any compromised site can read every other site's .env. This is a place where the hosting layer does real security work — CloudStick provisions each web application under its own isolated system user with an open_basedir restriction, so site A's PHP process cannot traverse into site B's directory at all, and a plugin exploit on one client site does not hand over the credentials of every app on the box. If you manage the server by hand, replicate that pattern: one system user per application, no exceptions.

Encrypt env files for safe transfer

When a production .env does need to move — a new team member setting up, a migration to a new server — Laravel has a built-in answer that beats pasting secrets into chat: env:encrypt produces an encrypted copy that is safe to send or even commit, and env:decrypt restores it with the key you share through a separate channel:

$ php artisan env:encrypt --env=production
INFO Environment successfully encrypted.
Key ........ base64:mFj3sN82hV... <- share this out-of-band
Cipher ..... AES-256-CBC
File ....... .env.production.encrypted
$ php artisan env:decrypt --env=production --key=base64:mFj3sN82hV...

The encrypted file and its key must never travel together — send the file by one channel and the key by another. For day-to-day edits directly on the server, an SSH session or a dashboard file manager (CloudStick ships both, with the terminal right in the browser) keeps the file where it belongs instead of round-tripping it through a laptop.

Per-environment files and next steps

Staging deserves its own .env with its own database, its own mail configuration (a trap like Mailpit, never the real mail provider), and its own APP_ENV=staging — the env:encrypt workflow above supports this directly with .env.staging and the --env=staging flag. What staging should never have is a copy of production credentials taken "just to test something": the whole point of the separation is that a staging mistake cannot touch production data.

If you run zero-downtime deploys, remember the .env lives in the shared directory and is symlinked into each release — one canonical copy, edited in one place, surviving every deploy. That structure plus the config cache rule covers the two ways env changes usually go wrong on release day.

The checklist to leave with: .env ignored by git and documented by .env.example; APP_DEBUG=false and a guarded APP_KEY; every value read through config() with the config cache rebuilt after each edit; mode 600 under an isolated per-site user; env:encrypt for any transfer; and separate files per environment. None of these steps is longer than a minute, and together they close the most common way Laravel apps leak the keys to everything.

Leave a comment
Full Name
Email Address
Message
Contents