Darwinweb

Shared SSL Domain and Clean URLs with mod_rewrite

May 12, 2006     

Contrary to my best intentions I’ve been awol for a couple months. Most of that time my laptop was being repaired (these things take time in New Mexico). Meanwhile I got married and started telecommuting! So clearly I’m full of excuses.

Rewrite Ninja

Mod_rewrite is my favorite part of Apache. Maybe I’m a masochist, but never has writing a configuration file been so satisfying. No matter how much I learn, there’s always the next level. Mod_rewrite is not just configuration, it represents a distinct programming paradigm—the functionality is minimal, but the logic is deep and complex. Mod_rewrite is utterly reliable.

Clean URLs, Shared SSL Domain

So at work I’ve been dealing with some half baked rewrite rules for some time. I’ve made incremental improvements over time. However, today I finally perfected the system. The problem is utterly simple: rewrite all non-existing URLs to index.php. The requirements are daunting:

  1. Must work form .htaccess.
  2. Certain file extensions should return 404s if they don’t exist rather than being rewritten.
  3. Must allow existing files and directories to pass through unaffected.
  4. Must apply to both a primary domain and a shared SSL domain with a subdirectory to identify the site.

When I started, only the first two requirements were satisfied. This severely crippled the web framework we were using, as everything had to pass through the index file.

RewriteRule !(\.pdf$|\.gif$|\.jpg$|\.png$|\.css$|\.js$|\.mov$|\.wmv$) index.php

Much like the United States government and Latin American dictators, This one rule has propped up the entire clean URL system for years.

Meeting the 3rd requirement is seemingly simple… just add some pre-conditions:

RewriteCond %{SCRIPT_FILENAME} !-f
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteRule !(\.pdf$|\.gif$|\.jpg$|\.png$|\.css$|\.js$|\.mov$|\.wmv$) index.php

This works on the primary domain, but does not work on the SSL domain, because the site URL has an extra directory added. We need to specify the base URL or Apache won’t find the index.php file:

RewriteBase /~site
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteRule !(\.pdf$|\.gif$|\.jpg$|\.png$|\.css$|\.js$|\.mov$|\.wmv$) index.php

That fixes SSL, but now the regular domain is broken. Unfortunately since both the regular and SSL sites use the same physical directory, we can’t feed separate .htaccess files. We also don’t have access to the Apache config (that would be too easy). So we’ve got to get clever on this one. I must admit banging my head against the wall on this one for an hour or two. The final solution is to add a secondary rewrite that strips off the RewriteBase for the regular domain. Observe my kung fu:

RewriteBase /~site
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteRule !(\.pdf$|\.gif$|\.jpg$|\.png$|\.css$|\.js$|\.mov$|\.wmv$) index.php

RewriteCond %{SERVER_PORT} 80
RewriteCond %{REQUEST_URI} /~site
RewriteRule .* /index.php

This solution works perfectly, but is surprisingly fragile. For one thing, you might think the second RewriteCond could be combined into the RewriteRule, however this is not the case because the RewriteRule doesn’t see the whole internal URI, just the tail end of it (index.php)… at least I think that’s what’s going on. The other subtlety is the leading slash on the final RewriteRule is required or the .htaccess won’t compile. Why? My guess is that without the leading slash the RewriteBase is added and the URL re-injected, creating an infinite loop. That’s just a guess though.