The .env File Your Web Server Is Serving to the Internet
One file. Every production secret. Accessible to anyone who types /.env in a browser.
17
Apps with exposed .env
12,000+
Apps scanned
Every secret
In one file
What a .env file contains
The .env file is typically the single file in a project that contains every production secret. Not one key. All of them.
A typical .env for a web application looks something like this:
DATABASE_URL=postgres://admin:••••••••@db.example.com:5432/prod STRIPE_SECRET_KEY=sk_live_•••••••••••••••••••••••• SUPABASE_SERVICE_ROLE_KEY=eyJhbGci•••••••••••••• AWS_SECRET_ACCESS_KEY=•••••••••••••••••••• RESEND_API_KEY=re_•••••••••••••••• NEXTAUTH_SECRET=•••••••••••••••••••••••• GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n••••" OAUTH_CLIENT_SECRET=••••••••••••••••
Database passwords, payment processor keys, signing secrets, OAuth credentials, third-party API tokens. Everything an attacker needs to take over every service the application depends on. In one HTTP request.
Why this is worse than a single leaked key
When a single API key leaks in a frontend bundle or a GitHub commit, the damage is scoped to that one service. A leaked Stripe key compromises payments. A leaked database connection string compromises the database. Each one is bad, but contained.
An exposed .env file is not one leak. It is every leak, simultaneously. The attacker does not need to scan JavaScript bundles, search commit history, or probe endpoints. They type yourdomain.com/.env in a browser and get back a plaintext file containing every credential the application uses. No authentication required. No rate limiting. No detection.
Unlike a secret committed to a GitHub repository (which requires scanning commit history and may be buried in an old diff), an exposed .env is immediately accessible to anyone who knows to ask for it. And automated scanners know to ask for it. They have been requesting /.env on every new domain they encounter for years.
How it happens
The .env file lives in the project root. Web servers serve static files from a directory. When those two directories are the same, the .env file becomes a static asset.
There are three common ways this happens:
01
Static host without dotfile exclusion
Platforms that serve files from a directory (Netlify, Vercel static exports, S3 buckets, GitHub Pages with custom builds) will serve any file in the output directory. If .env ends up in the build output, it gets served. Most frameworks exclude it during build. Some do not. If you copy your project wholesale into the served directory, .env comes along.
02
Misconfigured nginx or Apache document root
Setting the document root to the project root instead of a public/ or dist/ subdirectory makes every file in the project accessible over HTTP. This includes .env, package.json, node_modules, and anything else at the top level. It is one of the most common server misconfigurations.
03
Docker COPY without exclusion
A Dockerfile with COPY . /app copies everything in the build context into the image, including .env. If the served directory is /app or a parent of it, the .env file is reachable. Without a .dockerignore that explicitly excludes .env, it ships with every build.
In all three cases, the developer did not intend to expose the file. The deployment pipeline simply did not exclude it, and the web server did what web servers do: serve files from the directory it was pointed at.
How to check
This takes thirty seconds. Open a terminal and run:
curl -s -o /dev/null -w "%{http_code}" https://yourdomain.com/.envIf the response is 403 or 404, the file is not being served. If the response is 200, your .env file is publicly accessible and every secret in it should be considered compromised.
Also check common variations: /.env.local, /.env.production, /.env.backup. Attackers check all of them.
How to fix it
If your .env is currently accessible, the first step is rotating every secret in the file. Not later. Now. The file has been accessible since deployment, and automated scanners have likely already retrieved it.
After rotation, prevent it from happening again:
01
Deny dotfiles in your web server config
In nginx: location ~ /\. { deny all; }. In Apache: RedirectMatch 403 /\..*$. This blocks requests for .env, .git, .htaccess, and any other dotfile. It is the single most effective fix.
02
Add .env to .dockerignore
If you use Docker, create or update .dockerignore with .env, .env.*, and .env.local. This prevents the file from being copied into the image regardless of what COPY commands your Dockerfile uses.
03
Set the document root correctly
Point your web server at the build output directory (public/, dist/, .next/static/), not the project root. The project root contains configuration files, dependency directories, and secrets that should never be served.
04
Verify in your deployment pipeline
Add a post-deploy check that requests /.env from your production URL and asserts a non-200 response. If it ever returns 200, the deploy should fail. This catches regressions before they reach production.
The takeaway
Of the 12,000+ recently launched apps Talon scanned, 17 were serving their .env file over HTTP. That is a small percentage. But for those 17 apps, every production secret was readable by anyone on the internet. Database credentials, payment keys, signing secrets, OAuth tokens. All of it, in plaintext, in a single GET request.
The fix is a one-line web server rule and a thirty-second verification. The cost of not doing it is full compromise of every service your application touches.
Run a free surface scan
Talon checks your public GitHub repositories for committed secrets, your frontend for exposed API keys, and your server for accessible credential files. Passive, read-only, no account required.