Skip to content

Directory Access Control in .htaccess

Learn how to secure your Apache web server by controlling directory access in .htaccess using Options, Require, and FilesMatch to block sensitive files like .env, .git, and backups.

Controlling who can access what on your web server is one of the most critical responsibilities of a .htaccess configuration. A misconfigured server can expose source code, database credentials, version control history, and backup files to anyone with a browser. These are not hypothetical risks. Automated scanners constantly probe websites for files like .env, .git/config, and database.sql, and a single exposed file can lead to a complete compromise.

This guide covers the Options directive for controlling directory behavior, the Require directive for IP and host-based access control, FilesMatch blocks for targeted file protection, and comprehensive patterns for blocking access to sensitive files that should never be publicly accessible.

The Options Directive

The Options directive controls which server features are available in a specific directory. It determines whether Apache will list directory contents, follow symbolic links, or attempt content negotiation. Each feature can be enabled with a + prefix or disabled with a - prefix.

The general syntax is:

Options [+|-]option [+|-]option ...

Indexes (Directory Listing)

When a user requests a directory URL (like https://example.com/images/) and no index file (index.html, index.php, etc.) exists in that directory, Apache has two choices: show a listing of all files in the directory or return a 403 Forbidden error. The Indexes option controls this behavior.

With Indexes enabled (often the default), Apache generates an HTML page listing every file and subdirectory:

Index of /images/

Name                    Last modified      Size
─────────────────────────────────────────────────
backup-database.sql     2024-01-15 09:30   4.2M
config.php.bak          2024-01-14 14:22   1.1K
logo.png                2024-01-10 08:00   45K
secret-notes.txt        2024-01-12 11:15   2.3K

This is a significant security risk. Attackers can browse your entire file structure, discover backup files, configuration files, and other sensitive resources that you never intended to be publicly accessible.

Disable directory listing:

<IfModule mod_autoindex.c>
  Options -Indexes
</IfModule>

With Indexes disabled, requests for directories without an index file return a 403 Forbidden response instead of a file listing.

The FollowSymlinks option tells Apache whether to follow symbolic links (symlinks) in the directory. A symbolic link is a file that points to another file or directory elsewhere on the filesystem.

Options +FollowSymlinks

This option is required for mod_rewrite to function. If you use any RewriteRule directives in your .htaccess, you must have FollowSymlinks enabled, otherwise the rewrite engine will not work.

Options +FollowSymlinks
<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteRule ^(.*)$ /index.php [L]
</IfModule>

On some shared hosting environments, FollowSymlinks is disabled for security reasons (to prevent users from creating symlinks to files outside their home directory). In those cases, you can use the alternative:

Options +SymLinksIfOwnerMatch

SymLinksIfOwnerMatch only follows a symlink if the owner of the symlink matches the owner of the target file. This is more secure but has a performance cost because Apache must perform additional filesystem checks on every request.

MultiViews

MultiViews enables Apache's content negotiation feature. When a user requests /about and that exact file does not exist, Apache scans the directory for files named about.* (like about.html, about.php, about.txt) and serves the best match based on the client's preferences.

While this sounds convenient, it often causes problems:

  • It can interfere with clean URL rewrite rules by matching files before your RewriteRule has a chance to process the request.
  • It can serve unexpected files. A request for /download might serve download.php when you intended it to hit your front controller.
  • It creates unpredictable behavior that is difficult to debug.

Disable MultiViews:

Options -MultiViews

Example of the problem MultiViews causes:

Imagine you have these files:

/var/www/html/
├── about.html
├── about.php
└── index.php   (front controller)

With MultiViews enabled, a request to /about bypasses your front controller and directly serves either about.html or about.php (whichever Apache considers the "best match"). Your application's routing logic never runs.

With MultiViews disabled, the request goes through your rewrite rules and hits the front controller as expected.

None and All

The Options directive also supports two special values that control all options at once:

Options None

Options All

Options None turns off every option, including Indexes, FollowSymlinks, MultiViews, ExecCGI, and Includes. This is the most restrictive setting.

Options All enables all options except MultiViews (which must be explicitly enabled due to its potential for unintended behavior).

Recommended production baseline:

Options -Indexes -MultiViews +FollowSymlinks

This disables directory listing and content negotiation while keeping symlinks enabled for mod_rewrite.

Common Mistake: Mixing Relative and Absolute Options

Wrong approach:

Options FollowSymlinks

When you use Options without + or - prefixes, it replaces the entire options list rather than modifying it. Other options that were previously disabled by a parent configuration may become re-enabled.

Correct approach:

Options -Indexes -MultiViews +FollowSymlinks

Using + and - prefixes ensures you only change the specific options you intend to, leaving everything else as configured by the parent directory or server config.

The Require Directive (mod_authz_core)

The Require directive, provided by mod_authz_core (Apache 2.4+), controls who is allowed to access resources. It replaces the older Allow, Deny, and Order directives from Apache 2.2.

Require all granted

Allows access to everyone without restriction:

<IfModule mod_authz_core.c>
  Require all granted
</IfModule>

This is the most permissive setting. Use it for publicly accessible resources like your main website content, public assets, and static files.

Require all denied

Denies access to everyone without exception:

<IfModule mod_authz_core.c>
  Require all denied
</IfModule>

Any request returns a 403 Forbidden response. This is used to completely block access to specific files, directories, or locations. No IP address, no user, and no authentication can override this.

Require ip

Restricts access to specific IP addresses or ranges:

<IfModule mod_authz_core.c>
  Require ip 203.0.113.50

  Require ip 192.168.1.0/24

  Require ip 10.0

  Require ip 203.0.113.50 198.51.100.0/24 10.0
</IfModule>

When multiple Require ip directives are listed without a wrapping <RequireAny> or <RequireAll> block, they are implicitly combined with OR logic. A client matching any one of the listed IPs or ranges is granted access.

Require host

Restricts access based on the client's hostname (resolved via reverse DNS):

<IfModule mod_authz_core.c>
  Require host example.com

  Require host .example.com
</IfModule>

Combining Requirements

Apache 2.4 provides container directives to combine multiple Require statements with explicit logic:

RequireAll (AND logic): All enclosed requirements must be satisfied.

<RequireAll>
  Require ip 192.168.1.0/24
  Require valid-user
</RequireAll>

The client must be from the 192.168.1.0/24 network and must provide valid authentication credentials.

RequireAny (OR logic): At least one enclosed requirement must be satisfied.

<RequireAny>
  Require ip 192.168.1.0/24
  Require ip 10.0.0.0/8
</RequireAny>

The client can be from either network.

RequireNone (NOT logic): None of the enclosed requirements may be satisfied.

<RequireAll>
  Require all granted
  <RequireNone>
    Require ip 203.0.113.100
    Require ip 198.51.100.200
  </RequireNone>
</RequireAll>

This grants access to everyone except the two listed IP addresses.

Practical example: Admin area restricted to office IPs:

Restricting /admin/ to specific IPsapache
<IfModule mod_authz_core.c>
  <Location "/admin">
    <RequireAny>
      Require ip 203.0.113.0/24
      Require ip 198.51.100.50
    </RequireAny>
  </Location>
</IfModule>

FilesMatch Blocks for Access Control

While the Require directive controls access to directories and locations, <FilesMatch> lets you target specific files or file patterns using regular expressions. This is essential for protecting individual files without affecting the rest of your site.

The syntax is:

<FilesMatch "regex-pattern">
</FilesMatch>

Blocking a Specific File

<FilesMatch "^config\.php$">
  <IfModule mod_authz_core.c>
    Require all denied
  </IfModule>
</FilesMatch>

Any direct request to config.php returns 403 Forbidden. The file can still be included or required by other PHP scripts on the server, but it cannot be accessed directly via a browser.

Blocking Multiple File Types

<FilesMatch "\.(sql|log|ini|conf)$">
  <IfModule mod_authz_core.c>
    Require all denied
  </IfModule>
</FilesMatch>

This blocks direct access to all files ending in .sql, .log, .ini, or .conf.

Using Files Instead of FilesMatch

For a single, exact filename match without regex, use the simpler <Files> directive:

<Files ".htpasswd">
  <IfModule mod_authz_core.c>
    Require all denied
  </IfModule>
</Files>

The difference: <Files> matches exact filenames (with optional wildcards), while <FilesMatch> uses full regular expressions.

Blocking Access to Sensitive Files

This is where access control becomes most critical. Production servers frequently contain files that should never be publicly accessible. Here is a comprehensive approach to locking them down.

Dotfiles (.env, .git, .htpasswd)

On Linux and macOS systems, files and directories beginning with a dot (.) are hidden from normal directory listings but are fully accessible via HTTP if someone knows (or guesses) their name. These dotfiles often contain the most sensitive data on your server.

Common dangerous dotfiles:

File / Directory Contains
.env Database passwords, API keys, application secrets
.git/ Entire version control history, including source code
.gitignore Reveals project structure and file patterns
.svn/ Subversion metadata and source code
.htpasswd Hashed passwords for HTTP authentication
.htaccess Server configuration (should not be directly downloadable)
.ssh/ SSH keys (catastrophic if exposed)
.DS_Store macOS directory metadata revealing file structure

Block all dotfiles and dot-directories:

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{REQUEST_URI} "!(^|/)\.well-known/([^./]+./?)+$" [NC]
  RewriteCond %{SCRIPT_FILENAME} -d [OR]
  RewriteCond %{SCRIPT_FILENAME} -f
  RewriteRule "(^|/)\." - [F]
</IfModule>

Let's break down each line:

  • Line 1 excludes the .well-known/ directory from the block. This directory is a standardized location (RFC 5785) used by SSL certificate validation (Let's Encrypt), security policies, and other protocols. Blocking it would break certificate renewal and other legitimate services.
  • Lines 2-3 check that the requested path actually exists as a file (-f) or directory (-d). This prevents the rule from interfering with clean URL routing where dots might appear in the URL path.
  • Line 4 matches any path component that starts with a dot and returns a 403 Forbidden response ([F] flag).

Alternative approach using FilesMatch (simpler but less comprehensive):

<IfModule mod_authz_core.c>
  <FilesMatch "^\.">
    Require all denied
  </FilesMatch>
</IfModule>

This blocks access to any file starting with a dot. However, it does not block access to dot-directories or their contents as thoroughly as the rewrite-based approach. For maximum protection, use the mod_rewrite method.

Config Files

Configuration files containing database credentials, API keys, and application settings should never be accessible via HTTP. Even if your application framework stores them outside the document root, backup copies or deployment artifacts sometimes end up in publicly accessible directories.

<IfModule mod_authz_core.c>
  <FilesMatch "(^#.*#|\.(bak|conf|dist|fla|in[ci]|log|orig|psd|sh|sql|sw[op])|~)$">
    Require all denied
  </FilesMatch>
</IfModule>

This single regex pattern blocks a wide range of dangerous file types:

Pattern Matches Why It Is Dangerous
^#.*# #config.php# Emacs auto-save files containing source code
.bak config.php.bak Backup files with full source code
.conf app.conf, nginx.conf Configuration files with server settings
.dist .env.dist, config.dist Distribution config templates revealing structure
.fla animation.fla Adobe Flash source files
.in[ci] .inc, .ini Include files and INI configs with credentials
.log error.log, debug.log Log files that may contain sensitive data or stack traces
.orig file.orig Original files from merge conflicts
.psd design.psd Photoshop source files (intellectual property)
.sh deploy.sh, backup.sh Shell scripts revealing server operations
.sql backup.sql, dump.sql Database dumps with all your data
.sw[op] .swp, .swo Vim swap files containing file contents
~ config.php~ Backup files created by various text editors

Backup Files

Text editors and IDEs create temporary and backup files while you work. If you edit files directly on the server (which you should avoid, but it happens), these backup files can end up in your document root.

Beyond the general pattern above, here are additional patterns for commonly overlooked backup files:

<IfModule mod_authz_core.c>
  <FilesMatch "\.(bak|backup|old|save|tmp|temp)$">
    Require all denied
  </FilesMatch>

  <FilesMatch "^(\.gitignore|\.gitattributes|\.gitmodules|\.editorconfig|\.npmrc|\.yarnrc)$">
    Require all denied
  </FilesMatch>

  <FilesMatch "^(composer\.json|composer\.lock|package\.json|package-lock\.json|yarn\.lock|Gemfile|Gemfile\.lock|Makefile|Gruntfile|Gulpfile|Rakefile)$">
    Require all denied
  </FilesMatch>

  <FilesMatch "^(README|CHANGELOG|CONTRIBUTING|LICENSE|TODO|INSTALL)(\.md|\.txt|\.rst)?$">
    Require all denied
  </FilesMatch>
</IfModule>

Common Mistake: Blocking Only by Extension

Wrong approach:

<Files ".env">
  Require all denied
</Files>

Developers commonly have multiple environment files like .env.local, .env.production, .env.backup, etc. The <Files> directive with an exact name only blocks the base .env file.

Correct approach:

<FilesMatch "^\.env">
  Require all denied
</FilesMatch>

The regex ^\.env matches any filename starting with .env, catching all variations.

Comprehensive Security Configuration

Here is a complete, production-ready access control configuration that combines all the patterns discussed in this guide:

Complete directory access controlapache
Options -Indexes -MultiViews +FollowSymlinks

<IfModule mod_rewrite.c>
  RewriteEngine On

  RewriteCond %{REQUEST_URI} "!(^|/)\.well-known/([^./]+./?)+$" [NC]
  RewriteCond %{SCRIPT_FILENAME} -d [OR]
  RewriteCond %{SCRIPT_FILENAME} -f
  RewriteRule "(^|/)\." - [F]
</IfModule>

<IfModule mod_authz_core.c>

  <FilesMatch "(^#.*#|\.(bak|conf|dist|fla|in[ci]|log|orig|psd|sh|sql|sw[op])|~)$">
    Require all denied
  </FilesMatch>

  <FilesMatch "^\.env">
    Require all denied
  </FilesMatch>

  <FilesMatch "^(composer\.(json|lock)|package(-lock)?\.json|yarn\.lock|Makefile)$">
    Require all denied
  </FilesMatch>

  <FilesMatch "^\.ht">
    Require all denied
  </FilesMatch>

</IfModule>

Proper directory access control is not a one-time setup. Every time you add new tools, frameworks, or deployment processes to your project, review your access control rules to ensure that no new file types are left exposed. Treat every file in your document root as potentially accessible to the public, and explicitly block everything that should not be.

Navigation