Guides / Exposed .env and .git files
Is your .env or .git folder publicly reachable? How to check your deployed site and block it
If your host serves your whole project folder instead of just the build output, files like .env and the .git folder can end up at public URLs. Anyone who requests /.env then gets your keys, and anyone who requests /.git/ files can start rebuilding your source code. It takes two minutes to check.
The short answer
Request https://yoursite.com/.env and https://yoursite.com/.git/HEAD. If either one returns your real file instead of a 404, 403 or your app's normal page, that file is public. Fix it in this order:
- Rotate every secret that was in the exposed file. Assume someone already has it. Blocking the URL doesn't take a key back.
- Deploy only the build output (like
dist/orbuild/), never the project root, and keep.envfiles out ofpublic/. - Block dotfiles at the server as a backstop, so a future mistake returns a 403 or 404.
An exposed .git folder is worse than it sounds. It holds your repo's object store, so someone can rebuild your source and its history. That includes secrets you deleted in a later commit.
How it happens
- A file in
public/. Vite's docs say everything in thepublicdirectory is copied to the root ofdistas-is. A.envor backup file dropped in there goes live with the site. - Serving the project root. A static site deployed with no build step, an Nginx or Apache root pointed at the repo, or an
express.static('.')line all serve whatever is in the folder. - Assuming the host filters it. Vercel's docs list files it skips by default when you deploy with the Vercel CLI, including
.git,.env.localand.env.*.local. Plain.envand.env.productionaren't on that list.
How to check
Only test sites you own. These commands print status codes and yes/no answers, never file contents, so nothing sensitive ends up in your terminal history or a screenshot.
1. Check the status codes
SITE="https://yoursite.com"
for p in /.env /.env.local /.env.production /.git/HEAD /.git/config; do
printf '%-18s %s\n' "$p" "$(curl -s -o /dev/null -w '%{http_code}' "$SITE$p")"
done
A 404 or 403 is what you want. A 200 needs a second look, because many single-page apps answer every unknown path with index.html and a 200.
2. Tell a real file from your app's fallback page
SITE="https://yoursite.com" curl -s "$SITE/.git/HEAD" | head -c 200 | grep -q '^ref: ' \ && echo ".git/HEAD: EXPOSED" || echo ".git/HEAD: not a git file" curl -s "$SITE/.env" | head -c 2000 | grep -qE '^[A-Za-z_][A-Za-z0-9_]*=' \ && echo ".env: looks like a real env file, EXPOSED" || echo ".env: not an env file"
A real .git/HEAD starts with ref: , and a real .env has NAME=value lines. Your HTML fallback page has neither. Run the same check for .env.local and .env.production if step 1 gave them a 200.
3. Look at what you actually deploy
Build locally, then list the dotfiles in the output folder. Use your framework's output folder name:
npm run build find dist -name '.*' -print
Anything this prints will be uploaded and served. Also check your host's settings: the output or publish directory should be the build folder, not . or the repo root.
How to fix it
Rotate first
If either check came back exposed, treat every value in that file as leaked, and every secret ever committed if .git was public. Create new keys, update your host's environment variables, redeploy, then revoke the old ones. Our guide on finding and rotating exposed API keys walks through the order so your app doesn't go down in between.
Deploy only the build output
Move any .env or backup files out of public/. On Vercel, set the Output Directory in Project Settings, or pin it in vercel.json:
{
"outputDirectory": "dist"
}
For projects without a build step, a .vercelignore file keeps files from being uploaded at all. Vercel's docs say files listed there aren't uploaded, deployed or served:
# .vercelignore .env .env.*
On Netlify, set the publish directory to your build folder (dist, build and so on) rather than the base directory.
Block dotfiles at the server as a backstop
Netlify. Redirect rules don't apply when a real file exists at the path unless you add ! to force them. In _redirects:
/.env /404.html 404! /.env.local /404.html 404! /.env.production /404.html 404! /.git/* /404.html 404!
Nginx. Deny every path that starts with a dot, except /.well-known, which things like certificate renewal need:
location ~ /\.(?!well-known) {
deny all;
}
Apache (2.4 or later):
<FilesMatch "^\.">
Require all denied
</FilesMatch>
RedirectMatch 404 /\.git(/|$)
FilesMatch covers files whose names start with a dot, like .env. The RedirectMatch line (from mod_alias) covers files inside the .git folder, whose own names don't.
Express. express.static already ignores dotfiles by default, so don't set dotfiles: 'allow'. Serve a dedicated folder rather than the project root:
app.use(express.static('public', { dotfiles: 'deny' }))
Then rerun the checks above. Every path should come back 404, 403 or "not a ... file".
The same "the build shipped more than it should" problem shows up with JavaScript source maps. See source maps exposed in production for that check.
next step
Check your live app for free
Our free Leak Checker looks at your deployed app the way any visitor's browser does and flags exposed secret keys, public source maps and missing security headers. Run it after you've rotated your keys to confirm nothing secret is still in what the browser downloads.
If you'd rather have the whole app looked at, the $99 Scan + Report covers your repo and live app, including leaked keys, deploy settings, database access rules and auth checks.
Sources
Every product step above was checked against these official pages. Last checked Oct 2026. Products change; if a menu moved, the docs below are the source of truth.
- Vite: The public directory (copied to the root of dist as-is)
- Vercel: Build features, ignored files and folders
- Vercel: Exclude files from deployments with .vercelignore
- Vercel: vercel.json outputDirectory
- Netlify: Redirect options (404 status, forced redirects and shadowing)
- Nginx: ngx_http_access_module (deny)
- Apache HTTP Server 2.4: mod_authz_core (Require all denied)
- Apache HTTP Server 2.4: mod_alias (RedirectMatch with a non-3xx status)
- Express: serve-static dotfiles option
- Git: gitrepository-layout (HEAD, config, objects)
- OWASP WSTG: Review old backup and unreferenced files for sensitive information