Read more

Reverse-proxying web applications with Apache 2.4+

Henning Koch
May 21, 2015Software engineer at makandra GmbH

Note: Making a reverse proxy with nginx is much more straightforward.


Illustration web development

Do you need DevOps-experts?

Your development team has a full backlog? No time for infrastructure architecture? Our DevOps team is ready to support you!

  • We build reliable cloud solutions with Infrastructure as code
  • We are experts in security, Linux and databases
  • We support your dev team to perform
Read more Show archive.org snapshot

A reverse proxy Show archive.org snapshot is a "man in the middle" server that tunnels requests to another server. You can use for things like:

  • Expose a local service that you cannot directly reach over the internet
  • "Change" the domain or path of a web application by rewriting them on the fly
  • Instantly change servers that respond to a name or IP, without relying on DNS TTL

The following describes various Apache 2.4+ VHost directives to reverse-proxy an application that does not want to be reverse-proxied. The example we'll use is a proxy that responds to http://public-host/public-path and secretly tunnels all requests to http://internal-domain/internal-path.

Basic setup

For this to work you need the following Apache modules:

  • headers
  • substitute
  • proxy
  • proxy_http
  • filter
  • ssl

The basic VHost setup looks like this:

<VirtualHost *:80>
  ServerName public-host
  ProxyPass /public-path/ http://internal-host/internal-path/
  ProxyPassReverse /public-path/ /private-path/
</VirtualHost>

In the block above, ProxyPass will setup the reverse proxy.
ProxyPassReverse will make Apache rewrite 302 redirects like Location: /private-path/foo to Location: /public-path/foo.

If the internal server is on HTTPS

If you're tunneling to a server that's on HTTPs, you need to use SSLProxyEngine on:

ProxyPass /public-path/ https://internal-host/internal-path/
SSLProxyEngine on

Note that it doesn't make your proxy available over https://, it just makes sure that it can fetch from HTTPS internally. You've basically built your custom SSL-stripping MITM attack server Show archive.org snapshot .

If your proxy server should be accessible over HTTPS, use your regular Apache directives (SSLEngine and friends) for this.

Rewriting cookies

If the internal web app is hard-wiring cookie paths or domains, you need to rewrite those.
You can do this with the following directives:

ProxyPassReverseCookiePath /internal-path/ /public-path/
ProxyPassReverseCookieDomain internal-domain public-domain

Rewriting asset paths

If you are changing paths (/internal-path/foo to /public-path/foo) /while proxying, the proxied app will probably be broken:

  • all absolute paths in your HTML's link hrefs point to the wrong URL
  • any CSS that contains directives like background-image: url(/internal-path/wallpaper.png) will be break
  • Referenced javascripts or stylesheets will probably attempt to load from the wrong URL

The solution to this involves grepping and replacing all proxied text content. You also need to disable compression so you can find/replace text in the response. All of this is a little screwed up and might have unintended side effects, so make sure you really, absolutely, positively need to change the path while reverse-proxying. If you have control over the internal web application, prefer to deploy it to the same path that you expose publicly.

So anyway, here goes nothing:

# Disable compression
RequestHeader unset Accept-Encoding

Substitute "s|/internal-path/|/public-path/|n"
FilterDeclare NEWPATHS
FilterProvider NEWPATHS SUBSTITUTE "%{Content_Type} =~ m|^text/html|"
FilterProvider NEWPATHS SUBSTITUTE "%{Content_Type} =~ m|^text/css|"
FilterProvider NEWPATHS SUBSTITUTE "%{Content_Type} =~ m|^text/javascript|"
FilterProvider NEWPATHS SUBSTITUTE "%{Content_Type} =~ m|^application/javascript|"
FilterChain NEWPATHS

Note that code above isn't aware of HTML or CSS in any way, it does a brute find-and-replace. You can now no longer use the string /internal-path/ anywhere in your views.

Also note that the FilterProvider syntax has changed in recent Apache versions, so make sure your Apache version (apache2ctl -v) is 2.4 or higher.

Henning Koch
May 21, 2015Software engineer at makandra GmbH
Posted by Henning Koch to makandra dev (2015-05-21 14:12)