Skip to content

Syntax and Directives in .htaccess

Learn the fundamental syntax rules, directive contexts, conditional blocks (<IfModule>, <IfDefine>), and execution order for writing .htaccess files in Apache.

Every .htaccess file is built from directives: individual instructions that tell Apache how to behave. Before you start writing rules for redirects, security headers, or caching, you need to understand the fundamental syntax that governs how these directives are written, how Apache interprets them, and which modules provide them.

This guide covers the basic syntax rules, explains directive context, introduces conditional blocks like <IfModule> and <IfDefine>, provides an overview of the most commonly used Apache modules, and clarifies the order in which directives are executed.

Directive Syntax Basics

The syntax of .htaccess files is straightforward, but a few rules must be followed precisely. Even a small formatting mistake can result in a 500 Internal Server Error that takes your entire site offline.

One Directive per Line

Each directive occupies its own line. A directive consists of a name followed by one or more arguments, separated by spaces:

Redirect 301 /old-page /new-page

Here, Redirect is the directive name, and 301, /old-page, and /new-page are its arguments.

If a directive is too long for a single line, you can continue it on the next line by placing a backslash (\) at the very end of the line:

Header set Content-Security-Policy \
    "default-src 'self'; script-src 'self' https://cdn.example.com"

Apache treats the two lines as a single directive. Note that the backslash must be the last character on the line with no trailing spaces after it. A space after the backslash will break the continuation.

Comments

