/.env Web access log Triage: medium

GET /.env Requests in Your Logs: Scanners Hunting for App Secrets

Published

An automated scan for application .env files, which hold database passwords, API keys and other secrets in plain text whenever the web server hands one back.

What it looks like

203.0.113.7 - - [18/Oct/2026:03:51:11 +0000] "GET /.env HTTP/1.1" 404 162 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
198.51.100.42 - - [18/Oct/2026:03:51:12 +0000] "GET /.env.production HTTP/1.1" 404 162 "-" "python-requests/2.31.0"
192.0.2.88 - - [18/Oct/2026:04:02:44 +0000] "GET /api/.env HTTP/1.1" 404 162 "-" "Go-http-client/2.0"

On our own server, the 30 days to 18 October 2026

13,471

requests · about 449 a day

128

source addresses

Most requested variantsRequests
/.env936
/.env.production130
/.env.bak128
/.env.local123
/.env.backup120
/.env.save116
/api/.env110
/app/.env105

LogTriage's production API server (Hetzner, Germany): port 22 open to the internet, key-only since 6 October 2026 (password and keyboard-interactive authentication off); nginx in front of the API on ports 80 and 443. Paths as requested, query strings dropped and identifiers redacted; counted weekly from the nginx access log. More in the monthly attack report.

What the request is

/.env is the environment file convention used by the dotenv family of libraries across PHP, Node.js, Python and Ruby applications. The file holds configuration that the code reads at startup — database host and password, API keys and tokens, mail credentials, framework signing keys, sometimes cloud access keys — as plain KEY=value lines with no encryption and no access control of its own.

The file is only safe because nothing should serve it. When an application is deployed with its project root as the web root, or when a dotfile rule is missing from one vhost, the web server treats .env as an ordinary static file and returns its contents in the response body. There is no exploit involved: a single GET retrieves the credentials.

That is why scanners ask for far more than /.env. They walk a list of likely copies and likely locations — environment-specific files such as .env.production and .env.local, editor and deployment leftovers such as .env.bak, .env.save and .env.backup, and the same names one directory down under paths like /api/ or /app/ where a framework often lives. Each variant is one cheap request, and only one of them has to be reachable.

Who sends it

This is undirected, internet-wide scanning. The senders are mass scanners sweeping address ranges, credential-harvesting botnets that feed any recovered keys straight into cloud or mail abuse, and research crawlers that catalogue exposures. Nothing about a request to /.env implies that your organisation was chosen.

The user agent is not evidence of much. Some clients announce themselves as HTTP libraries such as python-requests or Go-http-client; many copy a current Chrome string. What distinguishes the traffic is shape rather than identity: a burst of requests from one address in a few seconds, no Referer, no matching requests for CSS, JavaScript or images, and a path list that moves on to other well-known files when .env returns 404.

Is your server exposed?

Check what your own logs already answered, then check the live path from the host itself:

# Every .env-style path in the log, with the status code it received
awk '$7 ~ /\.env/ {print $9, $7}' /var/log/nginx/access.log \
  | sort | uniq -c | sort -rn | head -20

# Anything that was NOT a 404/403 — this is the list that matters
awk '$7 ~ /\.env/ && $9 !~ /^(404|403)$/ {print}' /var/log/nginx/access.log

# Ask your own server, from your own server
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/.env

# Does an env file sit inside a document root at all?
find /var/www /srv -maxdepth 4 -name '.env*' -type f 2>/dev/null

Repeat the curl check against each vhost name you serve, not just the default one — a dotfile rule added to the main site and forgotten on a staging or admin vhost is the usual way these files stay reachable. Rotated and compressed logs are worth the same awk pass, since an exposure may have been fixed after the fact.

How to block it

The durable fix is to keep the file out of the served tree: point root at the application’s public directory rather than the project directory, so .env has no URL at all. Add a dotfile rule as a second layer, in every server block:

# ACME and other well-known URIs must stay reachable
location ^~ /.well-known/ {
    allow all;
}

# Everything else beginning with a dot: not found, not logged
location ~ /\. {
    access_log off;
    log_not_found off;
    return 404;
}

The ^~ prefix location is evaluated ahead of the regular-expression location, so /.well-known/acme-challenge/... keeps working while /.env, /.env.bak and /.git/config are answered 404. Returning 404 rather than 403 also tells the scanner nothing about which paths exist.

If a .env was ever served with a 200, the block is not the end of the work: treat every value in that file as disclosed and rotate it.

Probes that arrive with it

Detect it

MITRE ATT&CK: T1595.003T1552.001

Frequently Asked Questions

We answer every one of these with 404 — is there anything to do?
Not as an incident. A 404 means nothing was disclosed, and the traffic is background scanning that will not stop. The work worth doing is preventive: confirm no .env file sits under any document root, confirm the dotfile rule is in every server block including staging and admin vhosts, and keep an alert on non-404 responses to these paths rather than on the requests themselves.
Which response codes should actually page someone?
Any 200 or 206 to a path matching .env is a credential disclosure until proven otherwise — treat it as an incident, pull the bytes served from the response size, and rotate everything the file could have contained. A 301 or 302 is worth one look to see where it sends the client, and 403 is fine as long as it is a deny, not an error page that echoes a path.
The requests come from a hundred different addresses. Should I block them all?
Blocking individual scanner IPs has little lasting value because the source pool rotates constantly and this traffic is undirected. Fix the exposure instead, then, if the volume costs you resources, drop the requests cheaply at the edge with a dotfile location rule and turn off logging for it rather than maintaining an IP list.
We found an .env that really was reachable. What is the clean-up?
Assume every secret in the file is public. Rotate database passwords, API keys, mail credentials, signing keys and cloud access keys, then check the usage logs for each of those services around and after the first successful request. Move the file outside the document root and search the access log for other 200s from the same addresses to see what else they reached.

See what else is probing your server

Drop an nginx or Apache access log into the free browser analyzer: secret-file sweeps, exploit probes, scanners and credential stuffing are grouped by source with the evidence lines, and every known probe links back here. Nothing is uploaded.