Written by Adam Yala, 5thColumn Developer

Most developers might have started with the LAMP stack and for good reason; it works extremely well and is very reliable. As developers advance in their career, they might swap PHP for another language or MySQL for another database. However, in our experience, switching webservers happens rarely. So when developers have lots of Apache experience and want to use Nginx for the first time, they find themselves a bit lost and struggling to understand the cryptic (at first) syntax.

A few days ago we found ourselves in this situation. We needed to proxy Kibana 4 through Nginx while also having a API running through Django. Finding clear documentation for this scenario was a struggle that led us to reading old Kibana pull request comments. This post hopes to prevent you from having to do that yourselves.

We’re using Ububtu/Debian 14.04.

Nginx
First we install Nginx using:
sudo apt-get update && sudo apt-get install -y nginx

We know enough from Apache and our basic networking skills that SSL listens on port 443. After Ngninx is installed, there is a default configuration file that can serve as your template.

We want to use SSL, so that starts with:

server {
   listen 443;
   server_name foo.5thcolumn.net;

   root html;
   index index.html index.htm;

   ssl on;
   ssl_certificate cert.pem;
   ssl_certificate_key cert.key;
   ssl_session_timeout 5m;
   ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
   ssl_ciphers “HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES”;
   ssl_prefer_server_ciphers on;

   location / {
      try_files $uri $uri/ =404;
   }
}

We’re using a wildcard cert so we swapped the default cert values for our own. This server will also be on the net, so we’ll change localhost to the domain we’re going to use foo.5thcolumn.net. You should change localhost to whatever domain you’re using.

If you’re familiar with Apache, this Nginx configuration should also look familiar. We’re listening on port 443 for SSL, we declare our server_name, we declare our document root, and all our ssl settings.

The only location we have is the default root /. Lets start there.

When working with multiple locations in one SSL server block, we find it useful to break the configuration down into multiple files using the include directive. Includes add the included file’s text to the configuration. We could copy and paste it all into a single file, however, making them separate files will help with maintenance.

Django
We want to start with our Django API at our root.

We’ll make an includes folder:
 sudo mkdir /etc/nginx/sites-available/includes/

We’ll then create a file in there to hold our Django configuration.
 sudo touch /etc/nginx/sites-available/includes/django.conf

What’s happening in the django.conf file?
First we declare location / { … }. This tells Nginx that the root url (https://foo.5thcolumn.net/) should include the all of the configuration options within these brakets. include proxy_params; adds the file /etc/nginx/proxy_params to this block. That file contains common headers used for reverse proxies. We then use proxy_pass to allow a proxy like Gunicorn or uWSGI to pass data through Nginx.

The contents of our django.conf looks like:

location / {
   include proxy_params;
   proxy_pass http://unix:/path/to/gunicorn/sock.sock;
}
 

Django is typically deployed via Gunicorn which creates a socket file. How to set this up goes beyond the scope of this article, but the .sock path can be swapped out for any reverse proxy tool like uWSGI.

We add the new configuration include to our default nginx.conf file which looks like this:

server {
   listen 443;
   server_name foo.5thcolumn.net;

   root html;
   index index.html index.htm;

   ssl on;
   ssl_certificate cert.pem;
   ssl_certificate_key cert.key;
   ssl_session_timeout 5m;
   ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
   ssl_ciphers “HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES”;
   ssl_prefer_server_ciphers on;

   include sites-available/django.conf;
}

Elasticsearch

REMEMBER: Elasticsearch is not secured by default. Make sure you have your Elasticsearch Shield setup before exposing it to the world.

Like the django.conf file make a elasticsearch.conf file:
sudo touch /etc/nginx/sites-available/includes/elasticsearch.conf

What’s happening in elasticsearch.conf file?
As before, the location location directive tells Nginx what to do when a request is made to https://foo.5thcolumn.net/elasticsearch/. We also have a rewrite directive. rewrite tells Nginx how to navigate and serve up a path. We can break this rewrite down into 3 parts.

  1. ^/elasticsearch/(.*), is regex that will parse the url and optionally capture any information after /elasticsearch/.
  2. $1 is backreference (match). For this location we’re capturing anything after /elasticsearch/ with REGEX even though the variable will end up empty. This syntactic sugar is just a requirement that Nginx needs when parsing urls.
  3. break tells Nginx to stop after finding a match

We also include proxy_paramsproxy_pass to the port Elasticsearch is broadcasting on. We add a couple more proxy settings to help Kiabana as it makes Ajax calls to the Elasticsearch endpoints. The

proxy_http_version 1.1;

sets the http version to one that gives us some new features introduced in 1.1. We use a “Keep-Alive” (one of these new features) because it can greatly reduce the number of new TCP connections in an Nginx SSL setup, as Nginx can now reuse its existing connections per upstream. It also exposes these endpoints for any work we may want to do down the road.

The contents of our elasticsearch.conf looks like:

location /elasticsearch
    rewrite ^/elasticsearch/(.*) /$1 break;
    include proxy_params;
    proxy_pass http://127.0.0.1:9200;
    proxy_http_version 1.1;
    proxy_set_header Connection “Keep-Alive”;
    proxy_set_header Proxy-Connection “Keep-Alive”;
 }

We add the new configuration include to our default nginx.conf file.

Kibana

Our last configuration will be for Kibana:

Like the django.conf and elasticsearch.conf files, make a kibana.conf file
sudo touch /etc/nginx/sites-available/includes/kibana.conf

What’s happening in kibana.conf file?
Unlike Elasticsearch or Django, we have to match multiple paths in Kibana. Kibana serves different files from different directories

http://127.0.0.1:5601/bundles/
http://127.0.0.1:5601/status/

We want those passed via proxy accordingly. By using parenthesis we can match multiple paths delimited by |.

While we recognize we can reduce the REGEX a bit, we like to be explicit.

We also added a basic authorization to each of these paths. This will also look familiar to people who use Apache.

We include proxy_params, our proxy pass, and pass some timeouts for safety.

The contents of our kibana.conf looks like:

location ~ (/app/kibana|/bundles/|/kibana4|/status|/plugins) {
   auth_basic “Restricted”;
   auth_basic_user_file /etc/nginx/htpasswd.pls;
   rewrite ^/kibana/(.*) /$1 break;
   include proxy_params;
   proxy_pass http://127.0.0.1:5601;
   proxy_send_timeout 600s;
   proxy_read_timeout 600s;
}

We add the new configuration include to our default nginx.conf file

Finally

Our completed nginx.conf file looks like:

server {
   listen 443;
   server_name foo.5thcolumn.net;

   root html;
   index index.html index.htm;

   ssl on;
   ssl_certificate cert.pem;
   ssl_certificate_key cert.key;
   ssl_session_timeout 5m;
   ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
   ssl_ciphers “HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES”;
   ssl_prefer_server_ciphers on;

   include sites-available/django.conf;
   include sites-available/elasticsearch.conf;
   include sites-available/kibana.conf;
}

We test the configuration by running
sudo nginx -t

This will run through all your configurations to check for basic errors like syntax and missing files/paths etc. Once we get the [OK] from nginx

We the restart the nginx service:
sudo service nginx restart

We’ve hope this will alleviate some of the headache we ran into when setting this up. We’ve included all of the files on our Git Repo.