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 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 ;-)


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-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?


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/

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/

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.

| 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

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

ProxyAddHeaders off


| improve this answer | |
  • 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 '19 at 20:07

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.

| 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.



| 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

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy