Remediation Guides

.env File Exposed: How to Fix “Environment Configuration File Detected”

26 September 2026 8 min read

“Environment Configuration File Detected” means a scanner fetched your application’s .env file over HTTP. A .env file exposed this way can hand database passwords and API keys to anyone who asks. Fix it by serving only the framework’s public/ directory, denying dot-files in nginx, Apache or IIS, rotating every secret in the file, and confirming /.env returns 403 or 404.

What the scanner is actually detecting

Every scanner here does the same thing: it requests a file named .env at the web root (and, for some tools, in each directory it has crawled) and decides whether the response looks like a real environment file. The titles and severities differ.

Scanner Finding title ID Published rating
Tenable Web App Scanning Environment Configuration File Detected Plugin 98538 High, family Web Applications, CWE-200
Acunetix Dotenv .env file Not published as a numeric ID High (CVSS 3.0 7.5), CWE-538
ZAP .env Information Leak Alert 40034 (active rule) Medium, CWE-215

Greenbone web checks can report the same exposure under their own title, so match on the URL in the result details rather than on the wording.

ZAP’s logic is open source and worth knowing. For each directory it has seen, it replaces the last path segment with .env and sends a GET. It raises the alert only when the status is 200, the body is 500 characters or shorter, the Content-Type is absent or application/octet-stream, and the body contains a line that starts with a # comment or a KEY=VALUE pair. That size cap is why ZAP can miss a long .env file that Tenable or Acunetix still reports.

Real-world risk

The severity depends entirely on what is in the file. A typical framework .env holds the database host, user and password, mail server credentials, cache and queue passwords, and third-party API keys. Cloud access keys are usually the most urgent, because unless they are IP-restricted they work from anywhere, while a database password matters only if an attacker can reach the database port.

For a Laravel .env exposed on the web, the APP_KEY deserves special attention. Laravel encrypts and signs cookies and encrypted values with it, so a leaked key lets an attacker decrypt and forge those values. On old releases it is worse: CVE-2018-15133 describes remote code execution in Laravel through 5.5.40 and 5.6.x through 5.6.29 when the attacker knows the application key.

Requests for /.env are a routine part of automated internet probing, so on a public host, assume the file may already have been downloaded unless your logs prove otherwise. Internet-facing web roots are exactly where this belongs in your external attack surface management scope.

How to confirm it on the host

Reproduce the request, printing only the status and content type, then only the variable names. Avoid pasting values into tickets or chat.

curl -s -o /dev/null -w '%{http_code} %{content_type}n' https://www.example.com/.env
curl -s https://www.example.com/.env | cut -d= -f1 | head -n 20

Repeat for any subdirectory path the finding lists. Then find the document root and the files that sit under it:

sudo nginx -T 2>/dev/null | grep -E '^s*(server_name|root) '
sudo grep -Rhi '^s*DocumentRoot' /etc/apache2/ /etc/httpd/ 2>/dev/null
sudo find /var/www /srv -type f -name '.env*' 2>/dev/null

Check whether anyone downloaded it. With the default combined log format, field 9 is the status code:

sudo zgrep -h '/.env' /var/log/nginx/access.log* | awk '$9 == 200'
sudo zgrep -h '/.env' /var/log/apache2/access.log* | awk '$9 == 200'   # /var/log/httpd/access_log* on RHEL-family

How to fix it

Do all three steps. Blocking the URL without rotating secrets leaves already-leaked credentials valid.

1. Point the document root at public/

Laravel, Symfony and similar frameworks keep .env in the project root and expect the web server to serve only public/. Laravel’s deployment guide warns that serving the application from the project root exposes sensitive configuration files. If root or DocumentRoot points at the project directory, change it to /srv/example.com/public (your path will differ). For other stacks, move the file to a directory the web server does not map to any URL.

2a. nginx: deny dotfiles

Laravel’s official nginx example uses this block inside the server:

