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

I have a web application installed (ClockingIT) that acts based on the used subdomain. As we want to use SSL and do not have a wildcard certificate, this is not very convenient for us :-) So I thought about using Apache's mod_proxy and mod_rewrite functionality.

To be more precise, I want the URL Xttps://example.com/cit/ (external) to show the contents of Xttp://test.example.com:3000/ (internal).

Here's my setup:

   <VirtualHost *:443>
     ServerName example.com

     (SSL setup, etc)

     SSLProxyEngine On
     UseCanonicalName Off
     ProxyRequests Off   
     ProxyPreserveHost Off # or On, makes no difference

     RewriteEngine On
     RewriteRule      ^/cit$         Xttp://test.example.com:3000/ [P,NC]
     RewriteRule      ^/cit/(.*)$    Xttp://test.example.com:3000/$1 [P,NC]
     ProxyPassReverse /cit/          Xttp://test.example.com:3000/

test.example.com is not defined on the DNS server, but it is set in /etc/hosts to map to 127.0.0.1. If I do "w3m Xttp://test.example.com:3000/" on the server, I get the correct web page. However, if I access https://example.com/cit/ on my desktop's browser, I do not get the correct web page. The web app receives the request, however it seems to think the request was for the example.com domain and serves a default page instead of the intended subdomain "test" contents. It seems somehow the proxy does not pass on the test.example.com domain, though according to documentation it should. Instead of RewriteRule I also tried ProxyPass directive, but with the same result.

Is there anything I am missing?

(If relevant, ClockingIT is a Ruby on Rails application served via Mongrel)

P.S.: s/Xttp/http/g - ServerFault did not like me using http colon slash slash more than once in my question ;-)

Edit:

After looking at the traffic data using tcpflow, the issue seems to be that Apache sends the following to port 3000:

GET / HTTP/1.1
Host: test.example.com:3000
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: de-de,de;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-15,utf-8;q=0.7,*;q=0.7
Cookie: _session_id=99f5f70d684c2186e64c5ebb8f69d574
Via: 1.1 example.com
X-Forwarded-For: 1.2.3.4
X-Forwarded-Host: example.com
X-Forwarded-Server: example.com

Using "telnet localhost 3000" and pasting the above, I get a redirect. If I repeat this and omit the X-Forwarded-Host: line, I get the intended page. So my setup is actually working, but ClockingIT seems to base its decision on the X-Forwarded-Host value. Any way I can prevent this from being included?

share|improve this question

3 Answers 3

I got bitten by this. It's a really annoying odd one.

Apache's mod_proxy appends a header, x-forwarded-host, to all outbound requests. It can't be disabled with HeaderRequest unset x-forwarded-host, nor with ProxyVia, nor with ProxyPreseveHost. Nor with anything else I could find.

When Rails sees that header, it uses it to construct the Location: header of any HTTP responses. For reference, in the version of Rails vendor'd with Webistrano 1.4 (the app that was tripping me up with mod_proxy ) the relevant code seems to be on line 88 of vendor/rails/actionpack/lib/action_controller/cgi_process.rb, inside the function host_with_port_without_standard_port_handling.

Now look at the typical example of ProxyPass and ProxyPassReverse that's described everywhere on the net - including (essentially) your question and an alternative answer given here:

<VirtualHost *:80>
ServerName proxy.domain.tld
ProxyPass /app1/ http://app1host.internal/
ProxyPassReverse /app1/ http://app1host.internal/
</VirtualHost>

See the problem? It's the PPR line ..

Because Rails/ActionPack/dasFramework, in it's wisdom, is trying to help you by "correcting" the Location: header, the second half of the PPR line isn't correct: instead of matching

Location: http://app1host.internal/redirected/path

mod_proxy will actually see

Location: http://proxy.domain.tld/redirected/path

The fix, luckily, is quite easy - change the above vhost config to:

<VirtualHost *:80>
ServerName proxy.domain.tld
ProxyPass /app1/ http://app1host.internal/
ProxyPassReverse /app1/ http://proxy.domain.tld/
</VirtualHost>

If you have more than one app being proxied in the vhost, be aware that you'll need to put the PPRs inside Location sections at the very least to differentiate them.

share|improve this answer
    
I've been looking for a solution to this for a few days now. Lifesaver! Is there any drawbacks of using both "ProxyPassReverse /" and "ProxyPassReverse proxy.domain.tld/"; in a Location context, convering both "rails magically fixed" urls and "normal"? –  morbaq Sep 10 '12 at 22:12

You may find it easier to use ProxyPass instead of mod_rewrite to do what you're trying to do. It may not solve your problem, but it would certainly make your config file a bit cleaner:

ProxyPass        /cit/          http://test.example.com:3000/
ProxyPassReverse /cit/          http://test.example.com:3000/

You might like to try using something like tcpflow or wireshark to see exactly what headers apache is using to proxy the request.

share|improve this answer
    
Ah, good idea. I used tcpflow and it seems ClockingIT is basing its decision on the X-Forwarded-Host header value :-( Question updated. –  Daniel Jun 9 '09 at 18:07
    
Beside, RewriteRule dont follow ProxyPreserveHost directive... –  131 Sep 4 '12 at 9:02

Are you sure that the ProxyPreserveHost directive doesn't make a difference? If it's turned on, the host header from the initial request will be preserved in the request made to the backend server, which is what you are describing.

See:

http://httpd.apache.org/docs/2.2/mod/mod_proxy.html#proxypreservehost

share|improve this answer
    
Yes, it should make a difference, but afaict it doesn't. I tested both (also restarted apache after the change) and got the same result. That's why I am so puzzled. –  Daniel Jun 9 '09 at 16:52

Your Answer

 
discard

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