Your .env file is public?
Here is the calm runbook.

A .env file is where your app keeps its secrets: database passwords, API keys, signing tokens. If your website hands that file to anyone who asks, this page walks you through the fix, top to bottom. Confirm it, close it, rotate everything, check the logs.

Check your site now (free scan) Jump to the 60-second check

1. Confirm it yourself in 60 seconds

Open a private browser window and type your own address with /.env on the end:

https://yoursite.com/.env

A bad response looks like this: a plain page of NAME=value lines. Things like DATABASE_URL=postgres://..., STRIPE_SECRET_KEY=sk_live_..., OPENAI_API_KEY=sk-.... Some browsers download the file instead of showing it; if the downloaded file opens to lines like that, it counts exactly the same. Anyone on the internet can do what you just did.

A good response looks like this: your app's normal "page not found" screen, or a bare 404 or 403 error. That means the file is either not there or the server refuses to serve it. That is what you want.

While you are at it, try the common variants too: /.env.local, /.env.production, and /.env.bak. Backup copies leak just as often as the original.

The thorough version of this check is our free scanner. It looks at your live site passively, needs no signup, and checks this exposure class along with its close cousins (an exposed .git folder, reachable config and backup files, missing security headers). One caveat worth being honest about: a clean check today does not prove the file was never exposed. That is what step 5 is for.

2. Why bots found it before you did

Nobody targeted you. That is the part owners find hardest to believe, and the most useful to understand.

Automated scanners request the same short list of high-value paths from every website they can reach, and /.env is on every list, because one hit pays for millions of misses. The scale is documented: in a 2024 campaign analyzed by Palo Alto Networks' Unit 42, attackers scanned more than 230 million unique targets hunting for exposed .env files, pulled the .env files of around 110,000 domains, and used cloud keys found inside to break into the owners' accounts and demand ransom.

New sites do not get a grace period. Scanners walk the whole public address space, and many also watch public certificate logs, which announce new HTTPS sites shortly after they go live. If your site is reachable, it is in the rotation.

One calming note before you read your logs: a probe is not a breach. You will almost certainly find requests for /.env that got a 404. That is a bot knocking on a locked door. The only entries that matter are the ones where the door opened. Step 5 shows you how to tell the difference.

3. Close the hole on your host

The root cause is almost always the same: the .env file ended up inside the folder your host publishes. Fix that first, then add the seatbelt.

Vercel

Vercel serves only the contents of your output directory. If /.env is live, the file got into that folder: usually public/, your build output, or a project deployed as a plain static folder with the .env sitting inside it. Delete the file from the published folder, move each value into the project's Environment Variables settings, and redeploy. The new deployment replaces the old content.

Netlify

Netlify uploads only your publish directory; files outside it are never deployed. So an exposed .env means it is inside that directory, often because the publish directory is set to the project root. Move the file out (or set a proper publish directory like dist/), put the values into Site configuration, Environment variables, and redeploy.

Cloudflare Pages

Same story: Pages deploys only your build output directory. Check that setting; if it points at the project root, everything in the root is public, .env included. Fix the output directory, move secrets into the project's environment variable settings in the dashboard, and redeploy.

nginx

If nginx serves your .env, the site's root is pointing at a folder that contains it (usually the project root instead of public/). Fix that if you can. Either way, add this inside the server block as a seatbelt. It blocks every dotfile path (.env, .git, and friends) while leaving .well-known alone, which certificate renewal needs:

location ~ /\.(?!well-known) {
    deny all;
}

Then test and reload:

sudo nginx -t && sudo systemctl reload nginx

deny all answers with a 403. If you would rather not confirm the file exists at all, use return 404; in place of deny all;.

Apache

Apache's default config protects its own .htaccess and .htpasswd files, but not .env. Add this to your server config or an .htaccess file (Apache 2.4 syntax):

<FilesMatch "^\.">
    Require all denied
</FilesMatch>

Then reload Apache. This denies every file whose name starts with a dot. Certificate renewal still works, because the challenge files inside .well-known do not start with a dot themselves. And as with nginx, the deeper fix is pointing DocumentRoot at your public/ folder rather than the project root.

Once the fix is deployed, repeat the 60-second check from step 1. You want that 404 back before you move on.

4. Treat every value in that file as burned

This is the step people skip, and it is the one that actually protects you. You closed the door, but you usually cannot prove nobody walked through it while it was open. So assume everyone did. Every password, key, and token in that file gets rotated. All of them, not just the scary-looking ones.

Rotate in this order:

  1. Database connection strings and cloud provider keys (AWS, Google Cloud, Azure). These do the most damage: your data, your infrastructure, your bill.
  2. Payment and email keys (Stripe, Postmark, and similar). Money and the ability to send mail as you.
  3. AI provider keys (OpenAI, Anthropic, Google). Stolen ones get resold and run up usage fast.
  4. Everything else: auth secrets, JWT signing keys, webhook secrets, third-party tokens. A leaked signing secret means forged logins, so do not leave these for "later".

