Hosting Grails web applications using Tomcat and Nginx (our configuration)
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
.