
exposed .env in web root leaks every secret you own
curl -s -o /dev/null -w '%{http_code}' https://site/.envthe setup: one url, zero exploit
you don't need to be a hacker to find a .env file. you just need to know it might be there and try the url. that's it. that's the whole "attack." someone types https://site/.env into a browser or runs a one line curl command, and if the file is sitting in the web root, the server hands it right over. no injection, no exploit, no cleverness. just a webserver doing exactly what it was configured to do, which is serve files.
the scary part isn't the technique. it's what's usually inside that file: database passwords, live stripe keys, aws secrets, mail credentials, sometimes literally every key your app uses to talk to the outside world. one request, entire keyring.
why this happens
most frameworks (laravel, node apps, django projects using python-dotenv, whatever) keep a .env file at the project root for convenience during development. the problem is "project root" and "web root" are often the same folder once it's deployed. the webserver is told to serve everything in that directory, and .env doesn't get special treatment unless you tell it to.
same story for .git folders, .htpasswd files, and editor backup files like index.php~ or settings.py.bak. if it's dotfile or backup shaped and it's inside the served directory, assume it's reachable until you prove otherwise.
checking your own exposure
here's the command from the demo, broken down piece by piece:
curl -s -o /dev/null -w '%{http_code}' https://site/.env
curl -s makes the request quietly, no progress bar noise. -o /dev/null throws away the actual body of the response because you don't need to see the file, you just need to know if it exists. -w '%{http_code}' prints only the http status code the server returned.
a 200 means the file is sitting there in the open. a 403 means it exists but something is blocking access, which is better but still tells an attacker the file is present. a 404 is what you want, it means as far as the outside world knows, that file doesn't exist.
run this against your own domains for .env, .git/config, .htpasswd, and any backup file naming pattern your editor tends to leave behind.
the fix: get it out of the web root
the real fix isn't a permissions tweak, it's moving the secret somewhere the webserver can't reach at all:
mv /var/www/app/.env /etc/app/.env
chmod 600 /etc/app/.env && chown www-data /etc/app/.env
the mv takes the file out of the served directory entirely, so there's no url that maps to it anymore. chmod 600 means only the file's owner can read or write it, nobody else on the system gets a peek. chown www-data makes sure the user your app actually runs as owns it, so your app still starts up fine, it just points its config loader at the new path instead of assuming .env lives next to the code.
belt and suspenders: block the whole dotfile family
moving one file is good. blocking the entire category is better, because you will forget a file eventually. in nginx:
location ~ /\. { deny all; return 404; }
this matches any request path containing a dot-prefixed segment, meaning .env, .git, .htpasswd, .htaccess, all of it, and returns a flat 404 instead of serving the file. apache users want the equivalent in a Files block matching filenames starting with a dot. either way, the goal is the same: no dotfile should ever be servable, on principle, regardless of what ends up in your project directory later.
after you make the change, re-run the verify command and confirm you get a 404 instead of a 200.
if it was already exposed: rotate everything
finding and fixing the leak doesn't undo the leak. if that .env was ever publicly reachable, even briefly, treat every credential in it as burned. rotate the database password, revoke and reissue the stripe key, rotate the aws secret access key, reset mail credentials. scanners crawl the internet constantly looking for exactly this file, so "probably nobody found it" is not a security posture.
the takeaway
this isn't a hacking story, it's a configuration story. your secrets should never live in a folder your webserver is willing to hand out. move config files outside the web root, block dotfiles at the server level, and verify with a plain curl request that returns a 404. then check your own sites today, because the same one line request that "no exploit" got someone else's secrets works just as well in reverse, to prove yours are locked down.