Configuring Nginx as a Reverse Proxy for Hippo CMS
Jeroen Reijn
2015-04-17
Of late, there have been quite a few questions from within the Hippo CMS community on how to set up Nginx as a reverse proxy for Hippo CMS. Nginx is a high-performance HTTP and reverse proxy server (it can do a lot more though). In the Hippo CMS documentation we tend to stick to one web/reverse proxy server ( in our case Apache HTTP ), so we can streamline the documentation. However, Nginx is widely adopted and has been gaining quite some market share by large enterprises.
In this Hippo labs post, we'll go through a couple of steps and will set up a local environment where we have NGINX acting as a reverse proxy server in front of Hippo CMS.
Step One - Setting up a basic Hippo CMS installation
Before we install NGINX let's first start with a clean and basic Hippo CMS setup. To makes this as easy and reproducible as possible, let's just follow the getting started trail. For this project, I used the Hippo CMS 7.9 based archetype to generate the project. I kept the default 'myhippoproject' name for which references can be found later on in this post. After the Maven build and cargo run, I went to the Essentials setup application at http://localhost:8080/essentials and installed the news and blogs plugin, repackaged and restarted cargo before I continued with the rest of this post.
Step two - Configure Nginx
Now that we have the Hippo CMS part done, let's move on to Nginx. Since I'm running Mac OS X I'll use homebrew to install Nginx.
$ brew install nginx
In the /etc/hosts file, we will add the two domain names which will resolve to 127.0.0.1.Once the command completes, we have NGINX installed (in my case version 1.6.3). The Nginx configuration can now be found at /usr/local/etc/nginx . Let's first define some host names, which we'll use to mimic a production like environment.
127.0.0.1 cms.local.dev site.local.dev
Now with NGINX installed and our DNS entries set, we will move on to configure Nginx and register the two 'virtual hosts'.We will use the cms.local.dev domain to expose the CMS and preview site to our CMS editors and site.local.dev will be used for the actual live site. To validate both domains are working properly, you could try to ping the two domains to see if they actually resolve to your 127.0.0.1.
Putting the CMS behind Nginx
Let's first start with the CMS. To expose the CMS through Nginx, we will need to create a virtual host configuration for the domain cms.local.dev, which we will store in a file with that exact name.
I always like to use Atom as my text editor, so let's create this file from the command line with Atom by calling:
$ atom /usr/local/etc/nginx/sites-enabled/cms.local.dev
Now to do a proper setup we will have to define two locations:
- / for the CMS
- /site/ for the preview site (shown inside the CMS channel manager)
Each location will proxy to the correct web application running in Tomcat.
server {
listen 80;
server_name "cms.local.dev";
location / {
# Set headers for proxy header rewriting, like ProxyPassReverse in Apache http
# See http://wiki.nginx.org/LikeApache
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://localhost:8080/cms/;
proxy_redirect default;
proxy_cookie_path /cms/ /;
}
location /site/ {
proxy_pass http://localhost:8080/site/;
}
}
Now that we have also set up Nginx correctly, let's start Nginx:As you can see we've registered Nginx to listen on port 80, which means we will have to run Nginx later on with sudo, otherwise we can't make it listen on port 80.
$ sudo nginx
Make sure that the CMS is running properly by going to http://cms.local.dev. The final check is to make sure that the channel manager is working properly. In my case it works and I get to see the 'preview' site as expected.
Putting the 'live' site behind Nginx
Now that we have configured Nginx for the CMS correctly, let's set up the site. Because I like to keep my configuration as simple and straight forward as possible, let's create a separate Nginx virtual host configuration for the site:
$ atom /usr/local/etc/nginx/sites-enabled/site.local.dev
Now put the following content in this file.
server {
listen 80;
server_name "site.local.dev";
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://localhost:8080/site/;
proxy_redirect default;
proxy_cookie_path /site/ /;
}
}
Now the important part in this configuration are the proxy_set_header directives. They make sure that the Hippo delivery tier can understand which domain / site is being requested by the visitor. Make sure you save the new file and let's reload the nginx configuration.
$ sudo nginx -s reload
Let's see if everything is working when we browse to http://site.local.dev/
Hmm that looks a bit odd. It seems the default styling is missing. Let's see why this is happening. While looking at the HTML source you will notice that the reference to the CSS file in the HTML source code is:
<link rel="stylesheet" href="/site/css/bootstrap.css" type="text/css"/>
Now that's odd right? The reference to the CSS file still contains the /site/ context path of the web application. To make this work properly we need to register our domain in the hippo delivery tier.
Step three - Configuring the Hippo delivery tier
The reference to the /site/ application context is still in the URL because we've not yet configured the correct virtual host mapping in our Hippo HST configuration. By default the archetype generated project creates a configuration, which listens only to the localhost domain, uses port 8080 and exposes the context path of the site application in the links, so that it runs nicely when using locally with cargo at http://localhost:8080/site/. Now when using an unknown domain it by default falls back to the configuration of the localhost domain, hence it's reusing the cargo based virtual host configuration and appends the /site/ context.
To fix this we will need to go into the Hippo CMS console and navigate to:
/hst:hst/hst:hosts
To not interfere with the localhost cargo based setup and to mimic our DNS entries let's create a new virtual host group called: dev-local.
The HST configuration for the site.local.dev domain requires us to reverse the domain name and create virtual host entries for every part of the domain. So we need to create a tree with the following nodes: dev, local (child of 'dev'), site (child of 'local'). Once we're there, we can create an hst:root mount to tell the HST that this is the root of the domain. For convenience, I've exported the structure to XML, so you can just import the XML file in the CMS console at /hst:hst/hst:hosts.
<?xml version="1.0" encoding="UTF-8"?>
<sv:node xmlns:sv="http://www.jcp.org/jcr/sv/1.0" sv:name="dev-local">
<sv:property sv:name="jcr:primaryType" sv:type="Name">
<sv:value>hst:virtualhostgroup</sv:value>
</sv:property>
<sv:property sv:name="hst:cmslocation" sv:type="String">
<sv:value>http://cms.local.dev</sv:value>
</sv:property>
<sv:node sv:name="dev">
<sv:property sv:name="jcr:primaryType" sv:type="Name">
<sv:value>hst:virtualhost</sv:value>
</sv:property>
<sv:node sv:name="local">
<sv:property sv:name="jcr:primaryType" sv:type="Name">
<sv:value>hst:virtualhost</sv:value>
</sv:property>
<sv:node sv:name="site">
<sv:property sv:name="jcr:primaryType" sv:type="Name">
<sv:value>hst:virtualhost</sv:value>
</sv:property>
<sv:property sv:name="hst:showcontextpath" sv:type="Boolean">
<sv:value>false</sv:value>
</sv:property>
<sv:property sv:name="hst:showport" sv:type="Boolean">
<sv:value>false</sv:value>
</sv:property>
<sv:node sv:name="hst:root">
<sv:property sv:name="jcr:primaryType" sv:type="Name">
<sv:value>hst:mount</sv:value>
</sv:property>
<sv:property sv:name="hst:channelpath" sv:type="String">
<sv:value>/hst:hst/hst:channels/myhippoproject</sv:value>
</sv:property>
<sv:property sv:name="hst:mountpoint" sv:type="String">
<sv:value>/hst:hst/hst:sites/myhippoproject</sv:value>
</sv:property>
</sv:node>
</sv:node>
</sv:node>
</sv:node>
</sv:node>
Now within this configuration there are a couple of important definitions that differ from the 'default' setup. For the dev-local virtual host group, we define the CMS to be at the location http://cms.local.dev (see the hst:cmslocationproperty). This property is used for instance by links produced by the HST, which will point directly to the CMS.
The other two important properties are hst:showcontextpath and hst:showport. The hst:showcontextpath property is set explicitly set to false, which means links generated by the HST will not contain the web application context path. So in our case the /site/ part of the URL will be omitted, which was exactly what was breaking our styling. So now if we save our changes in the CMS Console and refresh the browser it should look like:
Conclusion
As you've seen it's not that hard to put Nginx in front of Hippo CMS. I've seen Nginx being used in combination with Hippo CMS for other scenarios as well (like SSL offloading). Setting up Nginx is not a lot of work except for setting the correct headers and the right proxy configuration. I hope that this post will help out others trying to set up Hippo CMS with Nginx. In case you see something missing are are running into issues please leave a comment.