Commit 113b6040 by Paul B Committed by Paul

wip: adding common tips for nginx configuration

1 parent 1092a51c
Pipeline #7113662 for 113b6040 passed in 5 minutes 15 seconds
install:
bundle check || bundle install
run: install
bundle exec middleman
build: install
bundle exec middleman
---
title: Common usage of an NGINX configuration
date: 2017-02-26
tags: nginx, webserver, reliability
---
After more than 3 years trusting Nginx to serve customer traffic I decided to list here a few common use cases I came up with. First in my previous working experience where I migrated the public facing webservers from Apache to Nginx. And now for my current company where I configure and maintain most of the Nginx configurations. The following tips are **common configuration** which could easily be **reusable or useful for other people**. All the following examples are from my experience and might not be the best solution for you so please do not hesitate to comment my choices at the end of this article :).
# Conf directory organisation
Organising your nginx configuration directory is a first step to have a **clean and understandable setup**.
The following paragraph is based on the already well detailed [Debian wiki page of Nginx directory structure](https://wiki.debian.org/Nginx/DirectoryStructure).
Given the main nginx directory `/etc/nginx` and<br/>given the configuration entry file `/etc/nginx/nginx.conf`,<br/>here is my proposed directory structure for any nginx conf.
~~~ tree
/etc/nginx/
├── nginx.conf
~~~
To serve any kind of traffic you will need to define some sites (called `vhost`s in apache) in a `sites-available` directory. The sites you want to enable will need to be symlinked in the `sites-enabled` directory. This is particularly useful if you want to disable a site temporarily: just delete the symbolic link, `reload` nginx and **you are done**.
~~~
├── sites-available
│ ├── default
│ ├── site
├── sites-enabled
│ ├── default -> ../sites-available/default
│ ├── site -> ../sites-available/site
~~~
If you need to load extra configuration files automatically the good place is the `conf.d` directory which will keep all **extra configuration files loaded in alphabetic order** in the `http {}` context. This is a good place for `upstream {}` definitions for instance.
~~~
├── conf.d
│ ├── loaded_in_alpha_order.conf
~~~
You will most probably also need configuration files which are not loaded automatically but **`include`d when needed**. A `includes.d` directory is thus a good idea. Typically An example configuration file that would be stored there is explained in the [next paragraph](). E.g. if you want to include extra headers multiple times in different `location {}` blocks.
~~~
├── includes.d
│ ├── include_me_later.conf
~~~
Last but not least, as you will discover in this article: **[If is evil but `Map`s are beautiful]()**. I.e. you will also need a `maps.d` directory which will contain all of your `map` definitions. Read on to see example `map` files.
~~~
├── maps.d
│ ├── conditional_logic.conf
~~~
# Don't loose your heads
Watch out when `add(ing)_header`s.
You could think that adding headers one by one in different subsequent blocks will keep all of them, well nope it will not. This is clearly explained in the [nginx documentation](http://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header).
> There could be several `add_header` directives. These directives are inherited from the previous level **if and only if** there are no `add_header` directives defined on the current level.
If you need a common set of custom headers to use in a set of different "final" location blocks. I would thus recommend to keep them in an specific includable file in the `includes.d` conf directory.
# If is evil but Maps are beautiful
This is the first thing you usually learn when you start playing aroung with an nginx configuration: _[If is evil](https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/)._
The `if` directive of nginx is part of the `rewrite` module. So you should only use then to `return` a response or `rewrite` it. Never use if for any other directives!
If you think you really need a `if` you probably have a better solution without one. Indeed with time I found that most conditions can be solved with variables defined with the [`map` directive](http://nginx.org/en/docs/http/ngx_http_map_module.html). The `map` directive is, in a way, a kind of if condition more precisely it is a sort of `switch` - `case` statement.
Whenever you need a condition in your configuration try to define a variable that will solve this condition for you through a `map`. I recommend to organise all of your conditions in the `maps.d` conf directory where all your `map`s will live.
E.g.
# Sequence of two named Locations
After looking quickly at the Nginx documentation you could think that it is not possible to forward requests from one named location block to another named location block. However the following paragraph describes you exactly how to do that.
I found myself in a few situations where
# Minimal Accept-Language parsing (without module)
You could tell me that there is a good [nginx module](https://github.com/giom/nginx_accept_language_module) for that. However sometimes you don't especially want to recompile an nginx binary or add extra modules to it. Here is a pretty straight forward way of parsing your accept-language headers to determine your users' prefered language:
# Minimal Mobile User Agent detection
Yet another thing that could be solved by an extra module. If you prefer a few nginx configuration lines to parse your users' device and serve mobile ready content, here is what you could do:
https://gist.github.com/perusio/1326701#gistcomment-2009231
~~~ nginx
map $http_user_agent $ua_device {
default 'desktop';
~*(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge\ |maemo|midp|mmp|mobile.+firefox|netfront|opera\ m(ob|in)i|palm(\ os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows\ ce|xda|xiino/i 'mobile';
~*android|ipad|playbook|silk/i 'tablet';
}
map $ua_device $is_desktop {
default 0;
'desktop' 1;
}
map $ua_device $is_mobile {
default 0;
'mobile' 1;
}
map $ua_device $is_tablet {
default 0;
'tablet' 1;
}
~~~
# A simplified application sharding solution
Small mruby / lua block of configuration to read sharding mapping. Can be stored in a Redis database or based on a simple computation..
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!