The step-by-step console instructions live in our leaked API key runbook, with a per-provider rotation table. Two of the highest-stakes cases have their own deep dives: a leaked Anthropic key and an exposed Supabase service role key (that one bypasses your row-level security, treat it as a database breach until proven otherwise).

Do not wait for the provider to save you. Some providers watch public sources for their own key formats and will email you about a leak, and a few may deactivate a key they find. None of them promise it, and a file sitting on your own website is not where they look first. Rotate by hand, then verify: a test call with the old key should be rejected. Most providers answer a dead key with a 401 authentication error; AWS answers a disabled key with a 403-class error instead. Either way, rejected is what you want to see.

Finally, fix the supply line so the new keys do not leak the same way: keep .env in your .gitignore, keep it out of any folder you publish, and give your deploy environment its values through the host's environment variable settings, never as a file in the site.

5. Check who already fetched it

You are looking for one thing: requests for /.env that succeeded. A 404 or 403 is a bot bouncing off a locked door. A 200 with a byte count means the file was served, and you should treat its contents as read.

nginx

Access logs commonly live at /var/log/nginx/access.log (plus rotated .log.1, .gz siblings). Search them: grep "\.env" /var/log/nginx/access.log*. In each line, the number right after the quoted request is the status. 200 means served; check the timestamp and the requesting IP.

Apache

Same search, different path: /var/log/apache2/access.log on Debian and Ubuntu, /var/log/httpd/access_log on Red Hat family systems. Include the rotated files; the exposure may be older than the current log.

Vercel

Open your project's logs in the dashboard (the Observability section) and filter for the request path /.env. Requests for static files show up as edge requests. Log history is limited, so look now rather than next week.

Netlify

Netlify exposes traffic logs for static files only through Log Drains, an Enterprise feature. On other plans you cannot see who fetched a static file. That is not a reason to relax; it is the reason step 4 rotates everything.

Cloudflare Pages

The dashboard gives you aggregated analytics, not a per-request log of static file fetches. Raw HTTP request logs require Cloudflare's log export tooling on higher plans. If you cannot get them, assume the file was read and let the rotation carry the day.

If you find a 200

Note the date. Then check the usage logs of every provider whose key was in the file for activity after that date, using the forensics section of the rotation runbook. And if you find nothing at all? Rotation already made the stolen copy worthless. That is the point of doing it first.

Who wrote this: a security engineer with 20 years in the field who does real responsible-disclosure work on exactly this exposure class, finding publicly reachable .env files and reporting them privately to their owners. The steps above are the same ones those reports recommend. No victim stories here; the owners' details stay private, which is rather the point.

Never find out from a ransom note again

Today you caught this because you went looking (or because something already went wrong). Both are bad alarm systems. Exposures recur: a config change, a new deploy preset, a teammate's shortcut, and the file is back.

Two ways to stay ahead of it. Re-run the free scan whenever you ship something significant. Or let continuous monitoring do it for you: it re-checks your live site around the clock and emails you an alert when something new is exposed, from $49/mo. Plans are on the business page, signup at app.lictor-ai.com/billing.

For the codebase side, the same checks ship in our free, open-source Claude Code plugin. It runs a 48-check audit catalog (exposed env and config files are check number 3) and reports in plain English. Install it with:

/plugin marketplace add Raffa-jarrl/Lictor-AI
/plugin install lictor-security-suite@lictor-ai

And because "trust us" is not evidence: we publish a reproducible benchmark. In a blind run on a 30-case stratified sample of OWASP Benchmark 1.2, the audit scored an F1 of 93.8%. That is a sample, not the full benchmark, and we say so in the results. Building with AI app builders? The Lovable security checklist covers the leaks those tools tend to create, .env exposure included.

Run the free scan → See continuous monitoring

Questions people ask at this point

Is an exposed .env file dangerous?

Yes. It holds your secrets in plain text: database passwords, API keys, signing tokens. If yoursite.com/.env returns those lines, anyone can read them, and automated campaigns hunt for exactly this file. One 2024 operation documented by Unit 42 scanned over 230 million targets for exposed .env files. Treat every value as stolen and rotate.

How do bots find .env files?

Mass scanning, not targeting. Scanners request the same short list of paths, including /.env, from every reachable site, and new sites enter the rotation quickly via address-space sweeps and public certificate logs. Nobody needs to know your site exists. Being online is enough.

Do I need to rotate every key in an exposed .env?

Yes, every value. You usually cannot prove which lines were read, and on managed hosts you often cannot see static file access logs at all. Rotate database strings and cloud keys first, then payment and email keys, then AI keys, then the rest. The per-provider steps are in the rotation runbook.