Guides / Exposed .env and .git files

last checked oct 2026

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:

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

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.