Web servers · 7 min read
How to block .env files in Apache
Last updated
Start with the DocumentRoot
Apache serves every readable file below DocumentRoot. If that is your project directory, the .env file next to your code is public. Point the virtual host at the application's public directory instead:
<VirtualHost *:443>
ServerName example.com
DocumentRoot /var/www/example-app/public
<Directory /var/www/example-app/public>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
# SSL directives omitted for brevity
</VirtualHost>Options -Indexes also disables directory listings, which would otherwise reveal file names.
Rule for the virtual host (recommended)
Rules in the server configuration are faster than .htaccess files and can't be switched off by an upload. Add both of the following inside each <VirtualHost> (or globally in apache2.conf/httpd.conf):
# 1) Deny environment files by name, in any directory.
<FilesMatch "^\.env">
Require all denied
</FilesMatch>
# 2) Answer 404 for every hidden file or directory (.git/, .svn/, ...)
# except /.well-known/, which ACME and security.txt need.
RedirectMatch 404 "/\.(?!well-known/)"<FilesMatch>compares the regular expression with the file name only, so^\.envmatches.env,.env.localand.env.productionin every directory. It doesn't match files inside hidden directories such as.git/config, which is why the second rule exists.RedirectMatch 404(frommod_alias) compares the regex with the URL path and responds with404 Not Found. With a status outside 3xx, no target URL is given.- Debian and Ubuntu already ship
<FilesMatch "^\.ht">for.htaccessand.htpasswd. That rule doesn't cover.env.
Rule for .htaccess (shared hosting)
If you can't edit the server configuration, put the same directives into the .htaccess file in your web root. On shared hosting where the whole application lives in public_html, this rule is essential:
<FilesMatch "^\.env">
Require all denied
</FilesMatch>
RedirectMatch 404 "/\.(?!well-known/)"Keep any existing rewrite rules (for example the front-controller rules shipped by Laravel, WordPress or Symfony) below these lines. LiteSpeed-based hosting also reads .htaccess files and understands these directives in typical setups. Confirm with a test request.
The AllowOverride trap
.htaccess rules are silently ignored if overrides are disabled
AllowOverride defaults to None. In that case Apache doesn't even read .htaccess, and your protection doesn't exist, without any error message.The directives above need these override classes for the directory: AuthConfig for Require and FileInfo for RedirectMatch. AllowOverride All includes both. Better yet, move the rules into the virtual host, where they don't depend on overrides at all.
Apache 2.2 syntax
Very old servers use the pre-2.4 access syntax:
<FilesMatch "^\.env">
Order allow,deny
Deny from all
</FilesMatch>On Apache 2.4, these directives only work with mod_access_compat, and mixing old and new syntax in the same context leads to confusing results. Use Require all denied on 2.4, and plan an upgrade if you are still on 2.2, which reached end of life in 2017.
Test and reload
sudo apachectl configtest # "Syntax OK"
sudo systemctl reload apache2 # httpd on RHEL/Fedora
curl -sS -o /dev/null -w "%{http_code}\n" https://example.com/.env # 403 or 404
curl -sS -o /dev/null -w "%{http_code}\n" https://example.com/.env.local # 403 or 404
curl -sS -o /dev/null -w "%{http_code}\n" https://example.com/.git/config # 404Finally, run the IsMyENVPublic check. If the file was reachable before the fix, follow the incident steps and rotate its secrets. For Nginx setups, see the Nginx guide.
Remediation checklist
DocumentRootpoints to the public directory, not the project root<FilesMatch "^\.env">withRequire all deniedis active- Hidden directories such as .git/ return 404, while /.well-known/ still works
- If you rely on
.htaccess:AllowOverridepermitsAuthConfigandFileInfo Options -Indexesdisables directory listingsapachectl configtestpasses and Apache was reloaded- If the file was exposed: all credentials were rotated