nginx as a Reverse Proxy to Apache Tomcat

Reading time of 2179 words
11 minutes
Reading time of 2179 words ~ 11 minutes


Did you find this article helpful?
Please consider tipping me a coffee as a thank you.
Ko-fi Buy Me a Coffee
Did you find this article helpful? Please consider tipping me a coffee or three as a thank you.
Tip using Ko-fi or Buy Me a Coffee

Why a proxy?

Apache’s Tomcat is a complex beast whose primary role is to render JavaServer Pages. While it can be configured to use and serve the modern web it’s often an unnecessarily complex procedure. By using a dedicated reverse-proxy server such as nginx it allows you to separate web applications from the task of web serving.

Use a solo instance of Tomcat?

Tomcat uses XML configuration files which have annoyingly strict syntax requirements. Break the syntax with a simple typo and Tomcat will refuse to load! A proxy setup in nginx requires very little reconfiguration of Tomcat. So you can offload all your site’s configurations onto nginx and leave Tomcat to handle the web application.

Tomcat can also be unreliable at web serving. I personally had all kinds of stability issues when using Tomcat with HTTPS. Speaking of which Tomcat uses the Java keystore for SSL certificates which is needlessly complicated.

Finally nginx will serve static files such as images, CSS, Javascript much faster than Tomcat. Plus it is a simpler task in nginx to tweak client browser settings such as compression, cache and protocol support.

Apache HTTP Server?

nginx proponents say it is faster and less resource intensive than HTTP Server. I find nginx is easier to use and configure plus it often has a quicker turnaround for the implementation of newer protocols.

The bad of nginx

My biggest gripe with nginx is the documentation which at best is inconstant. It’s separated into four products. The spartan nginx documentation page; the more professional Nginx Admin Guide and Tutorial that’s geared for paying Plus customers; an intimidating wiki that offers an endless collection of configuration snippets and best practises; and the rather unloved FAQ page.

Parts of the documentation can be obnoxious such as this gem from the commonly cited Pitfalls and Common Mistakes page that doesn’t endear new users to the product.

NEVER use 777. It might be one nifty number, but even in testing it’s a sign of having no clue what you’re doing.

I used a book to familiarise myself with nginx before hitting the online documentation. Nginx HTTP Server Second Edition from PACKT was a useful read but there are plenty of other great publications covering the product.

Install Tomcat

If you already have a working setup of Tomcat you can skip this part. The rest of this article will reference a clean install of Tomcat serving on port 8080.

Otherwise install Tomcat, either version 8 or 9 will do.

sudo apt-get install tomcat8
sudo service tomcat8 status

Make sure Tomcat is running.

sudo service tomcat8 start

Display the version installed.

/usr/share/tomcat8/bin/version.sh

Take note of your server’s hostname.

hostname

If no value is returned, use the -I option.

hostname -I

Install links and use it to test and view the Tomcat welcome page.

sudo apt-get install links

Make sure to replace ubuntu with the value returned from hostname.

links http://ubuntu:8080

Press the Q key to quit links.

links It worlds !

Pinning the correct repository

There are two ways to install nginx, either using a package hosted on the Ubuntu software repository or on the nginx.org repository. Each package uses different configurations and paths so it is best not to mix the two.

The Ubuntu package uses a Ubuntu like configuration where it incorporates subdirectories such as sites-enabled, sites-disabled, snippets. But I it think it’s better to use the nginx.org hosted package as its paths and configurations are what the nginx reference materials use.

cd ~
wget http://nginx.org/keys/nginx_signing.key
sudo apt-key add nginx_signing.key

Discover and take note of the the Ubuntu codename.

$ lsb_release -sc

wily

Then edit the apt sources list.

sudo nano /etc/apt/sources.list

Nginx has two branches of releases. The Stable branch and the Mainline branch. Despite the naming convention nginx recommends you deploy the Mainline branch over the Stable.

We recommend that in general you deploy the NGINX mainline branch at all times.

To use the Mainline, insert this snippet at the bottom of your sources.list.

Don’t forget to replace codename with your Ubuntu codename, ie: bionic for 18.04 LTS.

# nginx mainline repository
deb http://nginx.org/packages/mainline/ubuntu/ codename nginx
deb-src http://nginx.org/packages/mainline/ubuntu/ codename nginx

Save, exit and update your repositories.

nano sources.list
sudo apt-get update

Unfortunately we now have a conflict with the package sources. Both nginx.org and the Ubuntu repositories offer a nginx package.

We need to create a policy to force apt to only use the nginx.org repository for the nginx package.

sudo nano /etc/apt/preferences.d/nginx

Add the following snippet and save the file.

