Skip to content
IsMyENVPublic

Fundamentals · 9 min read

How to protect your .env file from public access

A practical, server-agnostic checklist to keep .env files out of your web root, block them at the web server, and respond correctly if one was exposed.

Last updated

Defense in layers

An environment file should be protected by more than one safeguard. If the document root is configured correctly, the file can't be served. If someone later changes the root, a deny rule still blocks it. If both fail, rotated credentials and short-lived tokens limit the damage. This guide walks through the layers from most to least important and ends with an incident checklist.

1. Keep the file out of the web root

The web server should publish a dedicated directory containing only files meant for browsers: the front controller (index.php, for example), compiled assets and images. Application code, dependencies and configuration live one level above it.

A safe project layout
/var/www/example-app/
├── .env              ← read by the app, never reachable over HTTP
├── app/
├── config/
├── vendor/  or  node_modules/
└── public/           ← the ONLY directory the web server publishes
    ├── index.php
    ├── robots.txt
    └── assets/

On shared hosting that only lets you upload into public_html, upload the application above it and place only the contents of public/ inside public_html. If that's impossible, make the deny rule from step 2 mandatory.

2. Deny dotfiles at the web server

A rule that refuses every path segment starting with a dot also covers backups such as .env.bak, .env.save or .env~, and directories like .git/. Keep /.well-known/ reachable, because it is used for TLS certificates (ACME) and security.txt.

Nginx (inside the server block)nginx
location ~ /\.(?!well-known/) {
    deny all;
    return 404;
}
Apache 2.4 (vhost or .htaccess)apache
<FilesMatch "^\.">
    Require all denied
</FilesMatch>
Caddycaddyfile
@dotfiles {
    path_regexp /\.
    not path /.well-known/*
}
respond @dotfiles 404
IIS (web.config)xml
<configuration>
  <system.webServer>
    <security>
      <requestFiltering>
        <denyUrlSequences>
          <add sequence="/.env" />
        </denyUrlSequences>
      </requestFiltering>
    </security>
  </system.webServer>
</configuration>

The server-specific guides explain details and pitfalls: Nginx and Apache. Put the rule in a shared snippet that every virtual host includes, including the plain-HTTP server block, so a new site can't forget it.

3. Restrict file permissions

The file only needs to be readable by the user the application runs as. On Linux, for example:

Shellbash
sudo chown deploy:www-data /var/www/example-app/.env
sudo chmod 640 /var/www/example-app/.env   # owner read/write, group read, others nothing

Permissions don't stop the web server from serving a file it can read, but they protect against other local users and misconfigured services.

4. Keep deployments clean

  • List .env and .env.* (except .env.example) in .gitignore and .dockerignore.
  • Don't copy the whole project into a directory that is served as static files, for example an Nginx container's /usr/share/nginx/html.
  • Check that build artifacts, ZIP archives and backups uploaded to storage buckets don't include the file.
  • Remember that frontend build tools embed variables with public prefixes such as NEXT_PUBLIC_ or VITE_ into JavaScript that every visitor downloads. Never give a secret such a prefix. The Node.js guide covers this in detail.

5. Consider a secrets store

On container platforms and in the cloud, it is often better not to have a .env file in production at all. Inject variables through the orchestrator (Docker/Kubernetes secrets, systemd EnvironmentFile= outside the web root) or load them from a secrets manager at startup. This also makes rotation easier, because values change in one place.

Verify the protection

Test from a machine outside your network. The expected answer is 403 or 404:

Shellbash
curl -sS -o /dev/null -w "%{http_code}\n" https://example.com/.env
curl -sS -o /dev/null -w "%{http_code}\n" https://example.com/.env.production
curl -sS -o /dev/null -w "%{http_code}\n" http://example.com/.env

A 200 isn't necessarily a leak, because your site may answer every path with an error page. The IsMyENVPublic check tells these cases apart for you.

If your .env was exposed

Blocking access is only the first step

Assume that everything in the file has been copied. Automated scanners download exposed files within hours. The credentials stay valid until you change them, even after the file is gone.
  1. Block access immediately with a deny rule, then move the file out of the web root.
  2. Find out whether it was downloaded. Search your access logs for successful requests:
    Shellbash
    # Nginx / Apache combined log format; zgrep also reads rotated .gz files
    zgrep -hE '"GET /\.env[^" ]* HTTP/[0-9.]+" 200 ' /var/log/nginx/access.log* | less
  3. Rotate every secret in the file, starting with the most powerful ones: cloud keys, database passwords, payment and mail API keys, the application key and signing secrets. Create new credentials, deploy them, then revoke or delete the old ones at each provider.
  4. Check provider audit logs for activity with the old credentials, for example AWS CloudTrail, your database logs or your payment provider's API logs.
  5. Invalidate sessions and tokens signed with rotated keys. For example, changing a JWT secret or Laravel's APP_KEY logs users out.
  6. Re-run the check to confirm the fix, and document the incident. Depending on the data involved, you may have legal notification duties, for example under Art. 33 GDPR.

Remediation checklist

  • The document root points to a dedicated public directory
  • A deny rule for dotfiles is active on every HTTP and HTTPS virtual host
  • /.well-known/ is still reachable for certificate renewal
  • The environment file is readable only by the application user
  • .env is excluded from Git, Docker images and build artifacts
  • No secret uses a public frontend prefix
  • After an exposure: every secret was rotated and old credentials were revoked
  • The check reports no exposure

Fundamentals

What is a .env file?

The dotenv format, where it comes from, what usually lives inside, and why it must stay private.

7 min read