Intro

To host Grails web application Apache Tomcat is quite a natural choice. However a big part of what is served are just static files (images, CSS, javascript code) or rarely changed pages. Using Tomcat to serve all that stuff would be a waste of resources, especially when running on small VPS like we do.
Nginx is a small efficient web server that helps with this task. It can be setup to both serve static files and act as reverse proxy to Tomcat running the Grails application. Even more, it can cache some of the web pages so that it sends less requests to Tomcat.
In this post I’ll highlight important parts of Nginx and Tomcat configuration files needed to achieve setup like this.

Nginx configuration

For Nginx configuration you can start from its default config file. Let’s add following to http section:

# Enable GZip compression
gzip                on;
gzip_http_version   1.1;
gzip_min_length     1000;
gzip_buffers        16 8k;
gzip_disable        "MSIE [1-6] \.";
gzip_types          text/html text/css text/xml application/x-javascript application/atom+xml text/plain
gzip_vary           on;

# Set proxy cache path
proxy_cache_path /var/nginx/cache keys_zone=one:10m;

# Main website Tomcat instance
upstream main {
    server localhost:8080;
}

This will enable GZip compression, set the path which we want to use for cache and set the Tomcat instance to proxy requests into.

Then it is needed to specify the configuration for the particular server. For this example virtual domain names will be used to define server (as it is most common case).

# iPad Sketchbook website server
server {
    listen       80;
    server_name  www.ipadsketchbook.com;

    # Redirect to appropriate language
    location = / {
        set_from_accept_language $lang en ru;
        rewrite ^/$ /$lang redirect;
    }

    location / {
        # Rewrite the URL for logged-in users
        if ($http_cookie ~* "JSESSIONID=([A-Z0-9]*)") {
            rewrite ^(.*)$ /loggedIn$1 last;
        }

        # Proxy all the requests to Tomcat
        proxy_pass	http://main;
        proxy_set_header  Host $http_host;

        proxy_cache one;
        proxy_cache_min_uses 1;
        proxy_cache_valid  200 302 1m;
        proxy_cache_valid  404 1m;
        proxy_cache_use_stale error timeout invalid_header http_500 http_502 http_503 http_504;
    }

    # Content for logged-in users should not be cached
    location /loggedIn {
        # Rewrite URL back before forwarding request to backend
        rewrite ^/loggedIn(.*)$ $1 break;

        proxy_pass	http://main;
        proxy_set_header  Host $http_host;

        if ($http_cookie !~* "JSESSIONID") {
            return 404;
        }
    }

    # Admin panel should not be cached
    location /admin {
        proxy_pass	http://main/admin;
        proxy_set_header  Host $http_host;
    }

    # Serve static resources
    location ~ ^/(images|css|js|google|yandex|y_key) {
        root /var/www/vhosts/ipadsketchbook.com/httpdocs;
    }

    error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
    }
}

There are several tricks in the aforementioned config:

  • it redirects to appropriate language version based on request headers. It is only possible if you install nginx_accept_language_module.
  • it rewrites request path for users with existing session (based on JSESSIONID cookie), so that the pages served for them aren’t cached
  • it then rewrites back request path (for logged in users), when issuing request to Tomcat
  • there is a list of path prefixes for which static files are served

Of course most likely there should also be redirect from other domains set up. It is quite easy to do:

# Redirect all the iPhone Sketchbook website domains to main domain
server {
    listen          80;
    server_name     ipadsketchbook.com ipad-sketchbook.com www.ipad-sketchbook.com;
    rewrite ^(.*)   http://www.ipadsketchbook.com$1 permanent;
}

Tomcat configuration

Of course in addition to configuring Nginx, Tomcat itself need to be configured. This is quite straightforward though. Note that only single domain name is configured, as other ones are redirected by Nginx.

So in server.xml we have such host configuration:

<Host name="www.ipadsketchbook.com" appBase="vhosts/ipadsketchbook.com"
    unpackWARs="true" autoDeploy="false"
    xmlValidation="false" xmlNamespaceAware="false">
</Host>

Note that autoDeploy is disabled, it is not needed on production server. appBase specifies the path to directory which contains applications' WAR files. Root of the domain would be served by ROOT.WAR.

Further reading