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
| Flag | Meaning |
|---|---|
| L | Last rule for this round of processing. |
| END | Stop all rewrite processing, including any restart (Apache 2.3.9 and later). |
| R=301 | Send an external redirect. R alone sends a 302. Use R=301 or R=308 for permanent moves. |
| NC | Case-insensitive match. |
| QSA | Append the original query string to the new URL. |
| NE | Do not escape special characters in the output. |
| F | Return 403 Forbidden. |
| G | Return 410 Gone, useful for removed content. |
| OR | Combine 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
- Make one change at a time, and keep a copy of the working file.
- Request the URL from the command line with curl -I to see the status and Location header without browser caching.
- Read the Apache error log; it names syntax problems with a line number.
- 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.
Try it yourself
The tool that pairs with this guide.
.htaccess Analyzer
Analyze Apache .htaccess configuration, rewrite rules, redirects, security settings and common server issues.
Redirect Chain Analyzer
Analyze URL redirects, detect redirect chains and loops, and inspect HTTP status behavior.
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.
Related articles
What Is .htaccess? A Practical Guide for Apache Sites
A per-directory configuration file for Apache: what it can do, where it stops working, and how to avoid the usual mistakes.
Published
Cron Syntax Explained: A Complete Guide to Cron Expressions
What each cron field means, how ranges and steps are matched, and how to read an expression without guessing.
Published
Five-Field vs. Six-Field Cron Expressions
The number of fields does not identify a cron dialect. Here is what changes between schedulers and why you cannot just add or drop a field.
Published