You probably know the basic installation instructions for Liferay Bundles: „unzip and run startup.sh“ - with this you get to a working Liferay installation in a minute. It will run with all defaults - which might not be what you want in production.
This is part 3 of a series. Start with part 1 for "Introduction, Basics and Operating System Level", continue with part 2, "Liferay's configuration" and come back here. You might also want to check if more chapters are already available.
8080? I want 80!
In Chapter 1 we kept tomcat running on port 8080 and I promised that this will be mitigated later. Now is the time. Apart from port 80 we'll also cover port 443 for https access, but let's go step by step:
In order to bind to a port below 1024, an application on Unix must run as root or gain those privileges in some other way. I've already commented that this is a very bad idea for a process that is connected to the internet. In case there's any security issue that can be exploited remotely, you're toast as it's trivial to gain root access on your computer.
For this reason (and some others) I like to run a proper webserver in front of tomcat. Let's take Apache httpd for this chapter. Substitute with the one you are most familiar with. I'll abbreviate it as "Apache" for the rest of this chapter.
Apache drops the root permissions after binding to ports 80 and 443, so effectively it will not run as root. This is a trick that is easy if you run native on the operating system, but hard for a JVM process. Win: We're answering requests on port 80 without running as root. Fail: Now Apache serves our content, not tomcat - they'll need to be connected. Several options are available for this purpose
HTTP vs AJP
Apache offers mod_proxy and mod_jk (among others). They differ in the protocol that is spoken between it and tomcat. mod_proxy (to be exact: mod_proxy_http) communicates through http, while mod_jk (also to be complete: and mod_proxy_ajp) communicate with a binary protocol, named AJP.
I'm a big proponent of AJP, as it covers all of the default expectations that you have for this purpose. Assuming that you're using your distribution's Apache and you've installed mod_jk, here's what you do:
Configure some workers.properties that are pointing to your tomcat's AJP-connector. Where's that? Check your conf/server.xml file in tomcat. The default is port 8009. For the purpose of this documentation, I'm assuming that Apache is running on www.example.com, while tomcat is running on tomcat.example.com.
workers.properties:
for me, this file is /etc/apache2/workers.properties, as the next snippet refers to it.
ps=/
worker.list=tomcat1
worker.tomcat1.port=8009
worker.tomcat1.host=tomcat.example.com
worker.tomcat1.type=ajp13
worker.tomcat1.lbfactor=1
Now, how does this get into Apache?
You'll most likely have some VirtualHost configuration in Apache anyway for the server that you're building. Here's some pseudocode for general Apache configuration, as well as for the virtual hosts. On Ubuntu the next snippet might go into /etc/apache2/conf/liferay-settings.
ServerSignature Off ServerTokens ProductOnly TraceEnable Off FileETag None Options -Indexes
JkWorkersFile /etc/apache2/workers.properties
JkLogFile /var/log/apache2/mod_jk.log
JkLogLevel error
JkOptions +ForwardKeySize +ForwardURICompat -ForwardDirectories +ForwardURICompatUnparsed
NameVirtualHost your-ip-address:80 NameVirtualHost *:80 NameVirtualHost your-ip-address:443 NameVirtualHost *:443
and a snippet from /etc/apache2/sites-available/default
<VirtualHost _default_:80>
ServerAdmin webmaster@example.com
ServerName www.example.com
DocumentRoot /srv/www/
ErrorLog /var/log/apache2/error.log
CustomLog /var/log/apache2/access.log combined
Options +MultiViews
JkMount /* tomcat1
JkMount / tomcat1
JkUnmount /static/*
</VirtualHost>
What does this do? Every request that gets to Apache's default virtual host will be forwarded to tomcat. The only exception is that requests to www.example.com/static/* will still be handled by Apache (see the JkUnmount line).
Achievement unlocked: We're answering on port 80 but still run as the unprivileged user that we've been used in chapter 1.
https anyone?
What about https? Well, not much to change. Configure Apache like you would for https anyway, add the same JkMount instructions to the virtual host. With AJP you're set: Tomcat/Liferay knows that you're communicating on https, knows the ports, host names etc.
I don't go too much into the setup of a proper https server - a lot of recommendations have changed with the issues that surfaced lately. Just so much: You might want to check your setup for the recent issues. ssllabs.com is one of the sites that offer free instant testing.
Keep your private key under tight control, get a certificate for your key, set up the virtual host and you're set: https is ready.
Should I force https?
If your site contains data (or uses passwords) that should be protected, and you offer https anyway, I believe that it's a good idea to force https on anybody. Won't this generate significant overhead on the webserver? Measure!
With the setup that we have so far, you could easily add a https-terminator into the game, or have https completely handled by your Apache. You'll need to figure out by yourself what fits your environment and load profile.
If you want to force https, just implement unconditional redirect on the VirtualHost for port 80 to the https VirtualHost, like this:
<VirtualHost _default_:80>
ServerAdmin webmaster@example.com
ServerName www.example.com
DocumentRoot /srv/www/
ErrorLog /var/log/apache2/error.log
CustomLog /var/log/apache2/access.log combined
RewriteEngine On RewriteCond %{HTTPS} off RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
</VirtualHost>
And if you know HSTS, you might want to add one line to your VirtualHost on port 443:
<VirtualHost _default_:443>
ServerAdmin webmaster@example.com
ServerName www.example.com
SSLEngine On
# further https options omitted
ErrorLog /var/log/apache2/error.log
CustomLog /var/log/apache2/access.log combined
JkMount /* tomcat1
JkMount / tomcat1
JkUnmount /static/*
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
</VirtualHost>
Naturally, this requires the required modules to be installed: Header and Rewrite* are not in the Apache core, but readily available.
What about http/https mixed mode?
This is quite a popular question. Why not use http for users that are not logged in, but use https as soon as they log in. Until recently I publicly stated that This! Does! Not! Work!. The main reason is that you'll definitely miss some setup and, sooner or later, leak some data, cookie or other information.
Recently I found a neat workaround that limits the amount of configuration errors. As soon as the next Internet Explorer is available and adopted, it might even be a viable option (all other browsers support it). You can conditionally enable HSTS just when a user logs in to Liferay. More information in the Liferay HSTS app that yours truly has published on marketplace. With this, the case for mixed mode turns a bit towards a mixed mode that I don't totally reject.
Check the description in the app for the options that it opens. Note that you'll still make your life easier with the single line I give above. But if you drive up the download numbers and give reviews for that plugin, that is very welcome ;)
Other options: mod_proxy_http
Another quite popular configuration is to communicate http to tomcat. This has some drawback, e.g. all requests to tomcat will originate on Apache, tomcat will have no idea where in the world they came from. Also, tomcat will believe that its hostname is tomcat.example.com - this is true, but in a properly firewalled network, this address will not be available from the outside. We'll need to hack this with a few more options:
If you prefer proxying through http, look up ProxyPreserveHost On, which will make the original hostname, www.example.com, available to tomcat. Also, you want to configure Liferay's portal-ext.properties to have the proper ports. Check this in the original portal.properties that you already read during the previous chapter:
#
# Set the HTTP and HTTPs ports when running the portal in a J2EE server that
# is sitting behind another web server like Apache. Set the values to -1 if
# the portal is not running behind another web server like Apache.
#
web.server.http.port=-1
web.server.https.port=-1
(you probably want to set these ports to 80 and 443)
All of this is not necessary with AJP - everything is readily communicated to tomcat.
https and mod_proxy_http
With mod_proxy_http you'll need more work to let tomcat know that you're communicating https. You'll typically terminate the https connection on Apache and just forward to tomcat through http. For this reason tomcat doesn't know about the encryption - it never sees any encrypted connection.
A neat hack that you can use here is: Introduce another HTTP connector on tomcat that you'll purely use for proxy requests from your https virtual host. Add the secure="true" attribute to let tomcat know that the original requests on this connector have been encrypted. The relevant part of your server.xml might look like this:
<Connector executor="tomcatThreadPool"
port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" URIEncoding="UTF-8" />
<Connector executor="tomcatThreadPool"
port="8081" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" URIEncoding="UTF-8"
secure="true"/>
Now you only need to make sure that nobody but the encrypted VirtualHost on Apache does connect to 8081 and tomcat assumes that requests coming in on 8081 have indeed been encrypted - but doesn't need to handle any encryption itself.
Future chapters
- more lockdown
- a new episode of Radio Liferay on Security
- DevOps Tipps and tools for monitoring, also on Radio Liferay
...coming soon...
Remember: This is not the only - and not the complete - truth. Please add your experience (and disagreements) in the comments

