How your .env file ends up public
The three ways a file full of secrets becomes readable at yoursite.com/.env — git, your deploy, or a public env prefix — and the habits that prevent it.
The .env file is where your app keeps its secrets: database passwords, API keys, tokens. It's supposed to live on your machine and your server and nowhere else. Yet "visit yoursite.com/.env and read someone's production secrets" is a real, repeatable thing that works on a surprising number of live sites — including apps built by people who did nothing obviously wrong.
If you understand the three ways it happens, you can make sure it never happens to you. All three come down to a .env file ending up somewhere the public web can reach it.
The short version: a .env file becomes public one of three ways — it gets committed to git and pushed to a public repo, it ships inside your deployed files so the host serves it at yoursite.com/.env, or a secret gets placed in a public env prefix like NEXT_PUBLIC_ and bundled into the frontend. All three are preventable with a few habits.
Failure 1: it got committed to git
.env is meant to be excluded from git by a .gitignore entry. When that entry is missing — or was added after the file was already committed — the file goes into your repository. Push that repo to a public GitHub and your secrets are now public, permanently: even if you delete the file in a later commit, it stays in history, and bots scrape public commits for exactly this.
Why AI-built projects hit this: a fresh project scaffolded quickly may not have a .gitignore yet, or the .env may get created and committed in the same rush as everything else. And once a file is tracked by git, adding it to .gitignore later does nothing — git keeps tracking files it already knows about.
The fix:
- Make sure
.envis in.gitignorebefore your first commit. Rungit status— if.envshows up as a change to be committed, it is not being ignored. - If it's already tracked:
git rm --cached .env, commit that, and then the.gitignoreentry takes effect. But if it was ever pushed publicly, also rotate every key in it — the history exposure already happened. (Rotation runbook.)
Failure 2: it shipped inside your deployed files
This is the sneaky one, because git is completely innocent. Some deploy setups upload your whole project directory to the web host — and if .env is sitting in that directory, it gets served as a static file at yoursite.com/.env. No commit required; the file just rode along with everything else you uploaded.
Why it happens: static hosts and simple deploy scripts often serve a folder as-is. If your build output and your source files (including .env) live in the same folder, and that folder is the web root, every file in it — dotfiles included — is a URL.
The fix: deploy only your build output, not your whole project folder. Modern platforms (Vercel, Netlify, and framework build pipelines) do this correctly by default — they publish a dist/.next/build directory that never contains your .env. If you're manually uploading files, make sure your web root is the build output, not the project root. And the definitive test costs five seconds: visit yoursite.com/.env — you want a 404.
Failure 3: the secret was never really in .env at all
Sometimes the .env file behaves perfectly and the secret still ends up public — because it was also placed in a variable that ships to the browser. A value assigned to NEXT_PUBLIC_SECRET, VITE_SECRET or REACT_APP_SECRET is deliberately bundled into the frontend by the framework. The .env file kept its secret; the public env-var prefix handed it out anyway.
The fix: anything with a PUBLIC / NEXT_PUBLIC_ / VITE_ / REACT_APP_ prefix is, by design, visible to every visitor. Only put genuinely public values there (a publishable Stripe key, a Supabase anon key). Real secrets get an un-prefixed name and are read only in server-side code. (The Supabase version of this mistake is worth a read — it's the most common one.)
How to know you're safe
Two of these you can check in seconds — git status for tracked files, and visiting /.env on your live site. The third (a secret leaking via a public prefix) is harder to eyeball, because you'd have to read your own JavaScript bundle the way a stranger would.
That's the check OpzyAI's free scan automates: it fetches your live site, reads the bundle and probes for an exposed .env (and .git, source maps, and leaked keys) the way any visitor could — plain-language results, about 15 seconds, no account. Passive by design: it only reads what your site already serves publicly, and never attacks anything.
The reassuring part: unlike deep application security, this failure class is fully preventable with three habits — .env gitignored before the first commit, deploy the build output not the project folder, and never put a real secret behind a public env prefix. Get those right and yoursite.com/.env stays a 404 forever.
Frequently asked
- How does a .env file end up publicly accessible?
- Three ways: it gets committed to git (and pushed to a public repo), it ships inside your deployed files so the host serves it at yoursite.com/.env, or a secret is placed in a public env prefix like NEXT_PUBLIC_ and bundled into the frontend. All three make secrets readable by anyone.
- How do I check if my .env file is exposed?
- Visit yoursite.com/.env in your browser — you should get a 404. If you see your environment variables, rotate every key in the file immediately and fix your hosting so the web root serves your build output, not your whole project folder.
- Does adding .env to .gitignore protect a file already committed?
- No. Git keeps tracking files it already knows about, so .gitignore only affects untracked files. If .env was already committed, run git rm --cached .env, commit that, and then the ignore rule applies — and rotate the keys if it was ever pushed publicly.