Why Laravel .env files get exposed, how to serve only the public directory, harden production settings, and rotate APP_KEY and credentials after an incident.
Last updated
Why Laravel .env files leak
A Laravel project keeps its .env in the project root, next to app/, config/ and vendor/. Only the public/ directory is meant to be reachable from the web. The file becomes public when that separation breaks:
the web server's document root points to the project root instead of public/,
the whole project was uploaded into public_html on shared hosting and made to work with a rewrite rule,
a server block without a dotfile rule serves the project directory, for example a forgotten staging vhost.
Because Laravel's .env nearly always contains APP_KEY, database credentials and mail credentials, it is one of the most frequently requested files on the internet.
Serve only the public/ directory
The document root must be the public folder. For Nginx:
Nginxnginx
server {
server_name example.com;
root /var/www/example-app/public;
index index.php;
# Also part of the Nginx configuration in Laravel's deployment docs
location ~ /\.(?!well-known).* {
deny all;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ ^/index\.php(/|$) {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_hide_header X-Powered-By;
}
location ~ \.php$ {
return 404;
}
}
For Apache:
Apacheapache
<VirtualHost *:443>
ServerName example.com
DocumentRoot /var/www/example-app/public
<Directory /var/www/example-app/public>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
Managed platforms such as Laravel Forge, Laravel Cloud or Ploi set the root to public/ for you. Check it anyway if you changed the site configuration by hand. More detail on the rules is in the Nginx and Apache guides.
Shared hosting without a configurable root
If your host only offers public_html:
Upload the project to a directory next topublic_html, for example ~/example-app.
Copy the contents of public/ into public_html.
Edit public_html/index.php so that its require paths point to ../example-app/vendor/autoload.php and ../example-app/bootstrap/app.php.
If you must keep the project inside public_html, add the .htaccess deny rules from the Apache guide and verify them with the check. Treat this as a temporary state.
Detailed error pages can reveal configuration values, environment variables, file paths and source code. Debug mode combined with outdated versions of the Ignition error page was exploited for remote code execution (CVE-2021-3129). Debug mode is for local development only.
Config caching and env()
During deployment, cache the configuration:
Shellbash
php artisan config:cache
After caching, Laravel no longer loads the .env file during requests, and calls to env() outside the files in config/ return null. Read settings through config() in your code. Caching improves performance, but it doesn't protect a file that the web server can still serve. The document root and deny rules remain essential.
Encrypted environment files
Since Laravel 9, you can encrypt the environment file, for example to store it in version control or to ship it through a CI pipeline:
Shellbash
php artisan env:encrypt # creates .env.encrypted and prints the key
php artisan env:decrypt --key=... # or set LARAVEL_ENV_ENCRYPTION_KEY
Keep the decryption key out of the repository, for example in your deployment platform's secret settings. An encrypted file served by mistake is useless without the key. The decrypted .env on the server still needs protection.
Rotating APP_KEY and credentials after an exposure
If the .env file was publicly reachable, block access first, then replace everything it contained:
Database: create a new password for the database user (or a new user), update DB_PASSWORD, and check that the database isn't reachable from the internet.
Mail, queues, storage and third-party APIs: regenerate MAIL_PASSWORD, AWS_*, REDIS_PASSWORD, PUSHER_*, payment keys and every other secret, and revoke the old values at the provider.
APP_KEY: generate a new key.Shellbash
php artisan key:generate --show # prints a new key without writing it
Laravel uses APP_KEY to encrypt cookies, sessions and values encrypted with the Crypt facade or encrypted model casts. Changing it logs all users out and makes previously encrypted data unreadable. Laravel 11 and later accept the old key in APP_PREVIOUS_KEYS so you can re-encrypt stored data. Because an attacker who has the old key could still create payloads the application accepts, remove the old key from APP_PREVIOUS_KEYS as soon as the migration is done.
Apply the changes:Shellbash
php artisan config:cache
php artisan queue:restart # workers keep the old configuration in memory
php artisan octane:reload # only if you use Octane
Review logs for successful requests to /.env and for unusual database or API activity. The incident section of the protection guide lists the commands.
Remediation checklist
The document root is public/ on every environment, including staging
A dotfile deny rule is active in the web server configuration
APP_ENV=production and APP_DEBUG=false in production
php artisan config:cache runs on every deployment, and code reads settings through config()
.env is not in Git. Use .env.example or env:encrypt instead
After an exposure: APP_KEY, database, mail and API credentials were rotated, and old ones revoked