Package: nginx
Pin: origin nginx.org
Pin-Priority: 900

You can learn more about Apt pinning at ubuntuusers.de and help.ubuntu.com.

Update apt.

sudo apt-get update

It should now only install the nginx packages sourced from nginx.org.

Install nginx

Let us install nginx.

sudo apt-get install nginx

Test for its version.

nginx -v

Then test the default configuration. Note nginx needs to be started using a root account, so this test should fail.

nginx -t

But this should pass.

sudo nginx -t

Make sure nginx is running.

sudo service nginx start

Test nginx’s web serving on port 80. Again don’t forget to replace ubuntu with the value returned from hostname.

links http://ubuntu
Welcome to nginx!

Basic proxy

With that all working we currently have two web sites being served. The Tomcat 8 welcome page hosted on port 8080 and the nginx welcome page on the default HTTP port 80. Both are being served by two different pieces of software.

By using a simple reverse-proxy configuration we will join nginx with Tomcat. Web browsers will connect to nginx on port 80 but instead of receiving the nginx welcome page. They will receive the welcome page served by Tomcat.

Disable the default nginx configuration.

cd /etc/nginx/conf.d/
sudo mv default.conf default.conf~

Create a new nginx configuration file.

sudo nano tomcat-proxy.conf

Add the following snippet.

server {
   listen 80;
   server_name example.com;
   location / {
     proxy_pass http://127.0.0.1:8080/;
   }
}

Save and exit. Then test your new configuration.

sudo nginx -t

If there are no problems, tell nginx to reload your new configuration.

sudo nginx -s reload

Finally test your changes in the links browser. You should see the Tomcat 8 welcome page despite being connected to nginx.

links http://ubuntu
Header info

Setup nginx to serve all static content

In the previous code nginx acted as a man in the middle. It passed on all HTTP requests from client browsers to Tomcat and vice versa. This is fine but it does underutilise the potential of nginx which offers better performance for serving static files.

So in this snippet we add some new directives to tell nginx to handle all client browser requests except for dynamic JSP files which will be processed by Tomcat. You could also use this technique to instead pass on files for other Java based languages such as Lucee/ColdFusion CFM or Groovy GSP.

sudo nano /etc/nginx/conf.d/tomcat-proxy.conf

Replace the content of the configuration with this snippet.

server {
  listen 80;
  server_name example.com;
  root /var/lib/tomcat8/webapps/ROOT/;
  index index.jsp index.html index.htm;
  location / {
    try_files $uri $uri/ =404;
  }
  location ~ \.jsp$ {
    proxy_pass http://127.0.0.1:8080;
  }
  location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
    expires 1y;
  }
}

Save, test and reload and you’re done.

sudo nginx -t
sudo nginx -s reload

Some additions to note.

The location / {} directive now searches for the existence of files in the directory set in the root directive. If no file is found a HTTP 404 error is returned to the client browser. The root directive should be the same as the root path used by Tomcat.

The location ~\.jsp$ {} directive tells nginx to forward files with the extension .JSP to Tomcat. This means nginx will handle all other files requested by the client browser.

Finally the location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {} directive tells nginx to implement client browser caching for all files that match these extensions and to expire that cache after 1 month.

HTTPS

With free services such as the Let’s Encrypt from the Linux Foundation there are fewer reasons not to offer client browser encryption for your websites. As when compared to the Tomcat implementation, adding HTTPS support to nginx is a breeze. It uses standard CRT (binary DER or text based CER) certificates and only requires a few lines of modification to your configuration.

listen 80;
server_name example.com;

Is replaced with the following. Note don’t forget to replace your_server.crt and your_server.key with the actual names of your certificate and key files.

listen 80;
listen 443 ssl;
ssl_certificate /etc/ssl/your_server.crt;
ssl_certificate_key /etc/ssl/your_server.key;
server_name example.com;

SPDY and HTTP/2

One of the benefits of using nginx over Tomcat to handle client browser requests is its out of the box support of newer web protocols such as SPDY and HTTP/2.

Due to protocol incompatibilities nginx will either support SPDY or HTTP/2 but not both. nginx only supports HTTP/2 on version 1.9.5 or later. While nginx version 1.94 and earlier only supports SPDY. Most web browsers only allow HTTP/2 or SPDY support over a HTTPS connection.

To add HTTP/2 support to nginx 1.95+ append HTTP2 to the listen 443 SSL directive.

listen 443 ssl http2;

Or to add SPDY support to nginx <1.94 append SPDY to the listen 443 SSL directive.

listen 443 ssl spdy;

Save, test and restart.

sudo nginx -t
sudo service nginx restart

