Relative substitution in mod_rewrite RewriteRule

I have struggled with the same problem, and for the same reason. I am trying to make a web app independent of the location where it is installed, without resorting to a config script or manual user intervention. Just drop the app somewhere and let it do its thing.

And it appears there is a solution after all, at least for Apache 2. Takes four lines. Explaining the thinking behind it takes more than four lines, though ;)

Tl;dr try this:

RewriteBase /

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond $1#%{REQUEST_URI} ([^#]*)#(.*?)\1$
RewriteRule ^(.*)$ %2index.php [QSA,L]

Here is the how and why

  • We can't set the RewriteBase dynamically, so we set it once and for all to the root URL of the server: RewriteBase /. This provides consistency, but it also means that we have to establish the url-path to the current directory ourselves and prefix it to the rewritten URL.

  • So which directory are we in? Let's assume a REQUEST_URI of /some/path/app-root/virtual/stuff. Our htaccess is in app-root. If we grab the virtual part - virtual/stuff - and remove it from the REQUEST_URI, we are left with the url-path to our app directory.

  • Capturing the virtual part is straightforward and can happen in the rewrite rule itself. RewriteRule ^(.*)$ ... makes it available in the $1 variable.

  • Now we do our little string operation and remove the virtual part from the request URI. We don't have string commands for that, but RewriteCond can match strings and capture substrings. So we'll add a RewriteCond with the sole purpose of extracting the url-path to the current directory. Other than that, the "condition" should stay out of our way and always be true.

  • We can use the $1 variable from the RewriteRule in the RewriteCond because mod_rewrite actually processes a ruleset backwards. It starts with the pattern in the rule itself, and if it matches, goes on to check the conditions. So the variable is available by then.

  • While the test string in the RewriteCond can use the variable, the actual condition regex can't. Inside the condition, we can only use internal back-references. So first, we assemble a test string "[virtual part][some separator][request uri]". The '#' char makes a good separator because it won't show up in the URL. Next, we match it against a condition of

    ([^#]*) - anything up to the separator, captures the virtual part
    #       - the separator
    (.*?)   - anything in the request uri up to what we've captured in group one,
              grabs the current directory url-path
    \1$     - group one again, ie the virtual part of the request uri
    

    So here's the full condition: RewriteCond $1#%{REQUEST_URI} ([^#]*)#(.*?)\1$.

  • The second captured group in the RewriteCond regex is our location. We just need to prefix it to the rewritten URL with a %2 reference. That leaves us with RewriteRule ^(.*)$ %2index.php [QSA,L]. Voilà.

I haven't done extensive testing yet, but I've established that it works with ordinary virtual hosts as well as mass virtual hosting (using VirtualDocumentRoot). By implication, other aliased locations should be fine as well.

Apache 1.3

Apache 1.3 is still around, unfortunately, and it will choke on the RewriteCond pattern. Apache 1.3 doesn't support the ungreedy modifier (the '?' in (.*?)).

But for Apache 2, it should do the trick. I'd definitely appreciate any feedback, though, in particular if it fails in your environment.

Edit: I have just posted a more comprehensive article about the subject on my blog ("Using mod_rewrite in .htaccess files without knowing the RewriteBase"). See there for more details.


There is no way as in my knowledge that you can achieve this. You have to configure RewriteBase. One way is to automate setting up of RewriteBase using a PHP script maybe? But that will need write permission (at least) on the .htaccess. But you will have to configure the RewriteBase in .htaccess.