Apache RewriteRule for proxying

16,937

Solution 1

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

Solution 2

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.

Solution 3

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:
16,937

Related videos on Youtube

Admin
Author by

Admin

Updated on September 17, 2022

Comments

  • Admin
    Admin almost 2 years

    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?

  • Admin
    Admin about 15 years
    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.
  • Admin
    Admin about 15 years
    Ah, good idea. I used tcpflow and it seems ClockingIT is basing its decision on the X-Forwarded-Host header value :-( Question updated.
  • 131
    131 almost 12 years
    Beside, RewriteRule dont follow ProxyPreserveHost directive...
  • morbaq
    morbaq almost 12 years
    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"?