Blocking direct web access to sensitive files in Apache prevents common publishing mistakes such as leaving .env files, backup copies, database dumps, or repository metadata under the web root. A single downloadable secret or backup can expose credentials, source history, or internal data that makes the next stage of an attack much easier.
Sensitive-file protection in Apache 2.4 works best when it is attached to filesystem-aware containers. FilesMatch applies rules to matching filenames anywhere under the served tree, while DirectoryMatch applies them to matching directories and everything below them. That matters because Location and LocationMatch operate on URL space rather than the underlying filesystem path, so they are the wrong tool for protecting on-disk content such as .git directories or misplaced backups.
The steps below use the Debian or Ubuntu layout with a server-wide include under /etc/apache2/conf-available/, then enable it with a2enconf. Keep the deny patterns tight on sites that intentionally serve archives or database exports, always test syntax before reloading, and if a custom policy later blocks all dot-directories make sure ACME validation paths under /.well-known/acme-challenge/ still remain reachable when Let's Encrypt HTTP-01 validation is in use.
$ sudo apache2ctl -S | sed -n '1,8p' VirtualHost configuration: *:80 host.example.net (/etc/apache2/sites-enabled/000-default.conf:1) ServerRoot: "/etc/apache2" Main DocumentRoot: "/var/www/html" Main ErrorLog: "/var/log/apache2/error.log" ##### snipped #####
On RHEL-family systems, the equivalent tree is usually /etc/httpd/ and the service name is httpd.
$ sudo vi /etc/apache2/conf-available/deny-sensitive-files.conf
A separate include keeps the security policy easy to audit and reuse instead of burying it inside unrelated virtual host changes.
# Block hidden files such as .env, .htaccess, and .user.ini.
<FilesMatch "^\.">
Require all denied
</FilesMatch>
# Block common backup and dump files that should not be downloadable.
<FilesMatch "(?i)(\.(bak|old|orig|save|swp|tmp|sql|sqlite|sqlite3|db)$|~$)">
Require all denied
</FilesMatch>
# Block VCS metadata directories anywhere below the document root.
<DirectoryMatch "(^|/)\.(git|svn|hg|bzr)(/|$)">
Require all denied
</DirectoryMatch>
This split keeps the policy specific: FilesMatch covers matching filenames anywhere under the served tree, while DirectoryMatch blocks repository directories and everything inside them without relying on URL-only LocationMatch rules.
Remove extensions from the second rule if the site intentionally serves those file types as downloads. Moving such files outside the web root is safer than weakening the deny rule globally.
On RHEL-style systems, place the same block in /etc/httpd/conf.d/deny-sensitive-files.conf/ and skip the a2enconf step.
$ sudo a2enconf deny-sensitive-files Enabling conf deny-sensitive-files. To activate the new configuration, you need to run: service apache2 reload
Files under /etc/httpd/conf.d/ are loaded automatically on RHEL-family systems, so there is no a2enconf step there.
$ sudo apache2ctl -t Syntax OK
Use sudo apachectl -t or sudo httpd -t on platforms that ship those control names instead of apache2ctl.
Related: How to test Apache configuration
$ sudo service apache2 reload * Reloading Apache httpd web server apache2 *
On systemd hosts, sudo systemctl reload apache2 is the normal equivalent. The RHEL-family unit name is usually httpd.
$ curl -I -H 'Host: host.example.net' http://127.0.0.1/.env HTTP/1.1 403 Forbidden Date: Wed, 08 Apr 2026 04:37:52 GMT Server: Apache/2.4.58 (Ubuntu) Content-Length: 274 Content-Type: text/html; charset=iso-8859-1 $ curl -I -H 'Host: host.example.net' http://127.0.0.1/.git/config HTTP/1.1 403 Forbidden Date: Wed, 08 Apr 2026 04:37:52 GMT Server: Apache/2.4.58 (Ubuntu) Content-Length: 274 Content-Type: text/html; charset=iso-8859-1
For name-based virtual hosts, keep the Host header pointed at the real site name so the request hits the intended vhost instead of the server default.
$ curl -I -H 'Host: host.example.net' http://127.0.0.1/.well-known/acme-challenge/test-token HTTP/1.1 200 OK Date: Wed, 08 Apr 2026 04:37:52 GMT Server: Apache/2.4.58 (Ubuntu) Last-Modified: Wed, 08 Apr 2026 04:37:50 GMT ETag: "d-64eeb7659425a"
The example rules above do not block ACME token files. This check matters only when the host actually uses HTTP-01 validation or when a broader dot-directory deny rule was added separately.
$ sudo tail -n 4 /var/log/apache2/access.log 127.0.0.1 - - [08/Apr/2026:04:37:52 +0000] "GET /.env HTTP/1.1" 403 435 "-" "curl/8.5.0" 127.0.0.1 - - [08/Apr/2026:04:37:52 +0000] "GET /.git/config HTTP/1.1" 403 435 "-" "curl/8.5.0" 127.0.0.1 - - [08/Apr/2026:04:37:52 +0000] "GET /app.sql HTTP/1.1" 403 435 "-" "curl/8.5.0" 127.0.0.1 - - [08/Apr/2026:04:37:52 +0000] "GET /.well-known/acme-challenge/test-token HTTP/1.1" 200 214 "-" "curl/8.5.0"
If the site already uses a vhost-specific access log, review that file instead of the global /var/log/apache2/access.log path.