I recently had to setup Apache as a front-end web server for multiple backend servlet containers. The backend containers serve up different web applications, and the Apache front-end unites them from a hostname and port standpoint. The following instructions describe how to configure Apache 2 on Mac OS X to proxy requests to Tomcat or Jetty running on localhost:8080. It also shows how to enable SSL on Apache and force it for certain URLs in your Java web application.

Apache comes pre-installed on OS X, so you should be able to start it by enabling "Web Sharing" in System Preferences > Sharing.

$APACHE_HOME on Leopard is /etc/apache2. On Tiger, it's /etc/httpd. If you've upgraded Tiger to Leopard, it's likely you'll have both directories so make sure you're modifying the right one. I lost a few hours figuring this out, so hopefully this knowledge will appease some googler in the future.

Configuring mod_proxy

Open $APACHE_HOME/httpd.conf and add the following on line 480 - at the very bottom, just before "Include /private/etc/apache2/other/*.conf".

ProxyPreserveHost allows request.getServerName() and request.getServerPort() to work as if there is no proxy server in place. In other words, even though Tomcat is running on 8080, request.getServerPort() will return 80.

The most important line is the last one as this is the dictates the location of your applications. Add more lines as you need to add more applications.

If everything is configured correctly, you should be able to run sudo apachectl restart and navigate to http://localhost/status. If you receive a "forbidden" error, make sure your /etc/hosts has an entry mapping 127.0.0.1 to localhost (as one of the last entries), or change "Allow from 127.0.0.1" to "Allow from localhost". If you get a "Server not found" error, you can tail the error log at "/var/log/apache2/error_log".

One issue I've seen with mod_proxy is when a request comes in and the backend server is down. When this happens, Apache returns a 503 Service Temporarily Unavailable and it doesn't seem to go away after the backend server is restarted. It does resume proxying after a while, but I haven't determined what causes the proxy to come back to life. If you know a setting that forces mod_proxy to check for the backend server on every request, please let me know.

Configuring SSL

Open $APACHE_HOME/httpd.conf and uncomment the following on line 470:

Include /private/etc/apache2/extra/httpd-ssl.conf

Open $APACHE_HOME/extra/httpd-ssl.conf and change line 78 to:

ServerName localhost:443

In httpd-ssl.conf, change line 99 to:

SSLCertificateFile "/private/etc/apache2/ssl.key/server.crt"

In httpd-ssl.conf, change line 107 to:

SSLCertificateKeyFile "/private/etc/apache2/ssl.key/server.key"

In httpd-ssl.conf, add the following after SSLEngine on to allow proxying via HTTPS:

Run sudo apachectl restart and go to https://localhost. If you get a "Server not found" error, run sudo apachectl -t to verify the syntax of your config files or tail -f /var/log/apache2/error_log to verify there are no errors in the log files.

The solution I came up with is to standardize on secure URLs in my application. That is, use /secure/* as a prefix for all URLs that should be accessed via SSL. To follow this convention and force it, I added the following in my application's web.xml file:

Once this is in place, accessing http://localhost/myapp/secure/index.html will result in an error. Accessing it using https will succeed. Following this, you can change your ProxyPass rules to the following and all requests to /secure/* will be https; other requests will be sent to http. The order of the rules below is important.

If this isn't a good strategy for you, Tomcat has the ability to use a redirectPort (in server.xml) that auto-redirects from http to https when CONFIDENTIAL is used in web.xml. I'm not sure if this redirect will carry through values from a form post.

I'm don't know how if this is true for for tomcat as well but at least for jetty the documentation says "Using the HTTP Connectors is greatly preferred, as Jetty performs significantly better with HTTP and the AJP protocol is poorly documented and there are many version irregularities."

.- You might also want to use ProxyPassReverse so redirects by the backend server are handled correctly, even though it is not mandatory.

.- In order to force SSL requests to go to a different server, you simply need to define a virtual host based on the port, and have the different proxy settings on each virtual host. This way you can have the same URL scheme in both circumstances. Just in case someone wondered, proxying to an HTTPS backend does not usually work, as mod_proxy does not understand HTTPS (or I have been unable to get it to work ;) ).

.- Mod_proxy just works for "directory wide" settings, but if you want to proxy certain files (*.jsp, *.myapp for example), mod_redirect can be used with the P or PT settings, or it can be used in combination with mod_proxy.

.- The 503 and 502 error status can also be helpful if you intercept them and show something along the lines of "application is being mantained, sorry for the inconveniece... blah blah" This way, when in maintenance mode you simply stop your container or change the port in the proxy setting & reload apache config and the message is displayed automatically.
...
Regarding mod_proxy vs mod_jk, we use mod_proxy because we can then use the containers we want, mixing versions, changing from one version to another, from one container vendor to another...without bothering with the native libraries required for each container/version. Yes, some performance is lost, but in our case (20+ applications) flexibility is preferred. Other people might have other preferences.

Hey Matt et al: I have an issue where I'm running a Facelets app on Tomcat with no problem. After implementing the proxy from Apache some CSS and Javascript files are not loaded. In FireFox nothing from RichFaces is loaded (CSS or JS). In Safari, my images, my CSS, and RichFaces are not loaded.

I tried to implement mod_proxy_html but I'm having difficulty because it requires libxml2.so and this file is not on my machine (MacBook Pro - OS X 10.5.6). I was able to build mod_proxy_html with like this:

apxs -c -I/usr/include/libxml2 -i mod_proxy_html.c

But libxml2.so needs to be loaded in httpd.conf before mod_proxy_html.

retry : Connection pool worker retry timeout in seconds. If the connection pool worker to the backend server is in the error state, Apache will not forward any requests to that server until the timeout expires. This enables to shut down the backend server for maintenance, and bring it back online later. A value of 0 means always retry workers in an error state with no timeout. Default value is 60 (seconds).

Example is:

ProxyPass /myapp http://localhost:8080/myapp retry=5

Forces ProxyPass to check back in 5 seconds after it detects an error state.