server {
    server_name example.com;
    root /srv/example.com/public;

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

This returns 403 for any path segment starting with a dot, except .well-known (used by ACME certificate validation and security.txt). Mind the matching order: nginx checks regex locations in the order they appear and stops at the first match, and a longest-prefix location with the ^~ modifier skips regex checks entirely. Place this block above other regex locations, and add a copy inside any ^~ location. If you prefer not to reveal that the file exists, use return 404; instead of deny all;. Apply with:

sudo nginx -t && sudo systemctl reload nginx

2b. Apache: deny .env in the vhost or .htaccess

In the virtual host, alongside the corrected DocumentRoot:

<VirtualHost *:443>
    ServerName example.com
    DocumentRoot /srv/example.com/public
    <FilesMatch "^.">
        Require all denied
    </FilesMatch>
</VirtualHost>

FilesMatch matches the basename (last component of the path), so it blocks /.env, /.env.bak and /app/.env.production, but not files inside a dot-directory such as /.git/config, which needs its own rule. ACME challenge tokens under /.well-known/ do not start with a dot, so they still work. Test and reload:

sudo apachectl configtest && sudo systemctl reload apache2   # httpd on RHEL-family

On shared hosting, the same FilesMatch block can go in the web root’s .htaccess. Require needs AllowOverride AuthConfig (or All) there; with AllowOverride None, Apache ignores the file, so verify with curl.

2c. IIS: request filtering

Add a denied file extension with request filtering; blocked requests get a 404, logged as substatus 404.7:

%windir%system32inetsrvappcmd.exe set config "Default Web Site" -section:system.webServer/security/requestFiltering /+"fileExtensions.[fileExtension='.env',allowed='False']"

Variants such as .env.production have a different extension, so keep them out of the site folder.

3. Rotate every secret in the file

  1. List the keys without printing values: cut -d= -f1 .env
  2. Rotate each credential at its source: database users, SMTP accounts, cloud access keys, payment and API tokens, JWT or signing secrets. Revoke the old value, do not just add a new one.
  3. For Laravel, copy the current APP_KEY, run php artisan key:generate, and put the old key in APP_PREVIOUS_KEYS (Laravel 11 and later) so existing encrypted data still decrypts (see Laravel’s key rotation guide).
  4. If configuration is cached, run php artisan config:cache again; cached config does not reload .env. Restart queue workers and other long-running processes.
  5. Check provider audit logs for use of the old keys between the exposure and the rotation.

How to verify the fix and rescan

for p in /.env /.env.bak /.env.production /app/.env; do
  printf '%s ' "$p"; curl -s -o /dev/null -w '%{http_code}n' "https://www.example.com$p"
done

Every line should show 403 or 404 and the site should still load. If you use HTTP-01 certificates, run sudo certbot renew –dry-run to confirm .well-known still works. If a CDN sits in front, purge its cache for /.env, because a cached copy can keep serving the old response. Then rescan with the same target URL and policy. ZAP only tests directories it has crawled, so run the spider first.

What can break and how to roll back

  • Document root change. Links that include /public/ in the path, or scripts sitting outside public/, stop resolving. Test the main pages before and after.
  • Dotfile rule. Any application that deliberately serves a path starting with a dot (other than .well-known) will get 403.
  • APP_KEY rotation. Without APP_PREVIOUS_KEYS, all users are logged out and previously encrypted data cannot be decrypted.
  • Password rotation. Cron jobs, workers and other servers sharing the same credentials fail until updated.

Back up configs before editing (for example sudo cp -a /etc/nginx/sites-available/example.conf /root/example.conf.bak), and roll back by restoring the file and reloading. Never roll back credential rotation; fix the consumers instead.

Common false positive reasons

  • Catch-all routes. A single-page app or framework router that answers every path with 200 and the HTML shell. ZAP filters HTML content types; check the evidence body in other scanners.
  • ZAP at LOW threshold. A 401 or 403 on /.env produces an Informational alert. That means access is already blocked.
  • Different target. The scanner hit the origin IP or a default virtual host, not the hostname you fixed, or the reverse.
  • Placeholder files. A deliberately public file with dummy values is technically a true positive; document it as an exception rather than excluding the check.

FAQ

Is 403 or 404 the better response?

Both close the finding. 404 reveals slightly less, because it does not confirm the file exists.

Do I still rotate secrets if the logs show no 200 responses?

If your logs cover the whole exposure window and every request got an error, rotation is optional. Rotated, missing or CDN-side logs are not proof, so rotate.

Is .env.example a problem?

Only if it contains real values. It should hold placeholders, but it is still better kept out of the web root.

Can I just delete the .env file?

Tenable’s solution is to remove it or restrict access. Deleting it can break an application that loads configuration from it, so move it out of the served directory instead.

Tracking this finding across many hosts

If you track this finding in SITEY, a self-hosted vulnerability management platform, results from its 16 scanner integrations land in one list, but duplicates are merged per scanner rather than across scanners. Per-finding retest is available for Nessus, Acunetix and Burp results, which fits an Acunetix “Dotenv .env file” finding; for other tools, rerun the scan. AI triage can suggest likely false positives with supporting evidence, and a human makes the final call.

Sources

SITEY closes the loop, not just the report.Discover, validate, fix and verify in your own infrastructure.

See pricing