Security guide

How to Find Exposed .env Files on Your Website

A .env file exposed at https://yourdomain/.env is a complete credential breach. Database passwords, API keys, Stripe secrets, cloud storage credentials — all in one file, readable by anyone. Here are the four ways .env files become publicly accessible and exactly how to check yours.

The 4 exposure vectors

A .env file ends up publicly accessible through one of four mechanisms. Most incidents involve the first two.

1. Misconfigured web server root

The most common cause: your application directory and your nginx or Apache document root point at the same folder. In a Node.js project, that often means the folder containing package.json, .env, and src/ is also the folder nginx reads files from. Any file in that directory is then directly downloadable by anyone who knows the filename.

nginx does not have a built-in concept of "dotfiles are private" — it serves whatever the filesystem exposes unless you tell it otherwise. Apache has similar behavior with directory serving enabled.

The correct fix is to either (a) move your .env file one level above the document root, or (b) add an explicit deny rule for dotfiles:

# nginx — deny all dotfiles
location ~ /. {
    deny all;
    return 404;
}
# Apache — deny .env and similar
<Files ".env*">
    Require all denied
</Files>

2. Deployment scripts that copy secrets into the public directory

Automated deployment scripts — especially quick one-liners and AI-generated pipelines — sometimes copy the entire project directory to the server, including .env. If the target directory is the document root, the file becomes public immediately on deploy. This is particularly common in shared hosting environments where FTP upload, rsync, or git-based deployments put everything in public_html/.

The fix: explicitly exclude .env* from deployment scripts, and confirm the file is absent in the target directory after every deploy.

3. Git history — the file was committed before .gitignore

You added .env to .gitignore — but only after an earlier commit included it. Even if the file is now ignored, its content exists in your git history. If the repository is public, every version of the file that was ever committed is readable with:

git log --all --full-diff -- .env

Removing a file from the working tree and committing the deletion does not remove it from history. Full remediation requires rewriting history with a tool like git filter-repo or BFG Repo Cleaner, and then rotating every secret that was exposed — regardless of whether you believe anyone saw it.

For public repositories, treat a committed .env as a confirmed breach. Rotate all secrets immediately.

4. Directory listing enabled

Apache's Options +Indexes and some nginx configurations enable directory listing — an HTML page showing every file in a folder when no index.html exists. If directory listing is on and .env is in a served directory, it appears in the listing and is one click away from exposure.

Disable directory listing at the server level:

# nginx — no directory listing by default (this is the default, confirm it is not overridden)
autoindex off;

# Apache — disable for all directories
Options -Indexes

How to check your own site

Make a direct request to the common .env paths using curl:

curl -sI https://yourdomain.com/.env
curl -sI https://yourdomain.com/.env.local
curl -sI https://yourdomain.com/.env.production
curl -sI https://yourdomain.com/.env.backup
curl -sI https://yourdomain.com/.git/

Interpret the response:

  • 200 OK — the file is accessible. This is a breach. Act immediately.
  • 403 Forbidden — the file exists on disk but access is blocked. The deny rule is working, but the file is still present in the document root. Consider moving it out entirely.
  • 404 Not Found — correct. The path does not return a readable file.

Common paths to check beyond /.env:

  • /.env.local — common in React/Vite and Next.js projects
  • /.env.development, /.env.production — environment-specific files
  • /.env.backup, /.env.bak, /.env.old — backup copies left by editors or deployment scripts
  • /.git/config — if .git/ is accessible, the full repository may be reconstructable from the object store

A pentes.io scan probes all of these paths automatically as part of the nuclei file-disclosure sweep — you see every exposed secret-file path in the findings report without running the checks manually for each path on each asset.

After fixing: the correct remediation order

The order matters. Most people fix the exposure first and rotate second. That is the wrong sequence.

  1. Rotate every secret in the file immediately. Assume every value in the file was read the moment it was exposed. Database passwords, Stripe secret keys, AWS access keys, Supabase service-role keys — all of them. Do this before anything else.
  2. Apply the nginx/Apache deny rule and deploy it.
  3. Verify that the file now returns 404 with curl -sI.
  4. Audit your server access logs for the period of exposure — look for GET requests to /.env paths from IPs you do not recognise. This tells you whether the file was actually retrieved, not just exposed.
  5. If git history contains the file, rewrite history and force-push (or ask your hosting provider to delete and re-create the repository).

Rotating first matters because fixing the exposure does not undo what was already read. Anyone who downloaded the file before you blocked it still has the old secrets. Only rotation makes those credentials worthless.

AI-built apps and exposed .env files

If you built your app with an AI tool (Lovable, Bolt, v0, Replit), .env exposure is one of the most common findings because the generated deployment configuration often puts the application root and the document root in the same place. Supabase service-role keys in an exposed .env bypass Row Level Security entirely — the service role key is a full-access master key. See the vibe coders guide and the AI-generated code security checklist for the broader surface to check.

See all security guides at the pentes.io guides index.