To test SPDY or HTTP/2 support in a browser many people find the HTTP/2 and SPDY indicators for Firefox and for Chrome useful.

Test install to this point.

Request Parameter Results

A common problem when implementing a reverse-proxy is that some parameters values returned by the request scope in a web application will be wrong.

To show a practical example create a new JSP.

sudo nano /var/lib/tomcat8/webapps/ROOT/test.jsp

Copy and paste the following JSP snippet, save and exit.

<html>
  <body>
   Request: <%= request.getRequestURI() %><br>
   Protocol: <%= request.getProtocol() %><br>
   <h4>Remote</h4>
   Host: <%= request.getRemoteHost() %><br>
   Address: <%= request.getRemoteAddr() %><br>
   Scheme: <%= request.getScheme() %><br>
  </body>
 </html>

View the saved JSP in a browser.

links http://ubuntu/test.jsp

Displayed is a number of commonly used request scope values except they are incorrect! Tomcat expects these values to originate from the client connection but in fact they are from the internal connection to nginx.

http results

To correct this we need to set pass on the values obtained by nginx over to Tomcat using custom HTTP Headers.

sudo nano /etc/nginx/conf.d/tomcat-proxy.conf

Find the following location directive.

location ~ \.jsp$ {
   proxy_pass http://127.0.0.1:8080;
}

Replace it with the following.

location ~ \.jsp$ {
   proxy_pass http://127.0.0.1:8080;
   proxy_set_header Host $host;
   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
   proxy_set_header X-Real-IP $remote_addr;
   proxy_set_header X-Forwarded-Proto $scheme;
}

Save, exit, test and reload.

sudo nginx -t
sudo nginx -s reload
links http://ubuntu/test.jsp

If you display the test.jsp after reloading nginx you will see there are no changes! As Tomcat hasn’t been told it’s behind a reverse-proxy and needs to change the source of some of its request values.

cd /etc/tomcat8/
sudo cp server.xml server.xml.original
sudo nano server.xml

If you’re using an unmodified server.xml configuration press Ctrl+_+C and then type in 109 to go to said line.

Otherwise manually scroll down until you find the container.

<Engine name="Catalina" defaultHost="localhost">

Replace it with the following snippet.

<Engine name="Catalina" defaultHost="localhost">
    <Valve className="org.apache.catalina.valves.RemoteIpValve"
         internalProxies="127\.0\.[0-1]\.1"
         remoteIpHeader="x-forwarded-for"
         requestAttributesEnabled="true"
         protocolHeader="x-forwarded-proto"
         protocolHeaderHttpsValue="https"/>

This Value Component tells Tomcat to use the built-in Remote IP Valve feature to replace the remote host, address and scheme values.

Engine element

Save, exit and restart Tomcat.

sudo service tomcat8 restart

Now if you point your desktop browser to /test.jsp you should see that the host, address and scheme are now correct.

Note using links http://localhost/test.jsp will not reflect these changes. As Tomcat will now associate a localhost request as one sourced from an internal proxy.

What you still won’t see correctly updated is the Protocol request.getProtocol() reply which is still sourcing its value from the internal proxy. You should be aware of this and other potential misinformation if your web application uses the request parameters.

A work-around is to tell nginx to pass on values using additional x-custom headers and directly access these in your web application.

Append the following custom header to the location ~ \.jsp$ {} directive in the tomcat-proxy.conf configuration.

proxy_set_header X-Server-Proto $server_protocol;

Save, test and reload nginx.

sudo nginx -t
sudo nginx -s reload

Then update test.jsp to use the custom header value by replacing this line.

Protocol: <%= request.getProtocol() %><br>

With this.

Protocol: <%= request.getHeader("x-server-proto") %><br>
Request result

Reload /test.jsp in your browser and you should see that X-Protocol returns the protocol connection from the web browser.

You can use this proxy_set_header technique to pass on any nginx variables to your web application.

You could use this to pass on nginx’s local time.

proxy_set_header X-Server-Time $time_iso8601;

Or this to pass on nginx’s version.

proxy_set_header X-Server-Ver $nginx_version;

The naming conventions for the x-custom headers are left up to you.

All done!

I hope everything has worked as intended. If you found this guide useful please thumbs up this article at the top of this page or even share it on your own social media. Any comments or questions can be left below to which I will happily respond.

Congratulations you have reached the end of this extensive guide. I hope this article useful, and if so, why not buy me a coffee as a thankyou ? ☕

Written by Ben Garrett

Did you find this article helpful?
Please consider tipping me a coffee as a thank you.
Ko-fi Buy Me a Coffee
Did you find this article helpful? Please consider tipping me a coffee or three as a thank you.
Tip using Ko-fi or Buy Me a Coffee