Comments begin with a hash symbol (#). Everything from the # to the end of the line is ignored by Apache:

Redirect 301 /old-page /new-page  # This is an inline comment

Use comments generously. A well-commented .htaccess file is much easier to understand when you come back to it weeks or months later:


Redirect 301 /blog https://blog.example.com

Redirect 410 /discontinued-product

Case Sensitivity

Directive names in Apache are case-insensitive. The following three lines are all equivalent:

RewriteEngine On
rewriteengine on
REWRITEENGINE ON

However, arguments are often case-sensitive, especially when they involve file paths, URLs, or regular expressions. On Linux servers, the filesystem itself is case-sensitive, so /About and /about refer to different files:

Redirect 301 /About /about-us
Redirect 301 /about /about-us

The conventional style across the Apache community and in official documentation is to use PascalCase or the canonical casing for directive names (e.g., RewriteEngine, ErrorDocument, Header). Following this convention makes your files easier to read and consistent with examples you will find online.

Whitespace

Apache is flexible with whitespace. Spaces and tabs between a directive name and its arguments are interchangeable, and extra whitespace is generally ignored:

Redirect    301    /old     /new

This works the same as:

Redirect 301 /old /new

However, indentation inside block directives is purely cosmetic. Apache does not require it, but indenting improves readability:

<FilesMatch "\.(jpg|png|gif)$">
    Header set Cache-Control "max-age=31536000, public"
</FilesMatch>

Blank Lines

Blank lines are ignored by Apache. Use them freely to visually separate groups of related directives:

Redirect 301 /old-page /new-page
Redirect 301 /legacy /modern

Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "SAMEORIGIN"

Directive Context

Not every Apache directive can be used in every location. Each directive has a defined context that specifies where it is valid. The common contexts are:

Context Where It Can Appear
server config Only in the main configuration files (httpd.conf, virtual host files).
virtual host Inside a <VirtualHost> block.
directory Inside a <Directory>, <Location>, or <Files> block, or in .htaccess.
.htaccess Inside .htaccess files (subject to AllowOverride).

When a directive's context includes .htaccess, it means you can use it in your .htaccess files. When it does not, the directive is restricted to the main configuration.

For example, the ServerName directive has a context of "server config, virtual host," which means it cannot be used in .htaccess:

ServerName example.com

On the other hand, RewriteRule has a context that includes .htaccess, so it works perfectly:

RewriteEngine On
RewriteRule ^about$ /about.html [L]

How to Check a Directive's Context

The Apache documentation lists the context for every directive. You can find this information on the Apache Directive Quick Reference page or on each directive's individual documentation page.

Each directive entry shows fields like:

Directive:    ErrorDocument
Module:       core
Context:      server config, virtual host, directory, .htaccess
Override:     FileInfo

The Context line tells you where the directive can appear. The Override line tells you which AllowOverride value must be set for it to work in .htaccess.

Block Directives

Some directives act as containers that wrap other directives and limit their scope to specific files, directories, or conditions. These are written with opening and closing tags, similar to HTML:

<DirectiveName argument>
</DirectiveName>

The most commonly used block directives in .htaccess are:

Files and FilesMatch

The <Files> directive applies enclosed directives to files matching a specific name. <FilesMatch> does the same but uses a regular expression:

<Files "config.php">
    Require all denied
</Files>

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

DirectoryMatch

While <Directory> can only be used in the main server configuration, you can use <DirectoryMatch> in certain contexts. However, in .htaccess files, you typically do not need directory blocks because the .htaccess file itself already defines the directory scope.

LocationMatch

<Location> and <LocationMatch> are not valid in .htaccess files. They can only be used in the main server configuration. If you need URL-based matching in .htaccess, use RewriteCond and RewriteRule instead.

Conditional Directives

Apache provides conditional blocks that let you wrap directives so they are only applied when certain conditions are met. This is essential for writing portable .htaccess files that work across different server environments without causing errors.

IfModule

The <IfModule> directive checks whether a specific Apache module is loaded. If the module is present, the enclosed directives are applied. If not, they are silently skipped:

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^about$ /about.html [L]
</IfModule>

This is extremely useful because it prevents your .htaccess file from triggering a 500 Internal Server Error on servers where the required module is not enabled. Without the <IfModule> wrapper, the RewriteEngine directive would fail on a server without mod_rewrite, taking the entire site down.

You can also negate the check to apply directives only when a module is not loaded:

<IfModule !mod_rewrite.c>
    ErrorDocument 404 /404.html
</IfModule>

A common pattern is to wrap each group of module-dependent directives in its own <IfModule> block:

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</IfModule>

<IfModule mod_headers.c>
    Header set X-Content-Type-Options "nosniff"
    Header set X-Frame-Options "SAMEORIGIN"
</IfModule>

<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/html text/css application/javascript
</IfModule>

IfDefine

The <IfDefine> directive checks whether a specific parameter was passed when Apache was started. Parameters are defined using the -D flag on the command line:

apachectl -D TESTING start

In your .htaccess or server configuration:

<IfDefine TESTING>
    Header set X-Debug-Mode "enabled"
</IfDefine>

This is less commonly used in .htaccess than <IfModule>, but it can be useful for toggling behavior between environments (such as enabling debug headers only on a staging server).

Nesting Conditional Blocks

Conditional blocks can be nested to create more specific conditions:

<IfModule mod_rewrite.c>
    <IfModule mod_headers.c>
        RewriteEngine On
        RewriteCond %{HTTPS} off
        RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
        Header set Strict-Transport-Security "max-age=31536000"
    </IfModule>
</IfModule>

This ensures that both mod_rewrite and mod_headers are available before applying the HTTPS redirect and HSTS header together.

Common Modules Overview

Apache's functionality is organized into modules. Each module provides a set of related directives. Understanding which module provides which directives helps you know what to enable on your server and what to wrap in <IfModule> blocks.

Here are the modules you will encounter most frequently throughout this course.

mod_rewrite

Purpose: URL rewriting and advanced redirection.

This is arguably the most powerful and most commonly used module in .htaccess files. It provides the RewriteEngine, RewriteRule, and RewriteCond directives that let you transform URLs, redirect requests, and route traffic based on complex conditions.

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^products/([0-9]+)$ /product.php?id=$1 [L]
</IfModule>

Enable it with:

sudo a2enmod rewrite

mod_headers

Purpose: Manipulation of HTTP request and response headers.

This module provides the Header and RequestHeader directives, which let you set, append, merge, or remove HTTP headers. It is essential for security headers, caching directives, and CORS configuration.

<IfModule mod_headers.c>
    Header set X-Content-Type-Options "nosniff"
    Header set X-Frame-Options "SAMEORIGIN"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>

Enable it with:

sudo a2enmod headers

mod_expires

Purpose: Control of browser caching through expiration headers.

This module provides the ExpiresActive, ExpiresDefault, and ExpiresByType directives, allowing you to tell browsers how long to cache different types of content.

<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType text/css "access plus 1 year"
    ExpiresByType application/javascript "access plus 1 year"
    ExpiresByType image/jpeg "access plus 1 month"
</IfModule>

Enable it with:

sudo a2enmod expires

mod_deflate

Purpose: Response compression to reduce file sizes during transfer.

This module compresses content before sending it to the browser, significantly reducing bandwidth usage and improving load times for text-based resources like HTML, CSS, and JavaScript.

<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/html text/css text/javascript
    AddOutputFilterByType DEFLATE application/javascript application/json
</IfModule>

Enable it with:

sudo a2enmod deflate

mod_mime

Purpose: Mapping file extensions to MIME types and content encodings.

This module provides the AddType, ForceType, AddEncoding, and AddCharset directives. It ensures browsers interpret files correctly by associating file extensions with their proper media types.

<IfModule mod_mime.c>
    AddType application/font-woff2 .woff2
    AddType image/svg+xml .svg
    AddType application/manifest+json .webmanifest
</IfModule>

This module is usually enabled by default on most Apache installations.

mod_authz_core

Purpose: Core authorization framework for access control.

This module provides the Require directive and the <RequireAll>, <RequireAny>, and <RequireNone> container directives. It is the foundation of access control in Apache 2.4, replacing the older Order, Allow, and Deny directives from Apache 2.2.

<RequireAll>
    Require ip 192.168.1.100
</RequireAll>
Require all denied

This module is part of Apache's core and is always available.

Order of Execution

Understanding the order in which Apache applies directives is crucial for writing rules that behave as expected. Misunderstanding this order is a frequent source of confusion and bugs.

Within a Single .htaccess File

Directives within a single .htaccess file are generally processed in the order they appear, from top to bottom. This matters most with mod_rewrite rules, where the output of one rule can become the input for the next:

RewriteEngine On

RewriteRule ^about$ /about.html [L]

RewriteRule ^old-about$ /about [R=301,L]

In this example, a request to /old-about first matches Rule 2, which redirects to /about. The browser then makes a new request to /about, which matches Rule 1 and serves about.html. The order of the rules matters because reversing them could produce different behavior.

The Role of the [L] Flag

The [L] (Last) flag in RewriteRule tells Apache to stop processing further rewrite rules in the current pass. However, if the rule triggers an internal subrequest (like rewriting a URL to a different file), the entire .htaccess file may be processed again from the top. This is a subtle but important detail covered in depth in the URL Rewrites article.

Across Multiple .htaccess Files

When .htaccess files exist in multiple directories along the path of a request, Apache processes them from the topmost directory to the deepest:

/var/www/html/.htaccess          ← Processed first
/var/www/html/blog/.htaccess     ← Processed second
/var/www/html/blog/2024/.htaccess ← Processed last

Directives in deeper files can override those from parent directories. This inheritance behavior is covered in detail in the Scope and Inheritance article.

Directive Type Ordering

Apache processes different types of directives in a specific internal order, regardless of how they appear in the file. The general processing order is:

  1. <Directory> and .htaccess directives (merged in directory order).
  2. <DirectoryMatch> and <Directory "~"> directives.
  3. <Files> and <FilesMatch> directives.
  4. <Location> and <LocationMatch> directives.

This means that a <Files> block will always be applied after a general directory-level directive, even if the <Files> block appears earlier in the file:

Require all granted

<Files "secret.txt">
    Require all denied
</Files>

In this example, secret.txt ends up denied even though Require all granted appears first, because <Files> blocks are always processed after directory-level directives.

A Practical Example of Order Issues

Consider this .htaccess where a developer tries to redirect and rewrite in a specific sequence:

Wrong approach (unexpected behavior):

RewriteEngine On

RewriteRule ^about$ /about.html [L]

RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

A request to http://example.com/about hits the first rule, which rewrites to /about.html internally. The HTTPS redirect never fires for this request because the [L] flag stopped processing. The page is served over plain HTTP.

Correct approach (redirects happen before rewrites):

RewriteEngine On

RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

RewriteRule ^about$ /about.html [L]

Now the HTTPS redirect is checked first. If the request is over HTTP, the visitor is redirected to HTTPS before any URL rewriting happens. This is the correct and expected behavior.

Navigation