Nando @ Aria Media

( learning Clojure ColdFusion Lucee Javascript jQuery Angular CSS Linux Apache HTML5 & etc )

Testing Nginx Config Directives

| Comments

After setting up both Railo and ColdFusion web apps behind Nginx, I wanted to prove to myself that the configuration was functioning how I thought it should. The apps worked, but there were several issues I was concerned about.

Here’s the configuration I wanted to test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
upstream    cf_servers {
  server          127.0.0.1:8500;
  keepalive       32; ## number of upstream connections to keep alive
}

server {
  server_name     domain.com;
  listen          80;
  root            /var/sites/domain-com;

  location ~* \.(?:ico|css|js|gif|jpe?g|png)$ {
      add_header Pragma public;
      add_header Cache-Control "public, must-revalidate, proxy-revalidate";
      gzip  on;
      gzip_http_version 1.0;
      gzip_vary on;
      gzip_comp_level 6;
      gzip_proxied any;
      gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
      gzip_buffers 16 8k;
      # Disable gzip for certain browsers.
      gzip_disable ~@~\MSIE [1-6].(?!.*SV1)~@~];
      expires modified +90d;
  }
  
  location / {
      proxy_pass  http://cf_servers/domain.com/;
      proxy_redirect '' /;
      proxy_http_version  1.1;
      proxy_set_header    Connection "";
      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;     ## CGI.REMOTE_ADDR
      proxy_set_header    X-Forwarded-Proto   $scheme;                        ## CGI.SERVER_PORT_SECURE
      proxy_set_header    X-Real-IP           $remote_addr;
      expires             epoch;
  }
}

First off, I had a doubt regarding how Nginx was selecting which location directive to use for each file requested from the documentation I’ve read. What I wanted to ensure is that Nginx was serving the static files, and proxying only the CFML files.

A deeper issue here is whether or not the proxy_pass directive contains a URI. If it does, you can’t use a regex to filter a location for CFML files only. A few examples will explain this better:

This configuration contains a URI in the proxy_pass directive, namely /domain.com/. You cannot use a regex in the location directive to filter the request, so in my case, the location directive simply had to point to the root ( recursively in the way Nginx works, so essentially this directive will process any file under the root ).

1
2
3
location / {
  proxy_pass  http://cf_servers/domain.com/;
}

This configuration also contains a URI in the proxy_pass directive, the trailing slash. Again, no regex in the location is possible.

1
2
3
location / {
  proxy_pass  http://cf_servers/;
}

This config does not have a URI component in the proxy_pass directive, no trailing slash and no subdirectory are present here. In this case, we CAN apply a regex filter to pass only those files we want to our application server.

1
2
3
location ~ \.(cfm|cfc|cfml)$ {
  proxy_pass  http://cf_servers;
}

The tradeoff here is that if we use a filter to ensure only CFML files are pass to our app server, then we need to additionally configure each virtual host in Tomcat’s server.xml ( assuming multiple hosts per server ) so that Tomcat can locate the files.

If you’d rather configure each server only in Nginx, as I did with my CF11 install, then another approach might be to use a regex to filter all your static files. Hence:

1
2
3
location ~* \.(?:ico|css|js|gif|jpe?g|png)$ {
  ...
}

With the essential background information out of the way, the question that arose was this. Is Nginx actually serving the static files, as I hoped, or are they all being passed to ColdFusion? The documention I found here and there seemed contradictory.

Here’s one way to test this using curl on the command line to fetch the response headers, with the output I got using the above settings:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$ curl -X GET -I http://domain.com/index.cfm
HTTP/1.1 200 OK
Server: nginx
Date: Thu, 08 Jan 2015 21:48:26 GMT
Content-Type: text/html;charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Set-Cookie: CFID=20071; Expires=Sat, 31-Dec-2044 21:48:26 GMT; Path=/; HttpOnly
Set-Cookie: CFTOKEN=8ff3e8f3dde25bcd-F33324A1-AEBC-7E8D-FEEEB610DD6412E7; Expires=Sat, 31-Dec-2044 21:48:26 GMT; Path=/; HttpOnly
Cache-Control: no-cache
Pragma: no-cache
Expires: Thu, 01 Jan 1970 00:00:01 GMT
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block

$ curl -X GET -I http://domain.com/images/some_logo.gif
HTTP/1.1 200 OK
Server: nginx
Date: Thu, 08 Jan 2015 21:50:12 GMT
Content-Type: image/gif
Content-Length: 10220
Last-Modified: Thu, 11 Dec 2014 17:18:36 GMT
Connection: keep-alive
ETag: "5489d1ec-27ec"
Expires: Wed, 11 Mar 2015 17:18:36 GMT
Cache-Control: max-age=5340504
Pragma: public
Cache-Control: public, must-revalidate, proxy-revalidate
Accept-Ranges: bytes

See the difference? The image is being cached on the browser and the headers are being set as per the static file location block, while index.cfm isn’t being cached. The expires epoch; directive tells the browser to set the expires date to 01 Jan 1970, a date in the past means don’t cache this, while the expires modified +90d; tells the browser to set the expires date 90 days from the Last-Modified date.

Another way to see these headers is via Developer Tools in Chrome ( or a comparable tool in any other modern browser ). Open a Dev Tools window while browsing the web page you’d like to check, refresh the page, go to the Network tab, and click on any file to see the headers. This approach will give you a better overview of how your Nginx settings are serving and caching each file involved in an http request.

After some thought and study, I decided to change my initial approach to caching static content to one that will help guarentee any content that I modify is served fresh from the server rather that stale from the cache. Here’s what I came up with, for now, for a particular business application that I am still developing:

1
2
3
4
5
6
7
location ~* \.(?:ico|css|js|gif|jpe?g|png)$ {
  add_header Pragma public;
  add_header Cache-Control "public, must-revalidate, proxy-revalidate, max-age=86400";
  etag on;
  gzip  off;
    expires +1d;
}

The users who access this site every day will only need to re-download static content once a day, an additional second or two once a day won’t hurt them. In the version of Nginx I’m currently using, enabling gzip disables etags. For now, I’d rather have etag enabled, so I’m disabling gzip for static content, but leaving it enabled for the dynamically generated content that is returned from the proxied app server.

Resources for further reading:

  1. https://www.mnot.net/cache_docs/
  2. http://en.wikipedia.org/wiki/HTTP_ETag
  3. http://stackoverflow.com/questions/499966/etag-vs-header-expires
  4. http://stackoverflow.com/questions/5799906/what-s-the-difference-between-expires-and-cache-control-headers
  5. https://www.owasp.org/index.php/List_of_useful_HTTP_headers
  6. http://cyh.herokuapp.com/cyh

Comments