8

I want to create a mod_rewrite RewriteRule which is independent from the location where the web page is installed. I want to define the rewrite rule in a .htaccess file. Let's take this as an example:

RewriteEngine on
RewriteRule ^(.*)\.html html.php

With this rule I want to map all *.html requests to a html.php script which is located in the web root. The problem is, the public base url of the webroot can change. So the web root could be located on http://www.somewhere.tld/ or in some sub directory at http://www.somewhere.tld/foo/bar/.

But using a relative path in a rewrite rule doesn't work. So I have to write one of these:

/html.php (When web is located in root directory of the web)
/foo/bar/html.php (When web is located in foo/bar sub directory)

Alternatively I can set a RewriteBase but I simply don't want to configure this path at all. I want apache to automatically do the right thing so I can just copy the web to some directory and it just works without telling the rewrite rules where the web is located in. How can I do this.?

2
  • What determines the location of the "web root" in this context?
    – MrWhite
    Jan 19, 2019 at 16:25
  • "But using a relative path in a rewrite rule doesn't work." - There would seem be something else going on here, because a relative path should work when used in a directory context (eg. .htaccess), providing you have not already set a RewriteBase elsewhere in your config (including any inherited configs, if mod_rewrite inheritance has been setup). Note that this relates to internal rewrites (as used in the question and all answers below), not external redirects (that would indeed pose a problem).
    – MrWhite
    Feb 13, 2019 at 23:38

5 Answers 5

2

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.

1
  • 1
    I accept this answer because no solution appeared for a long time. So I simply have to live with the fact that it is not possible without setting RewriteBase or using absolute URLs in the rewrite targets.
    – kayahr
    Sep 13, 2011 at 11:36
12

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.

3
  • Thank you so much for your solution, I couldn't upvote it enough. Seems to be working on my setup. Mar 15, 2013 at 11:22
  • This won't work if request URI is something like /some/path/app-root/virtual//stuff(note the double slashes), because Apache will strip those slashes when matching against the URL-path in the RewriteRule directive (and ^(.*)$ will capture virtual/stuff at $1 and not virtual//stuff) May 9, 2016 at 22:44
  • 1
    Maybe I'm missing something, but ... "we set [RewriteBase] ... to the root URL of the server: RewriteBase /. This provides consistency" - this would seem to be the error. Why do you need to set the RewriteBase at all in this context? Why for "consistency"? RewriteBase has a specific purpose; this is not it. If you don't set a RewriteBase then you can simply use a relative path substitution - problem solved. See my answer.
    – MrWhite
    Jan 19, 2019 at 17:09
1

I find Apache's documentation to be misleading:

When using the rewrite engine in .htaccess files the per-directory prefix (which always is the same for a specific directory) is automatically removed for the RewriteRule pattern matching and automatically added after any relative (not starting with a slash or protocol name) substitution encounters the end of a rule set. See the RewriteBase directive for more information regarding what prefix will be added back to relative substutions.

But the prefix it adds back is completely different (path on disk instead of original URL). I can't think of a situation where this would be the correct behavior.

1
  • 1
    "But the prefix it adds back is completely different" - that depends on what you think the per-directory prefix is. The per-directory prefix is the filesystem path that leads to the .htaccess file (including the trailing slash), not the original URL path. This makes sense for internal rewrites (which are arguably more common and as used in the question), but for external redirects you need to override this by setting the RewriteBase, or use root-relative (or absolute - which some say you should always use) URLs.
    – MrWhite
    Oct 21, 2015 at 11:43
1

It's not clear in the question what actually determines the location of the "web root" in this context? (As it clearly differs from the document root.)

For the sake of answering, I assume the "web root" is the directory in which the web application is installed. This should also be the location of the .htaccess file controlling this "web application" (not in the document root).

In which case, you can simply use a relative path substitution. You must not use a RewriteBase directive (which overrides the directory-prefix that is added back later and cannot be set "dynamically").

So, if the "web root" is /foo/bar then the .htaccess at /foo/bar/.htaccess should read something like:

RewriteEngine On
RewriteRule \.html$ html.php [L]

(The RewriteRule pattern suffix ^(.*) is superfluous here.)

So, given a request for /foo/bar/baz/something.html then the request will be internally rewritten to <document-root>/foo/bar/html.php (filesystem path). <document-root>/foo/bar/ is the "directory-prefix" (the filesystem path of the location of the .htaccess file) that is added back to all relative path substitutions.


Standard front-controller pattern (.htaccess file located at <web-application-root>/.htaccess):

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]

Don't set the RewriteBase directive.


Note that the question and all examples on this page are dealing with internal rewrites, not external redirects. Only if we are dealing with external redirects would we need to (dynamically) set/compute the effective base URL (or RewriteBase). Because if you allow the directory-prefix to be added back to a relative substitution in the case of an external redirect, then you will most probably experience an invalid redirect.

0

How about something like

RewriteEngine on
RewriteRule ^(.*)\.html $1/html.php

My example preserves the filename part of html file, though -- for example, page1.html would get redirected to page1/html.php. (Note: I did not test this at all, try at your own risk :))

Also, mod_rewrite guide has tons of examples similar to your problem. Did you take a look at that already?

6
  • This doesn't work. When the .htaccess file is located in the directory which is publicly accessible via the path /foo/bar and I access the file /foo/bar/test.html then $1 only contains /test.html. The path which is outside of the scope of the .htaccess file is stripped away by mod_rewrite.
    – kayahr
    Jun 9, 2011 at 11:48
  • @kayahr first of all, these rules don't need to be in a .htaccess file. Secondly, the .htaccess file does not need to live in the directory $path/foo/bar ... it can live in $path/. Each directory path is checked by Apache for .htaccess Jun 9, 2011 at 12:49
  • 1
    @Phil: I know that the rules don't need to be in .htaccess but I WANT them in .htaccess and I WANT it to be inside the directory of the web and not somewhere in a parent dir. The reason is "easy deployment". Simply unzip the web somewhere and it works out of the box. When I have to manually configure a RewriteBase or manually create some .htaccess file elsewhere or manually edit the global apache configuration then this is counter-productive.
    – kayahr
    Jun 9, 2011 at 13:03
  • 1
    @kayahr, have you seen the way Wordpress isntall? They automatically setup .htaccess but it is generated depending on the Wordpress location from DocumentRoot. This is the only way to achieve this. add a install script that generates .htaccess, write it if the script have write permission, or else show it to use so they can manually add it to .htaccess. Jun 9, 2011 at 15:20
  • @kayahr Think outside the box, there are other ways to deploy your application but they involve work. Welcome to the wonderful world of devops! :) Jun 9, 2011 at 19:31

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .