I’ve probably answered thousands of mod_rewrite questions on IRC. Here’s an ordered list of common pitfalls.
-
You’re putting your rules in .htaccess but your config file has an effective value of
AllowOverride nonefor the directory.If you can put garbage in your htaccess file, and you don’t get a 500, it’s not being read by Apache.
- Leading slash in htaccess/per-directory context.
In htaccess or per-directory context, the base part of the URL is stripped before RewriteRule comparisons including a trailing slash. This means if your htaccess is sitting in directory
fooand you want to match a request for /foo/bar- RewriteRule /foo/bar doesn’t match
- RewriteRule ^/bar doesn’t match
- RewriteRule /bar doesn’t match
- RewriteRule bar does match
- RewriteRule ^bar does match
-
You’re trying to match against the Query String in a RewriteRule
You need to use RewriteCond %{QUERY_STRING} before your RewriteRule, capturing as appropriate
-
You’re using %{REQUEST_FILENAME} in vhost context and not realizing it’s just %{REQUEST_URI}, because no mapping has occurred yet.
-
You’re trying to match against more than the “abs_path” of the URL (protocol, host, or port)
Use a RewriteCond for %{HTTP_HOST} if you really need it to be host-specific
-
Your rules are in per-directory or htaccess context, and your URL maps to that place in the filesystem via an Alias, but you either left the RewriteBase off or used the filesytem path and not the URL-path as the argument to RewriteBase.
-
You think the [L] flag prevents your rules from being run, but your rules are in htaccess/per-directory context
In this context, your entire rule-set is re-run if you make any change, which may cause the new URL to not trigger the rule with the [L] flag a second time. Use more precise rules or conditions to guard against the re-entry.
- You tried to match a substring ‘abc’ with something enclosed in square brackets.
Use parens.
- NEW: You’re using a backreference, like %1 or $1, or some other type of variable in the second argument to RewriteCond. It’s a literal there. There’s a trick you can use if you’re trying to (fuzzy) match two variables:
RewriteCond %1,%{REQUEST_URI} !^([^,]+),.*\1
(remove the .* if you want to force the first term to be equal to the second instead of just a suffix of the second
-
You embeded slashes before/after/around your regex that aren’t matched in the URL you care about
-
You didn’t realize .* is both greedy (will match us match as possible) and will also match slashes.
-
You used some convoluted character class to match “not a slash” instead of [^/]
-
You only thought about how your initial request should be modified redirected, without any consideration for what should happen on subsequent requests. Or, when does your rule have to NOT run?
-
NEW: You think your rules are only partially working, because MultiViews is enabled and doing half the stuff your non-matching RewriteRules are trying to do (foo->foo.php but you were also expecting query string side effects)
-
NEW: You think mod_rewrite is sending a redirect instead of internally rewriting, despite your valid configuration — but you’re rewriting internally to directory under your docroot but not adding a trailing slash. mod_dir is going to take your internally rewritten URL and add a trailing slash to it and send it out as a redirect!
-
NEW: Your rule is finally doing something, but it results in a 400 error. The 2nd argument to RewriteRule is your substitution. When the rules are in per-vhost context, the substitution better start with a slash, since all URL-paths start with a slash. If it doesn’t, it turns into an invalid request (400).
-
NEW: You’re looping because your rules are in htaccess and you haven’t used a RewriteCond to make sure you don’t continually rewrite to something you’ll need to also rewrite the next time around. Build yourself an escape hatch, or set an environment variable and check it next time.
-
NEW: You have an unexpected redirect, even though you don’t have an ‘R’ flag. Perhaps the target of your substitution is the name of a directory on-disk and you aren’t adding a trailing slash, but mod_dir will via the DirectorySlash directive (default enabled).
