Thursday April 24, 2008
Apache 2 on OS X: Configuring mod_proxy and SSL 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".
# # Proxy Server directives. # <IfModule mod_proxy.c> ProxyRequests On ProxyPreserveHost On ProxyStatus On <Location /status> SetHandler server-status Order Deny,Allow Deny from all Allow from 127.0.0.1 </Location> ProxyPass /myapp http://localhost:8080/myapp </IfModule>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 onto allow proxying via HTTPS:SSLProxyEngine on
- Follow the Using mod_ssl on Mac OS X tutorial. For "Common Name/Server Name", use "localhost". You can download the source for mod_ssl (which you need at one point during the tutorial) at http://www.modssl.org/source/.
- 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.
Forcing HTTPS for certain URLs
If you proxy requests from /myapp -> http://localhost:8080/myapp, request.isSecure() will return false. If you change it to /myapp -> https://localhost:8443/myapp, request.isSecure() will return true. I needed to figure out a way to have http://localhost/myapp go to http://localhost:8080/myapp and https://localhost/myapp to go http://localhost:8443/myapp. Even better, I wanted to configure things in a way so request.isSecure() returned the value based on the originally requested URL, not on the proxied URL. Configuration like the following would be ideal:
ProxyPass http://*/myapp http://*:8080/myapp ProxyPass https://*/myapp https://*:8443/myapp
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:
<security-constraint>
<web-resource-collection>
<web-resource-name>Secure Area</web-resource-name>
<url-pattern>/secure/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
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.
ProxyPass /myapp/secure https://localhost:8443/myapp/secure ProxyPass /myapp http://localhost:8080/myapp
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. Posted in Open Source at Apr 24 2008, 10:58:03 AM MDT 4 Comments
Search This Site
Recent Entries
- AppFuse + DisplayTag: External Sorting and Paging Example
- Spring MVC vs. JSF and The State of Spring Web
- Extensionless URLs with Java Web Frameworks
- AppFuse 2.0.2 Released
- AppFuse Light 1.8.2 Released
- Issues with AntRun Plugin and Maven
- JavaOne Parties Update
- Happy Cinco de Linko!
- Denver Weather
- Running Spring MVC Web Applications in OSGi
Posted by Abel Muiño on April 28, 2008 at 01:32 AM MDT #
Posted by Mauli on April 29, 2008 at 09:57 AM MDT #
Posted by Kai Grabfelder on April 29, 2008 at 10:37 AM MDT #
Hi Matt,
We have been using that for a while and I would add some notes:
.- 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.
S!
Posted by GreenEyed on April 30, 2008 at 12:25 AM MDT #