Take the 2-minute tour ×
Server Fault is a question and answer site for professional system and network administrators. It's 100% free, no registration required.

I have a VirtualHost that I'd like to have several subdomains on. (For the sake of clarity, let's say my domain is example.com and I'm just trying to get started by making foo.example.com work, and build from there.)

The simplest way I found for a subdomain to work non-invasively with the framework I have was to proxy to a sub-path via mod_rewrite. Thus paths would appear in the client's URL bar as http://foo.example.com/(whatever) while they'd actually be served http://foo.example.com/foo/(whatever) under the hood.

I've managed to do that inside my VirtualHost config file like this:

ServerAlias *.example.com

RewriteEngine on

RewriteCond %{HTTP_HOST} ^foo\.example\.com [NC]   # <---
RewriteCond %{REQUEST_URI} !^/foo/.*$ [NC]         # AND is implicit with above
RewriteRule ^/(.*)$ /foo/$1 [PT]

(Note: It was surprisingly hard to find that particular working combination. Specifically, the [PT] seemed to be necessary on the RewriteRule. I could not get it to work with examples I saw elsewhere like [L] or trying just [P]. It would either not show anything or get in loops. Also some browsers seemed to cache the response pages for the bad loops once they got one... a page reload after fixing it wouldn't show it was working! Feedback welcome—in any case—if this part can be done better.)

Now I'd like to make what http://foo.example.com/foo/(whatever) provides depend on who asked. If the request came from outside, I'd like the client to be permanently redirected by Apache so they get the URL http://foo.example.com/(whatever) in their browser. If it came internally from the mod_rewrite, I want the request to be handled by the web framework...which is unaware of subdomains.

Is something like that possible?

share|improve this question
    
Tell me if I'm wrong, but I think the second RewriteCond tells the engine to rewrite only the requests like /foo/(whatever). This should be the exact opposite, as the client asks for /(whatever) and should be redirected to /foo/(whatever). –  mimipc Oct 19 '13 at 20:46
    
@mimipc You're right, and I have no idea how that happened, as I copied and pasted...and the ! is in the file on the server...else it would not work. :-/ Mysterious! Thanks for noticing--fixed. –  HostileFork Oct 19 '13 at 21:07
add comment

2 Answers

up vote 2 down vote accepted

Seems like you are almost there, no?

Use a RewriteCond based on the REMOTE_ADDR, for example:

#
# Provide HTTP redirect "[R]" for network-external requests
# For permanent redirects, use [R=301], but note cache concerns:
# http://getluky.net/2010/12/14/301-redirects-cannot-be-undon/
#
RewriteCond %{REMOTE_ADDR} !^10\.2\.
RewriteCond %{HTTP_HOST} ^example\.com [NC]
RewriteCond %{REQUEST_URI} ^/foo/.*$ [NC]
RewriteRule ^/foo/(.*)$ http://foo.example.com/$1 [R]

#
# Pass-Through "[PT]" to subpath URL for subdomain requests
# (Assumes that foo.example.com/foo would serve the same
# content as example.com/foo, if not for the above rule)
#
RewriteCond %{HTTP_HOST} ^foo\.example\.com [NC]
RewriteRule ^/(.*)$ /foo/$1 [PT]

The example of using a match in REMOTE_ADDR against 10.2.x.x addresses is from http://httpd.apache.org/docs/2.2/rewrite/intro.html

share|improve this answer
    
This works...! But the REMOTE_ADDR test seems to be unnecessary here, as the two rules don't conflict without it. In fact, I'm not sure if the REMOTE_ADDR changes in an internally proxied request like this. (probably shouldn't...?) I'm actually busy moving today, but do appreciate the feebdack in any case! +1 and awarding the answer since I pasted in code that fit the question... (though feel free to expound on it further if you know more!) Bounty to @Regan for the detailed effort. Thanks to you both for your help! –  HostileFork Nov 6 '13 at 18:12
add comment

I'd suspect you have to use "PT" due to having an Alias somewhere else in your config. Or a non-direct VirtualHost DocumentRoot.

Anyway, this seems to do the trick in my testing:

<VirtualHost _default_:80>
    DocumentRoot /var/www/html
        RewriteEngine on

        RewriteCond %{HTTP_HOST} ^foo\.localhost [NC]
        RewriteCond %{REQUEST_URI} ^/foo/.*$ [NC]
        RewriteRule ^/foo/(.*)$ /$1 [R]

        RewriteCond %{HTTP_HOST} ^foo\.localhost [NC]   # <---
        RewriteCond %{REQUEST_URI} !^/foo/.*$ [NC]         # AND is implicit with above
        RewriteRule ^/(.*)$ /foo/index.php?q=$1 [PT]

</VirtualHost>

With the following output:

$ curl -D - foo.localhost/index.php

HTTP/1.1 200 OK
Date: Wed, 06 Nov 2013 13:46:45 GMT
Server: Apache/2.2.17 (Fedora)
Vary: Host
X-Powered-By: PHP/5.3.6
Content-Length: 55
Connection: close
Content-Type: text/html; charset=UTF-8

Host: foo.localhost
URI: /index.php
Query: q=index.php

$ curl -D - foo.localhost/foo/index.php

HTTP/1.1 302 Found
Date: Wed, 06 Nov 2013 13:46:51 GMT
Server: Apache/2.2.17 (Fedora)
Location: http://foo.localhost/index.php
Content-Length: 293
Connection: close
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>302 Found</title>
</head><body>
<h1>Found</h1>
<p>The document has moved <a href="http://foo.localhost/index.php">here</a>.</p>
<hr>
<address>Apache/2.2.17 (Fedora) Server at foo.localhost Port 80</address>
</body></html>

$ curl -D - foo.localhost/anything

HTTP/1.1 200 OK
Date: Wed, 06 Nov 2013 13:46:59 GMT
Server: Apache/2.2.17 (Fedora)
Vary: Host
X-Powered-By: PHP/5.3.6
Content-Length: 53
Connection: close
Content-Type: text/html; charset=UTF-8

Host: foo.localhost
URI: /anything
Query: q=anything

$ curl -D - foo.localhost/foo/anything

HTTP/1.1 302 Found
Date: Wed, 06 Nov 2013 13:47:04 GMT
Server: Apache/2.2.17 (Fedora)
Location: http://foo.localhost/anything
Content-Length: 292
Connection: close
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>302 Found</title>
</head><body>
<h1>Found</h1>
<p>The document has moved <a href="http://foo.localhost/anything">here</a>.</p>
<hr>
<address>Apache/2.2.17 (Fedora) Server at foo.localhost Port 80</address>
</body></html>
share|improve this answer
    
Thanks! Though it does seem to require changing the underlying site. I can do this particular change: I'd just have to modify the internal URL map so that example.com/foo/(whatever) serves a redirect, while example.com/subdomain/foo/(whatever) is set up to serve the content formerly at /foo. My worry would be somehow leaking the /subdomain/(whatever) style-links, so I'd want to disable those from external access...while keeping the internal forwarding working. So there's kind of no getting around wanting to distinguish an internal request from an external one... :-/ –  HostileFork Nov 6 '13 at 14:58
    
I edited @Eduardo's answer to a solution that is working to my spec, but giving you the bounty for the detailed effort that got me started on looking at the problem again. It's solved to my needs. Thanks!! –  HostileFork Nov 6 '13 at 18:14
    
Glad you got it working, the rules I showed already automatically put in the redirect when the subdomain subpath is leaked. Maybe I missed something you didn't say explicitly. My index.php in the test, merely echos out the $_SERVER variables to show what's happening. I used the q= thing to make trial and error easier in my test setup. I should have noted that. –  Regan Nov 6 '13 at 22:44
    
Now that I re-read, I think I was thinking of your "internal" and "external" to refer to as internal to apache's rules (preventing a loop). Didn't consider it to be inside network and outside network. –  Regan Nov 6 '13 at 22:46
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.