Your .env file is exposed. Here’s what to do now.
If your .env, a wp-config.php backup or .git/config can be downloaded, assume the secrets in it are already in someone else’s hands. Bots request /.env on millions of sites a day. Work through these steps in order — the first two take minutes.
- Block the file
- Rotate every secret in it
- Check whether it was downloaded
- Move secrets out of the web root
- Scan again
1. Block the file now
Stop new downloads first. It is one config change and a reload. These rules cover every .env, backup and dot-folder path DotenvScan checks (.git, .svn, .aws, .DS_Store), and leave /.well-known/ alone so HTTPS certificates keep renewing.
nginx
Inside the server { } block, above any location ~ \.php$ block — nginx uses the first regex location that matches.
# Dotfiles (.env, .git/…), but keep /.well-known/ for certificates
location ~ /\.(?!well-known(?:/|$)) {
deny all;
}
# Backup and editor copies (.env.bak, wp-config.php.bak, …)
location ~* \.(?:bak|old|orig|save|swp)$ {
deny all;
}
sudo nginx -t && sudo systemctl reload nginx
Apache
In the virtual host or in .htaccess (Apache 2.4). A virtual-host change needs a reload; .htaccess applies at once.
# Dotfiles (.env, .DS_Store …) and backup/editor copies
<FilesMatch "(^\.|\.(bak|old|orig|save|swp)$)">
Require all denied
</FilesMatch>
# Dot-folders (.git, .svn, .aws …), but not /.well-known/
RedirectMatch 404 /\.(?!well-known/)[^/]+/
sudo apachectl configtest && sudo systemctl reload apache2 # httpd on RHEL-family systems
Caddy
@secrets path */.env* */.git/* */.svn/* */.aws/* */.DS_Store *.bak *.old *.orig *.save *.swp
respond @secrets 404
sudo systemctl reload caddy
Two of the checks aren’t dotfiles: a phpinfo page should be deleted, and a public laravel.log means the document root is wrong. Then run DotenvScan again: every path should answer 403 or 404.
2. Rotate every secret in the file
Blocking the file doesn’t un-leak it. Change each credential at its source, deploy the new value, then revoke the old one:
- Database passwords. Change the database user’s password, and limit that user to connections from your app’s host if you can.
- Cloud keys (AWS, Google Cloud, Azure). Create a new key, deploy it, then deactivate and delete the old one. Check the provider’s audit log (AWS CloudTrail, for example) for activity you don’t recognise.
- Payment, email and SMS keys (Stripe, SendGrid, Mailgun, Twilio…). Roll them in each provider’s dashboard.
- App secret and signing keys (
APP_KEY,SECRET_KEY, JWT and session secrets). Rotating them signs everyone out. On Laravel, a leaked key must not stay inAPP_PREVIOUS_KEYSfor long — see rotating APP_KEY safely. - WordPress keys and salts. Replace all eight in
wp-config.phpwith fresh values from the WPSalt salts generator, then work through the wp-config backup guide. - Everything else in the file — OAuth client secrets, webhook signing secrets, SMTP passwords.
If .git/config was exposed, the whole repository can usually be rebuilt from the open .git directory. Treat every secret ever committed, even in old commits, as leaked — the exposed .git guide covers it.
3. Check whether it was downloaded
Your access logs show whether anyone fetched the file. A 200 response means they got it. zgrep reads current and rotated (.gz) logs alike:
sudo zgrep -hE '"GET /[^ "]*(\.env|\.git/|\.bak|\.old|\.save)[^ "]* HTTP/[0-9.]+" 200 ' /var/log/nginx/access.log*
For Apache, use /var/log/apache2/access.log* (or /var/log/httpd/access_log*). DotenvScan’s own requests show the user agent DotenvScan/1.0. Any other hit means the file was taken: finish step 2 first, then look for unfamiliar activity in the services those keys unlock.
4. Move secrets out of the web root
Blocking rules are a safety net. The real fix is that the file never sits in a public directory:
- Point the document root at your app’s public folder (
public/for Laravel and Symfony), not the project root — how, for Laravel. - Keep
.envabove the document root, or hand secrets to the process instead — systemdEnvironmentFile=, Docker secrets, or a secret manager. - Don’t edit config in place on the server: editors and quick
cpbackups leave.save,.bakand.oldcopies behind. Find strays withfind /var/www -type f \( -name '.env*' -o -name '*.bak' -o -name '*.old' -o -name '*.save' \). - Never deploy the
.gitdirectory into the web root. Deploy a build, or keep the checkout outside the document root.
5. Scan again, and check the whole server
Run DotenvScan again to confirm every path now answers 403 or 404. One exposed file usually means others: to find every secret file on a server — all sites, accounts and backup copies — run ServerSecretVault on the server itself.