The .env file is one of the first things every Laravel developer encounters. It is also one of the first things to cause problems in production. What starts as a convenient place to store your database password quickly becomes a tangled mess of secrets, feature flags, service URLs, and configuration values that differ between your local machine, staging server, and production environment.
The twelve-factor app methodology established the principle: store configuration in the environment, not in code. Laravel embraced this through its .env file and config() helper system. But the methodology says nothing about how to manage those environment variables across multiple environments, how to keep secrets secure, or how to avoid the common pitfalls that lead to production incidents.
This guide covers the mistakes we see most often, the strategies that work, and how Deploynix simplifies the entire process.
The Most Common .env Mistakes
Mistake 1: Committing .env to Version Control
This is the cardinal sin, and it still happens regularly. A developer initializes a new repository, forgets to check .gitignore, and pushes their .env file to GitHub. Even if you delete it in a subsequent commit, the file remains in the git history. Automated scrapers scan public repositories for committed secrets, and they are fast — credentials pushed to a public repo can be exploited within minutes.
Check your .gitignore right now. It should contain:
.env
.env.*
!.env.example
The .env.example file — containing variable names without secret values — should be committed. It serves as documentation for what environment variables your application needs.
Mistake 2: Using env() Outside of Config Files
Laravel's documentation is clear on this: the env() function should only be used inside configuration files in the config/ directory. Everywhere else, use config(). The reason is caching. When you run php artisan config:cache, Laravel compiles all configuration files into a single cached file and stops reading .env entirely. Any env() call outside of a config file will return null when the config is cached.
This is a particularly insidious bug because it works perfectly in development (where you rarely cache config) and breaks silently in production.
// Wrong - will break with config caching
$apiKey = env('THIRD_PARTY_API_KEY');
// Right - works with config caching
// In config/services.php:
'third_party' => [
'api_key' => env('THIRD_PARTY_API_KEY'),
],
// In your code:
$apiKey = config('services.third_party.api_key');
Mistake 3: Identical Secrets Across Environments
Using the same database password, API key, or encryption key across development, staging, and production is a security liability. If your staging environment is compromised (and staging environments are often less secured), the attacker now has production credentials.
Every environment should have unique secrets. Every single one. This includes:
-
APP_KEY— each environment needs its own encryption key - Database credentials
- Third-party API keys (use sandbox/test keys for non-production)
- Mail credentials
- Cache and queue connection passwords
Mistake 4: Leaving Debug Mode Enabled in Production
APP_DEBUG=true in production exposes stack traces, environment variables, and database queries to anyone who triggers an error. This is not just a security issue — it is an information disclosure vulnerability that gives attackers a detailed map of your application's internals.
APP_DEBUG=false
APP_ENV=production
These two lines should be verified on every production deployment. Deploynix sets these correctly by default, but if you manage environment variables manually, double-check them.
Mistake 5: Storing .env Backups on the Server
Developers sometimes create .env.backup or .env.old files on the server before making changes. If your web server is misconfigured (or if a vulnerability allows file reading), these backup files may be accessible via HTTP. Unlike .env, which Nginx is typically configured to block, .env.backup may not be covered by your deny rules.
Never create .env backup files on the server. Use your deployment platform to manage environment variable history instead.
The Multi-Environment Strategy
A typical Laravel project has at least three environments: local development, staging, and production. Each needs different configuration, and managing these differences is where most teams struggle.
What Should Differ Between Environments
Variable
Local
Staging
Production
APP_ENV
local
staging
production
APP_DEBUG
true
true
false
APP_URL
DB_HOST
127.0.0.1
staging-db-host
production-db-host
DB_PASSWORD
secret
unique-staging-pw
unique-production-pw
MAIL_MAILER
log
smtp (Mailtrap)
smtp (SES/Postmark)
QUEUE_CONNECTION
sync
redis
redis
LOG_CHANNEL
stack
stack
stack
LOG_LEVEL
debug
debug
warning
Notice that staging mirrors production in many ways (same queue driver, similar database setup) but uses different credentials and may use test/sandbox versions of third-party services.
The .env.example Contract
Your .env.example file is a contract between your application and its operators. Every environment variable your application needs should be listed here, with sensible defaults where appropriate and clear comments for variables that require specific values.
# Application
APP_NAME="My Laravel App"
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
# Database
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=my_app
DB_USERNAME=root
DB_PASSWORD=
# Third-Party Services
# Get your API key from https://dashboard.service.com
THIRD_PARTY_API_KEY=
THIRD_PARTY_WEBHOOK_SECRET=
When you add a new environment variable to your application, add it to .env.example in the same commit. This ensures that anyone deploying the updated code knows they need to set the new variable.
How Deploynix Handles Environment Variables
Deploynix provides a dedicated interface for managing environment variables on each site. Here is how it addresses the common challenges:
Encrypted Storage
Environment variables entered through the Deploynix dashboard or API are encrypted at rest. They are never stored in plain text in the platform's database, and they are transmitted to your servers over encrypted SSH connections.
Per-Site Configuration
Each site on a server has its own set of environment variables. This means you can run staging and production on different servers (as you should) with completely independent configurations, all managed from a single dashboard.
Edit and Deploy Workflow
When you update environment variables through Deploynix, the changes are synced immediately to the .env file on your server. For Laravel to pick up the changes, you can either trigger a full deployment (which clears and rebuilds the config cache) or use the "Reload Config" button in the site dashboard, which runs php artisan config:cache without a full redeploy.
API Access
For teams that prefer infrastructure-as-code, Deploynix's API (authenticated with Sanctum tokens) allows you to manage environment variables programmatically. This is useful for:
- Setting environment variables as part of a CI/CD pipeline
- Rotating secrets automatically
- Syncing non-sensitive configuration across environments
Advanced Strategies
Environment-Specific Config Files
Laravel allows you to create environment-specific configuration. While .env handles the basics, some teams create config files that vary behavior based on APP_ENV:
// config/logging.php
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => app()->environment('production')
? ['daily', 'slack']
: ['daily'],
],
],
This keeps environment-specific logic in version-controlled config files rather than requiring different .env values.
Feature Flags via Environment Variables
Environment variables are a simple way to control feature flags:
FEATURE_NEW_DASHBOARD=true
FEATURE_BETA_API=false
// config/features.php
return [
'new_dashboard' => env('FEATURE_NEW_DASHBOARD', false),
'beta_api' => env('FEATURE_BETA_API', false),
];
// In your code
if (config('features.new_dashboard')) {
// Show new dashboard
}
This approach lets you enable features per-environment without code changes. Enable on staging first, verify it works, then enable on production by updating the environment variable and redeploying.
Secret Rotation Without Downtime
When rotating secrets (database passwords, API keys), the concern is always downtime during the transition. Here is a safe rotation pattern:
- For database passwords: Create a new database user with the new password. Update the
.envon Deploynix. Deploy with zero-downtime deployment (the new process uses the new credentials while the old process finishes its requests with the old credentials). Once the deployment is complete, remove the old database user. - For API keys: Many services allow multiple active API keys. Generate a new key, update
.env, deploy, then revoke the old key. - For APP_KEY: This is the most sensitive rotation because it affects encrypted data. Laravel's
php artisan key:generatecan be combined with theAPP_PREVIOUS_KEYSenvironment variable to support graceful key rotation without immediately invalidating all encrypted data.
Validation at Boot Time
Add validation to your application that checks for required environment variables at boot time rather than failing at the point of use:
// In a service provider boot() method
$required = ['APP_KEY', 'DB_HOST', 'DB_DATABASE', 'MAIL_MAILER'];
foreach ($required as $var) {
if (empty(config(strtolower(str_replace('_', '.', $var))))) {
throw new RuntimeException("Required environment configuration is missing: {$var}");
}
}
This gives you an immediate, clear error message during deployment rather than a cryptic failure when a user hits the code path that needs the missing variable.
The .env Audit Checklist
Before every production deployment, verify:
-
APP_ENVis set toproduction -
APP_DEBUGis set tofalse -
APP_KEYis set and unique to this environment -
APP_URLmatches the actual production URL (including scheme) - Database credentials are unique to this environment
- Mail is configured for a production mail service (not
logorarray) - Queue connection is set to a persistent driver (not
sync) - Session driver is set appropriately (not
fileon load-balanced setups) - Cache driver is set to a persistent store
- All third-party API keys are production keys (not sandbox/test)
- No
.env.backupor.env.oldfiles exist on the server
Conclusion
Environment variables are deceptively simple. The mechanics of reading a value from a .env file are trivial, but managing those values securely and consistently across multiple environments is a discipline that requires thought and tooling.
The core principles are straightforward: never commit secrets to version control, never share credentials across environments, always use config() instead of env() in application code, and use a deployment platform that encrypts your secrets and provides a clear workflow for updates.
Deploynix handles the infrastructure side of environment variable management — encrypted storage, per-site configuration, and API access for automation. Your job is to maintain a clean .env.example, rotate secrets regularly, and verify your production configuration before every deployment. Get these habits right, and the .env file becomes what it was always meant to be: a clean separation between your code and its configuration.
Top comments (0)