Security6 min read

Is Your .env File Public? How to Check, and What to Rotate First

A .env file served from your domain hands over every credential your app has. One curl command tells you whether yours is reachable, and the order you rotate keys in decides how much damage a leak does.

By UseShipReady

Run curl -s https://yourdomain.com/.env. If the response is lines of NAME=value pairs, your .env file is public and every secret in it should be treated as stolen. Fix the deployment so it serves only build output, then rotate credentials in order of damage: database and cloud keys first, then payment and email keys, then AI provider keys, then signing secrets. A 404, or your normal HTML page, means the file is not served at that path.

A .env file exists to keep secrets out of source code. That only works while the file stays on the server’s disk and out of its web root. The moment a web server is willing to hand it to a browser, it becomes the single most valuable file on your domain: every credential your app uses, in plain text, at a predictable URL.

How does a .env file end up public?

Almost never on purpose. It happens when the thing being served is the project folder rather than the build output. The common routes:

  • Uploading the whole project directory to shared hosting, an S3 bucket or a drag-and-drop static host, instead of the dist or build folder.
  • A server configured to serve the project root, such as express.static('.') or an nginx root pointing at the repository.
  • A .env placed inside public/ because "the app could not find it". Everything in public/ is served as-is by Next.js, Vite and most other frameworks.
  • A Docker image built with COPY . . and a container that serves its working directory.
  • Backup copies such as .env.bak, .env.old or .env.production left beside the original, which a rule written only for .env will not catch.

Automated scanners request /.env on public websites continuously, so assume a reachable file has already been read. There is no grace period between "deployed" and "found".

How do I check if my .env file is exposed?

shell
for f in .env .env.local .env.production .env.development .env.bak .env.old; do
  printf "%-18s %s\n" "$f" "$(curl -s -o /dev/null -w '%{http_code}' "https://yourdomain.com/$f")"
done

A status code alone is not proof either way. Many single-page apps answer every unknown path with 200 and their normal HTML page, which is a soft 404: it looks like a hit but is not one. For any path that returns 200, look at the body. A real exposure looks like DATABASE_URL=postgres://..., not like <!doctype html>.

Check the neighbours too

If .env is served, the deployment is serving your repository, and the repository holds more than one secret. Check /.git/config, /docker-compose.yml, /config.json and any .sql dump in the root. An exposed .git directory is arguably worse than the .env itself, because the full history can be rebuilt from it, including every secret ever committed and later "removed". The same failure exposes AI tool config files like .cursorrules and mcp.json.

I found it. What should I rotate first?

Rotate everything in the file eventually. But you can only do one thing at a time, and the order matters because some keys let an attacker do lasting damage in minutes while others mostly cost you money. Work down this list:

OrderSecretWhy it comes here
1Database URLs and passwords, Supabase secret / service role keysDirect read and write access to all user data, and the ability to delete it.
2Cloud provider keys (AWS, GCP, Azure)Can create infrastructure billed to you and reach other stored data.
3Payment secrets (Stripe secret key, webhook signing secret)Refunds, customer data, and forged webhook events your app will trust.
4Email and messaging keys (Resend, SendGrid, Twilio, Slack)Phishing sent from your verified domain damages your reputation for months.
5AI provider keys (OpenAI, Anthropic, OpenRouter and others)Direct billing abuse; set a spend limit while you rotate.
6Session, JWT and cookie signing secretsLets someone forge sessions. Rotating signs everyone out, so do it last and deliberately.
Rotation order for a leaked .env file

For each one: create the new credential, deploy it, confirm the app works, then revoke the old one. Revoking first takes your own site down. After rotation, review each provider’s audit or usage log for activity since the file became reachable. You rarely know that date exactly, so go back to the deployment that introduced it.

Your own access logs can narrow that window. Search them for requests to /.env that returned 200: the first one tells you when exposure began, and the requesting addresses tell you whether it was read by more than your own testing. Keep the result, because some providers ask for it when you report abuse.

How do I stop .env being served again?

  1. Serve build output only. For a framework app, the host should serve .next, dist or build, never the working tree. This is the actual fix; the rules below are a safety net.
  2. Move the file out of public/ and any other folder the framework serves.
  3. Deny dotfiles at the web server or CDN, with an exception for /.well-known/, which certificate issuance and some standards need.
  4. Add .env* to .gitignore and .dockerignore, keeping only a .env.example with placeholder values.
  5. Prefer your host’s environment variable settings over a file on disk in production.
nginx
# Deny every dotfile except /.well-known/
location ~ /\.(?!well-known) {
    deny all;
    return 404;
}
apache
# .htaccess — deny files beginning with a dot
<FilesMatch "^\.">
    Require all denied
</FilesMatch>
A deny rule for .env protects one file. Serving only build output protects all of them.

Is a .env file inside my frontend bundle the same problem?

Related, but different. A served .env file is a hosting problem. A secret compiled into your JavaScript, typically because a variable carried a NEXT_PUBLIC_ or VITE_ prefix, is a build problem, and no server rule will fix it. Both end the same way, which is why it is worth checking for API keys leaked into your JavaScript bundle straight after this.

How UseShipReady detects an exposed .env file

UseShipReady requests the common .env paths on your origin and confirms a hit by the content’s shape, NAME=value lines, not by status code alone, so a soft 404 is not reported. It rates an exposed environment file critical. The evidence lists the variable names it saw and how many there were, never a value: the report tells you what to rotate without becoming another copy of your secrets.

The same scan looks for the neighbours covered above, including an exposed .git directory, config files containing credentials and SQL database dumps. The production readiness checklist covers the rest of the launch-day ground.

Frequently asked questions

Is it bad if my .env file is publicly accessible?
Yes. It usually contains every credential your application uses, so anyone who reads it can act as your app: query your database, send email from your domain, and spend on your API accounts. Treat every value in it as compromised.
Does deleting the .env file from the server fix the leak?
It stops future reads, but anything already downloaded stays valid until you rotate it. Rotate first or in parallel; deleting the file alone leaves the leaked keys working.
My .env URL returns 200 but shows my homepage. Am I exposed?
Probably not. Single-page apps often answer unknown paths with the normal page and a 200 status. Look at the response body: an exposure shows NAME=value lines, not HTML.
How would anyone find my .env file if I never linked to it?
They do not need a link. Automated scanners request /.env and similar paths on huge numbers of domains, discovering new sites through DNS and certificate transparency logs. A predictable path on a public domain is found by default.
Should .env files be committed to git?
No. Commit a .env.example with placeholder values and keep real values in your host’s environment settings or a secrets manager. A committed .env stays in git history even after you delete it.

Sources

Related reading

Is your site ready to ship?

UseShipReady scans up to ten pages for security, AI exposure, email deliverability, SEO and launch readiness — with a paste-ready fix for each finding. Free, no signup.