6

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?

4 Answers 4

6

Apache 2.4 and later has a directive to remove the X-Forwarded-* headers.

ProxyAddHeaders off

https://httpd.apache.org/docs/2.4/mod/mod_proxy.html#proxyaddheaders

1
  • 1
    The directive is not to remove headers, but to skip adding them. Any X-Forwarded header already in the request are kept, which may be useful for some chained proxy scenarios.
    – jjmontes
    Jul 2, 2019 at 20:07
5

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.

1
  • 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, 2012 at 22:12
1

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.

2
  • 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, 2009 at 18:07
  • Beside, RewriteRule dont follow ProxyPreserveHost directive...
    – 131
    Sep 4, 2012 at 9:02
0

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

1
  • 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, 2009 at 16:52

You must log in to answer this question.