How to Use Apache’s mod_rewrite for URL Rewriting

How to use Apache's mod_rewrite for URL rewriting

How to use Apache's mod_rewrite for URL rewriting

I’ll be honest — mod_rewrite intimidated me for a long time. The regex-heavy syntax looked like line noise the first few times I tried to read someone else’s rewrite rules. But once I sat down and actually learned the logic behind it, it became one of the most powerful tools in my Apache toolkit, letting me clean up ugly URLs, enforce HTTPS, set up redirects, and build entire routing systems for single-page applications.

What mod_rewrite Actually Does

mod_rewrite is Apache’s URL rewriting engine. It lets you intercept incoming requests and transform them based on pattern matching (regular expressions), before Apache decides how to actually serve the request. This lets you:

Prerequisites

Step 1: Enable mod_rewrite

sudo a2enmod rewrite
sudo systemctl restart apache2

On CentOS/RHEL, mod_rewrite is usually enabled by default, but verify with:

httpd -M | grep rewrite

Step 2: Allow Overrides (If Using .htaccess)

If you plan to place rewrite rules in .htaccess files rather than directly in your virtual host configuration, make sure AllowOverride All is set for your document root:

<Directory /var/www/example.com/public_html>
    AllowOverride All
</Directory>

I generally prefer putting rewrite rules directly in the virtual host file when I control the server, since .htaccess files add a small performance overhead (Apache has to check for them on every request). But .htaccess is unavoidable in shared hosting environments where you don’t have access to the main config.

Step 3: Understand the Core Directives

The three directives you’ll use constantly:

A basic rule looks like:

RewriteEngine On
RewriteRule ^old-page$ /new-page [R=301,L]

This says: if the request path matches exactly old-page, redirect (R=301, a permanent redirect) to /new-page, and stop processing further rules (L, meaning “last”).

Step 4: Common Real-World Rewrite Recipes

Force HTTPS

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Force www

RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^(.*)$ https://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Force non-www (the opposite)

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

Clean URLs (Query String to Path)

Turning /product.php?id=42 into /products/42:

RewriteEngine On
RewriteRule ^products/([0-9]+)/?$ product.php?id=$1 [L,QSA]

Here, ([0-9]+) captures one or more digits, and $1 refers back to that captured group in the target.

Removing .php or .html Extensions

RewriteCond %{REQUEST_FILENAME}\.php -f
RewriteRule ^(.*)$ $1.php [L]

This checks if a .php file matching the request exists, and if so, serves it without requiring the visitor to type the extension.

Front Controller Pattern (Used by Most Frameworks)

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]

This routes every request that doesn’t match an actual file or directory through index.php, letting the application’s internal router handle it. This is exactly how WordPress’s permalink system and most PHP frameworks work under the hood.

Step 5: Understanding Rewrite Flags

Flags (the bracketed part at the end of a rule) control behavior:

Step 6: Testing Your Rules Safely

Always test syntax before restarting in production:

sudo apache2ctl configtest

For debugging why a rule isn’t matching as expected, enable rewrite logging temporarily (Apache 2.4+ uses LogLevel with a module-specific trace level rather than the old RewriteLog directive):

LogLevel alert rewrite:trace3

Check your error log after making a request, and you’ll see a detailed trace of each condition and rule being evaluated. Remember to turn this back down to a normal log level once you’re done debugging — trace-level logging is verbose and will bloat your logs quickly.

Real-World Use Cases

Troubleshooting Common Issues

Problem: “Too many redirects” errors. Usually caused by a rewrite rule that keeps matching its own output. Double-check your RewriteCond — for instance, forcing HTTPS without first checking %{HTTPS} off can create an infinite loop.

Problem: Rules work in the main config but not in .htaccess (or vice versa). Remember that in .htaccess context, the leading slash behavior and RewriteBase requirements can differ from virtual host context. Add RewriteBase / near the top of your .htaccess file if paths aren’t resolving correctly.

Problem: Query string parameters disappear after rewriting. Add the [QSA] flag to append the original query string to the rewritten URL instead of discarding it.

Problem: Rules seem to be ignored entirely. Confirm mod_rewrite is enabled (apache2ctl -M | grep rewrite) and that AllowOverride All is set if you’re using .htaccess.

Common Mistakes to Avoid

Security Best Practices

RewriteRule ^\.env$ - [F,L]
RewriteRule ^\.git/ - [F,L]

Performance Optimization Tips

Frequently Asked Questions

What’s the difference between RewriteRule and Redirect (from mod_alias)? Redirect and RedirectMatch handle simple path redirection without complex conditions. mod_rewrite is more powerful, supporting conditional logic, capture groups, and internal (non-redirected) rewrites.

Do I need mod_rewrite if I’m using a modern framework like Laravel? Yes, typically — most PHP frameworks rely on the front controller pattern, which requires a rewrite rule to route all requests through index.php.

Can mod_rewrite rules go in the httpd.conf file directly? Yes, either inside a <VirtualHost> block or a <Directory> block, which is generally more performant than using .htaccess.

How do I redirect an entire old domain to a new one while preserving paths?

RewriteEngine On
RewriteCond %{HTTP_HOST} ^olddomain\.com$ [NC]
RewriteRule ^(.*)$ https://newdomain.com/$1 [R=301,L]

Summary and Key Takeaways

mod_rewrite looks intimidating at first because of its regex-heavy syntax, but the underlying logic is straightforward once you’ve written a handful of rules: match a pattern, optionally check conditions, then rewrite or redirect.

Key points to remember:

  1. Enable mod_rewrite and ensure AllowOverride All if working through .htaccess.
  2. Use RewriteCond to add conditions before a RewriteRule applies.
  3. Learn the common flags — [L], [R=301], [NC], [QSA] — since you’ll use them constantly.
  4. Test changes with apache2ctl configtest before restarting, and use rewrite:trace3 logging temporarily when debugging.
  5. Prefer virtual host-level rules over .htaccess for performance when you have the access to do so.

Once it clicks, mod_rewrite becomes one of those tools you reach for constantly — for SEO cleanup, migrations, security hardening, and framework routing alike.

References

Exit mobile version