Websites & apps

Apache mod_rewrite in depth: RewriteRule, RewriteCond and flags

Understand how mod_rewrite really processes rules: pattern matching, back-references, RewriteCond variables, the L, R, NC, QSA and END flags, and the differences between vhost and .htaccess context.

AdvancedApache and LiteSpeed3 min read

Apache mod_rewrite is a regex-based engine that changes or redirects URLs before Apache serves them. It powers pretty permalinks, HTTPS redirects and countless .htaccess tricks, and it is also the source of many redirect loops. This guide explains how mod_rewrite actually evaluates rules so you can write and debug them with confidence. The same syntax works on LiteSpeed.

Before you start

  • mod_rewrite enabled (a2enmod rewrite) and AllowOverride FileInfo (or All) if you use .htaccess.
  • Basic regular expressions: ^, $, ., (.*), [a-z]+.
  • For ready-made recipes see friendly URLs with .htaccess.

Anatomy of a RewriteRule

RewriteEngine On
RewriteRule ^products/([0-9]+)/?$ product.php?id=$1 [L,QSA]
  • Pattern ^products/([0-9]+)/?$ is matched against the URL path.
  • Substitution product.php?id=$1; $1 is the first captured group.
  • Flags in square brackets change behaviour.

What the pattern is matched against

This is the number one source of confusion:

  • In a vhost or server config, the pattern sees the full path with a leading slash: /products/42.
  • In .htaccess, the directory prefix is stripped: in the document root it sees products/42 with no leading slash.
  • The query string is never part of the pattern. Match it with RewriteCond %{QUERY_STRING}.

A pattern like ^/products therefore works in a vhost but never matches in .htaccess.

RewriteCond: conditions

Conditions go directly above the rule they guard. All must be true (AND) unless you add [OR].

RewriteCond %{HTTPS} off
RewriteCond %{HTTP_HOST} ^(?:www\.)?(.+)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]

%1 refers to a group captured in the last matching RewriteCond; $1 refers to the RewriteRule pattern.

Useful server variables

VariableContains
%{HTTP_HOST}Requested domain
%{REQUEST_URI}Path, with leading slash, no query string
%{QUERY_STRING}Everything after ?
%{HTTPS}on or off
%{REQUEST_FILENAME}Mapped file path on disk
%{REMOTE_ADDR}Client IP
%{HTTP_USER_AGENT}Browser string

Test operators: -f (is a file), -d (is a directory), ! to negate, = for an exact string.

The flags you need

FlagEffect
LLast: stop processing this round of rules
ENDStop and do not start another round (stops .htaccess loops)
R=301 / R=302External redirect with that status
NCCase-insensitive match
QSAAppend the original query string to the new one
QSDDiscard the original query string
NEDon't escape special characters such as #
F / GReturn 403 Forbidden / 410 Gone
PProxy the request (needs mod_proxy)

Why L does not always stop in .htaccess

In .htaccess, an internal rewrite to a new path triggers a fresh pass through the same .htaccess file. [L] ends only the current pass. If the new URL matches again you get a loop or a 500 "Request exceeded the limit of 10 internal redirects". Fixes:

  • Add RewriteCond %{REQUEST_FILENAME} !-f and !-d so real files are left alone.
  • Use [END] instead of [L] (Apache 2.4+, supported by LiteSpeed).

The front controller pattern

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [L]

Every URL that is not an existing file or folder goes to index.php, which routes it. This is how WordPress, Laravel and most PHP frameworks work.

Prefer simpler tools when you can

  • A single fixed redirect: Redirect 301 /old /new (mod_alias) is clearer.
  • In a vhost you control, rules there are faster than .htaccess, which is read on every request.
  • Use <If> expressions for simple header logic.

Debug rewrite rules

  1. Test redirects without the browser cache: curl -sI https://example.com/old.
  2. Use 302 while testing; browsers cache 301s aggressively.
  3. On your own Apache, enable tracing temporarily: LogLevel alert rewrite:trace3 in the vhost, then read the error log. Turn it off afterwards.

FAQ: Apache mod_rewrite

Does order of rules matter?

Yes. Rules run top to bottom; put specific rules before general catch-alls.

Why does my rule work in the vhost but not in .htaccess?

The leading slash. Remove / from the start of the pattern in .htaccess.

Does mod_rewrite work on LiteSpeed?

Yes, LiteSpeed Enterprise parses the same rules, including in .htaccess.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.