Syntax and Directives in .htaccess
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.
A common mistake is adding a trailing space after the backslash in a multi-line directive. This invisible character prevents Apache from recognizing the line continuation, resulting in a syntax error and a 500 response. If a multi-line directive is not working, check for hidden whitespace.
Comments
Comments begin with a hash symbol (#). Everything from the # to the end of the line is ignored by Apache:
# This is a full-line comment
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:
# =============================================
# URL Redirects
# =============================================
# Blog moved to subdomain in January 2024
Redirect 301 /blog https://blog.example.com
# Old product page discontinued
Redirect 410 /discontinued-product
Comments have zero performance cost. Apache skips them instantly during parsing. There is no reason to avoid them, and every reason to use them to document your intent.
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:
# These redirect two DIFFERENT URLs on a Linux server
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:
# Redirects
Redirect 301 /old-page /new-page
Redirect 301 /legacy /modern
# Security headers
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:
# This will cause a 500 Internal Server Error in .htaccess
ServerName example.com
On the other hand, RewriteRule has a context that includes .htaccess, so it works perfectly:
# This works in .htaccess
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.
Placing a directive in a context where it is not allowed is one of the most common causes of 500 Internal Server Error in .htaccess files. If you encounter a 500 error after adding a new directive, check its context in the Apache documentation to ensure it is valid for .htaccess use.
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>
# Directives inside this block
</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:
# Block access to a specific file
<Files "config.php">
Require all denied
</Files>
# Block access to all files starting with a dot
<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>
# Fallback behavior when mod_rewrite is not available
ErrorDocument 404 /404.html
</IfModule>
A common pattern is to wrap each group of module-dependent directives in its own <IfModule> block:
# URL Rewriting (requires mod_rewrite)
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</IfModule>
# Security Headers (requires mod_headers)
<IfModule mod_headers.c>
Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "SAMEORIGIN"
</IfModule>
# Compression (requires mod_deflate)
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/css application/javascript
</IfModule>
While <IfModule> is a valuable safety net, do not use it to mask configuration problems. If you know a module should be available in your environment, it is better to ensure the module is actually enabled rather than silently falling back to no behavior. Use <IfModule> primarily for portability, when your .htaccess might be deployed across servers with different module setups.
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>
# Only active when Apache was started with -D 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.
# Allow access only from a specific IP
<RequireAll>
Require ip 192.168.1.100
</RequireAll>
# Deny access to everyone
Require all denied
This module is part of Apache's core and is always available.
Apache 2.2 used Order, Allow, and Deny for access control. These directives are deprecated in Apache 2.4 and provided only by mod_access_compat for backward compatibility. New configurations should always use the Require directive from mod_authz_core.
Apache 2.2 (deprecated):
Order deny,allow
Deny from all
Allow from 192.168.1.100
Apache 2.4 (recommended):
Require ip 192.168.1.100
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
# Rule 1: Remove .html extension
RewriteRule ^about$ /about.html [L]
# Rule 2: Redirect old URL
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:
<Directory>and.htaccessdirectives (merged in directory order).<DirectoryMatch>and<Directory "~">directives.<Files>and<FilesMatch>directives.<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:
# This general directive is applied first
Require all granted
# This Files block is applied after, overriding for matching files
<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
# Rewrite clean URLs
RewriteRule ^about$ /about.html [L]
# Force HTTPS
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
# Force HTTPS first
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
# Then rewrite clean URLs
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.
A good rule of thumb for ordering directives in .htaccess:
- Redirects and protocol enforcement (HTTPS, www canonicalization) at the top.
- Access control and security rules next.
- URL rewrites (clean URLs, front controller) after that.
- Headers, caching, and compression at the bottom.
This order ensures that visitors are redirected to the correct, secure URL before any content is processed or served.