Skip to main content
DevOps
Published 9 min read

Apache Rewrite Rules Explained: RewriteRule, RewriteCond and Flags

Understand how RewriteRule and RewriteCond fit together, what the flags do, and why per-directory rules behave differently.

mod_rewrite is the Apache module that lets you change URLs on the fly. It powers friendly URLs, HTTPS enforcement and site migrations, and it has a reputation for being cryptic. Most of the confusion goes away once you know the three parts of a rule: a pattern, an optional set of conditions, and flags.

The building blocks

RewriteEngine On
RewriteCond  <test string>  <condition pattern>  [flags]
RewriteRule  <url pattern>  <substitution>  [flags]
  • RewriteEngine On switches the module on for this context. Without it no rule runs.
  • RewriteRule matches the requested URL path against a regular expression and says what to do.
  • RewriteCond adds a test that must also be true. A condition applies only to the single RewriteRule that follows it.

How a RewriteRule is matched

In the main server configuration the pattern is matched against the full path, starting with a slash. In an .htaccess file, which is per-directory, Apache strips the directory prefix first, so the pattern sees the path relative to that directory and without a leading slash. A rule written as ^/products/ in the server config therefore becomes ^products/ in .htaccess. This difference trips up almost everyone who copies a rule between the two.

The pattern never includes the query string. Test that separately with a condition.

Capturing parts of the URL

# /blog/2026/my-post  ->  /post.php?year=2026&slug=my-post
RewriteRule ^blog/([0-9]{4})/([a-z0-9-]+)/?$ /post.php?year=$1&slug=$2 [L]

Parentheses capture text, and $1, $2 insert it into the substitution. Inside RewriteCond, the captures from the condition are available as %1, %2.

RewriteCond: only when something else is true

# Redirect only when the host is not the canonical one
RewriteCond %{HTTP_HOST} !^example\.com$ [NC]
RewriteRule ^ https://example.com%{REQUEST_URI} [L,R=301]

The test string can be a server variable such as %{HTTP_HOST}, %{HTTPS}, %{REQUEST_URI} or %{QUERY_STRING}. Several conditions in a row are combined with AND by default; add [OR] to a condition to combine it with the next one using OR.

The flags you will use most

FlagMeaning
LLast rule for this round of processing.
ENDStop all rewrite processing, including any restart (Apache 2.3.9 and later).
R=301Send an external redirect. R alone sends a 302. Use R=301 or R=308 for permanent moves.
NCCase-insensitive match.
QSAAppend the original query string to the new URL.
NEDo not escape special characters in the output.
FReturn 403 Forbidden.
GReturn 410 Gone, useful for removed content.
ORCombine this RewriteCond with the next using OR.

A front controller rule

Many frameworks send every request that is not a real file to a single script:

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

The two conditions say: if the request is not an existing file and not an existing directory, hand it to index.php. Because there is no R flag this is an internal rewrite and the address stays the same.

Why rewrite loops happen

In .htaccess, after a rule rewrites a URL Apache can run the per-directory rules again against the new URL. If your rule matches its own output, it rewrites forever and Apache gives up with a 500 error, while an external redirect that points back at itself produces the browser message about too many redirects. The cure is a guard condition that stops the rule matching once it has done its work, or the END flag. The HTTPS rule in the .htaccess guide is safe because the condition checks that the request is not already HTTPS.

Common mistakes

  • Assuming the pattern sees a leading slash in .htaccess. It does not.
  • Expecting the pattern to match query strings.
  • Placing a broad rule above a specific one. Rules run top to bottom.
  • Forgetting that RewriteCond applies to only the next RewriteRule.
  • Using R without a status code and creating temporary redirects by accident.
  • Stacking separate rules for HTTPS, www and trailing slashes so that one request takes three redirects.

Debugging approach

  1. Make one change at a time, and keep a copy of the working file.
  2. Request the URL from the command line with curl -I to see the status and Location header without browser caching.
  3. Read the Apache error log; it names syntax problems with a line number.
  4. Use the .htaccess Analyzer to read the file's logic, and the Redirect Chain Analyzer to see what a URL actually does after deployment.

Server behaviour varies

Whether .htaccess is honoured, and which modules are loaded, depends on the host. A rule that works on one server may be ignored on another, so always verify the result on the real site.

Conclusion

Rewrite rules are a small language with firm rules: a pattern against the path, conditions that gate it, and flags that decide what happens next. Learn the per-directory difference and always guard against loops, and most of the mystery disappears. For the surrounding file, read what .htaccess is and when it works.

Frequently asked questions

What is the difference between a rewrite and a redirect?
A rewrite changes which file or script Apache uses internally, and the visitor's address bar does not change. A redirect sends a 3xx response telling the browser to request a different URL, so the address changes. In mod_rewrite, adding the R flag turns a rule into a redirect.
Why does my RewriteRule not match the query string?
The pattern is matched against the URL path only. To test the query string, use a RewriteCond on %{QUERY_STRING}. To keep an existing query string when you rewrite, add the QSA flag.
What does the L flag do?
It marks a rule as the last one for this pass of processing. Be careful in per-directory files: after a rewrite Apache can restart processing, so an L rule may be evaluated again. The END flag stops rewriting completely.
How do I stop a rewrite loop?
Add a condition so the rule does not match its own output, for example by testing that the file does not already exist, or use the END flag where it is available. The Redirect Chain Analyzer and .htaccess Analyzer can help you find the cycle.

The tool that pairs with this guide.

Did this help you sort out your server configuration?

If these free developer tools save you time, consider buying me a byte. Your support helps keep DevCraftKit free and growing.

Buy me a byte 💾