Skip to content
gegron edited this page Apr 9, 2013 · 56 revisions

First h1 (will be ignored)

Workshop Agenda

1. First contact - Discover Nginx configuration and installation

2. Virtual Hosting - Define your virtual host

3. SSL/TLS Support - Enable SSL support for your site

4. Logging and Monitoring - How-to setup your access and error logs

5. Site or location protection - IP based access rules and basic auth

6. Reverse Proxy - Use Nginx in front off another webserver

7. Rewrite Rules - Short test of the rewrite engine

8. Proxy cache - Define cache headers, and use Nginx as a proxy cache


1. First contact

Default page browsing

Connect to your Nginx EC2 instance : http://www-nginx-1.aws.xebiatechevent.info/ You should see the default page.

Discover Nginx installation

Download the ssh key https://s3-eu-west-1.amazonaws.com/xfr-workshop-nginx/nginx-workshop.pem (for a ssh access via putty see tutorial here, the pem file must be converted to ssh key). Now connect using ssh to your instance using it:

ssh -i nginx-workshop.pem ec2-user@www-nginx-1.aws.xebiatechevent.info

As mentioned in the default page, nginx configuration resides in /etc/nginx and default site root is in /usr/share/nginx/html.

See nginx running process:

ps faux | grep -e nginx

You should see:

[ec2-user@ip-10-234-143-146 ~]$ ps faux | grep -e nginx
ec2-user 20174  0.0  0.1 103452   832 pts/0    S+   17:06   0:00              \_ grep -e nginx
root     17390  0.0  0.3  96040  1908 ?        Ss   Nov01   0:00 nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf
nginx    17392  0.0  0.5  96496  3084 ?        S    Nov01   0:00  \_ nginx: worker process                   

So Nginx is running as root and has one worker running as nginx user.


2. Virtual Host

Since we have specific domain name for our instance let's specify it to Nginx.

First create a separate root directory and index file to ensure vhost is working.

sudo mkdir /opt/xebiatechevent.info
sudo chown -R nginx /opt/xebiatechevent.info
sudo vim /opt/xebiatechevent.info/index.html

Just put some text in index.html so you can see the result in your browser.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title>Xebia workshop nginx vhost</title>
</head>
<body>
<h1>That works !</h1>
</body>
</html>

Now create the vhost configuration file

sudo vim /etc/nginx/conf.d/www-nginx-1.aws.xebiatechevent.info.conf

Content should be:

server {
 listen 80;
 server_name www-nginx-1.aws.xebiatechevent.info;
 root /opt/xebiatechevent.info;
 index index.html;
}

Now reload nginx configuration.

sudo /etc/init.d/nginx reload

Reload the browser url http://www-nginx-1.aws.xebiatechevent.info/. You should now see your new index.html page instead of default page.

Directives documentation


3. SSL

Refer to Documentation SSL/TLS configuration

Certificate generation

Generate key

 cd /etc/nginx/conf.d/ && sudo openssl genrsa -des3 -out xebiatechevent.key 1024

Create a signing request (to send to certification authority)

 sudo openssl req -new -key xebiatechevent.key -out xebiatechevent.csr

Remove Passphrase from key

 sudo cp xebiatechevent.key xebiatechevent.key.org && sudo openssl rsa -in xebiatechevent.key.org -out xebiatechevent.key

Generate a self-signed certificate

sudo openssl x509 -req -days 365 -in xebiatechevent.csr -signkey xebiatechevent.key -out xebiatechevent.crt

Configure SSL in vhost

Ok, now we have a self-signed certificate and a private key. We can add ssl support to our host. So edit your www-nginx-1.aws.xebiatechevent.info.conf and add missing directives to listen on port 443, activate ssl on it and reference private key and self-signed certificate.

Now open https://www-nginx-1.aws.xebiatechevent.info/ in your browser, you should have a secured access to the index page.


4. Logging and Monitoring

Separate access and error logs

Since we have a separate virtual-host, real world application would need separate logging. Using the access_log, setup a separate access_log file for your vhost.

It should be stored in /var/log/nginx/www-nginx-1.aws.xebiatechevent.info.access.log.

Add a separate error log for this vhost using error_log directive, and store it in /var/log/nginx/www-nginx-1.aws.xebiatechevent.info.error.log.

Reload configuration:

sudo /etc/init.d/nginx reload

Check that your logs are working:

tail -f /var/log/nginx/www-nginx-1.aws.xebiatechevent.info.*

Then reload the page, you should see something like:

==> /var/log/nginx/www-nginx-1.aws.xebiatechevent.info.access.log <==
82.230.161.212 - - [03/Nov/2012:13:23:02 +0000] "GET / HTTP/1.1" 200 225 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.79 Safari/537.4"

==> /var/log/nginx/www-nginx-1.aws.xebiatechevent.info.error.log <==
2012/11/03 13:23:02 [error] 18249#0: *32 open() "/opt/xebiatechevent.info/favicon.ico" failed (2: No such file or directory), client: 82.230.161.212, server: www-nginx-1.aws.xebiatechevent.info, request: "GET /favicon.ico HTTP/1.1", host: "www-nginx-1.aws.xebiatechevent.info"

==> /var/log/nginx/www-nginx-1.aws.xebiatechevent.info.access.log <==
82.230.161.212 - - [03/Nov/2012:13:23:02 +0000] "GET /favicon.ico HTTP/1.1" 404 570 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.79 Safari/537.4"

Status monitoring

Ok having separate log files is a good point, but if we want to monitor vhost status and let's say use graphite or cacti to provide some nice graphs.

Nginx has a module for that called stub status, so let's add a location to display status in our vhost.

Edit your vhost config file and add a location called /nginx-status to display server stats.

 location /nginx-status {
   stub_status on;
   access_log   off;
 }

Reload server configuration and load the location in your browser https://www-nginx-1.aws.xebiatechevent.info/nginx-status.


5. Site or location protection

Host based access

Ok we have /nginx-status displaying server statistics, but it can be viewed by anyone. Now we want to protect this location so only localhost/local ip can access it.

Using the HttpAccessModule protect this location. See how access granting differs from Apache just simpler isn't it ? Be sure to grant the host ip or make www-nginx-1.aws.xebiatechevent.info refer to 127.0.0.1 in /etc/hosts.

Reload the location in your browser, you should have a 403 forbidden page instead of the statistics:

Now load it from the host using curl:

curl http://www-nginx-1.aws.xebiatechevent.info/nginx-status 
Active connections: 1 
server accepts handled requests
 44 44 138 
Reading: 0 Writing: 1 Waiting: 0 

6. Reverse proxy

For the lab purpose we deployed a cocktail site on a different EC2 instance accessible through http://xfr-cocktail-nginx-1.aws.xebiatechevent.info:8080/.

We want to put our nginx instance in front of this site.

Basic proxying

In this exercise we will use Proxy Module.

Add a / location to proxy URIs to the cocktail site.

location / {
    proxy_pass        http://xfr-cocktail-nginx-1.aws.xebiatechevent.info:8080/;
    proxy_redirect    off;
     # Forwarded original Host
    proxy_set_header  Host            $host;
     # Forward original client IP 
    proxy_set_header  X-Real-IP       $remote_addr;
     # Properly feed X-Forwarded-For header
    proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
}

Now reload nginx configuration, and you should be able to browse the cocktail site through nginx:

Since the host is available from both http and https, you should send to the destination host a X-Forwarded-Proto header indicating the original scheme (use $scheme variable).

Mirror on demand

Static content should be served by Nginx because it's far more performant than any Java webservers. But while developping your site it's painfull to deploy static resources like images, js, and css to a different server. One solution can be Nginx mirror on demand feature.

Edit the vhost configuration file and add these two locations:

# First match static image files ...
location ~ \.(gif|png|jpg|jpeg)$ {
    # We consider they are stored in our root directory
    root /opt/xebiatechevent.info;
    # In case of file not found then go to fetch
    error_page 404 = @fetch;
}

# Special location for internal use only
location @fetch {
   # Client accessing this URI will get 404
  internal;
  proxy_pass           http://xfr-cocktail-nginx-1.aws.xebiatechevent.info:8080;
   # Activate proxy store mirror on demand real feature
  proxy_store          on;
  proxy_store_access   user:rw  group:rw  all:r;
  proxy_temp_path      /tmp;
   # Files stored will be persisted here to match exact uri path starting root as /
  root                 /opt/xebiatechevent.info;
}

Now reload nginx configuration, and navigate the cocktail site.

Check the root directory

ls -lr /opt/xebiatechevent.info/*
-rw-r--r-- 1 nginx root   225 Nov  2 17:57 /opt/xebiatechevent.info/index.html

/opt/xebiatechevent.info/img:
total 4
-rw-rw-r-- 1 nginx nginx 3420 Nov  2 16:32 devoxx-france-logo.jpg

The logo image is now directly served by nginx. This is not a cache, so mirrored content will never change.

Load Balancing

The basic reverse proxy is a good start point, but now we want to do real load balancing between two or more deployed cocktail application.

In fact we deployed two instances of the cocktail site on the EC2 instance:

Using the Upstream module, describe these two backend servers in an upstream block and make the ProxyPass point to the new upstream block.

Now let's add $upstream_addr and $upstream_response_time in your log directive to see how it works.

Edit /etc/nginx/nginx.conf and add a log_format custom below the main one:

log_format custom '$remote_addr - $remote_user [$time_local]  '
                '"$request" $status $body_bytes_sent '
                '"$http_referer" "$http_user_agent" '
                '$upstream_addr $upstream_response_time';

Edit the vhost config file and replace your old format used in access logs with the custom one.

When done with this all, reload nginx configuration, tail the logs and browse the cocktail site:

tail -f /var/log/nginx/www-nginx-1.aws.xebiatechevent.info.access.log
82.230.161.212 - xebia [04/Nov/2012:17:57:21 +0000]  "GET / HTTP/1.1" 200 3313 "http://www-nginx-1.aws.xebiatechevent.info/cocktail/4aT0A" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.79 Safari/537.4" 46.137.100.133:80 0.017
82.230.161.212 - xebia [04/Nov/2012:17:57:21 +0000]  "GET /img/devoxx-france-logo.jpg HTTP/1.1" 200 3420 "http://www-nginx-1.aws.xebiatechevent.info/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.79 Safari/537.4"- -
82.230.161.212 - xebia [04/Nov/2012:17:57:24 +0000]  "GET / HTTP/1.1" 200 3313 "http://www-nginx-1.aws.xebiatechevent.info/cocktail/4aT0A" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.79 Safari/537.4" 176.34.126.54:80 0.013
82.230.161.212 - xebia [04/Nov/2012:17:57:24 +0000]  "GET /img/devoxx-france-logo.jpg HTTP/1.1" 200 3420 "http://www-nginx-1.aws.xebiatechevent.info/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.79 Safari/537.4"- -

The backend IP change for each request, by default Nginx use a simple round robin, but you can specify different strategies.


7. Rewrite rules

You can do many things with rewrite rules, see documentation for some samples. It even exists a migration script to convert Apache rewrite rules to Nginx (see Winginx).

SEO likes nice uris by the /cocktail/sex-on-the-beach will be better scored for this cocktail than /cocktail/Er7py. So first browse the to existing cocktails and pickup their id's (uri pattern is /cocktail/($id)) then add two rewrite rules allowing you to use a nice uri to access the cocktail page.

Hint: Use an if statement against the cocktail's nice uri and rewrite it to the real uri.


8. Proxy cache

Documentation:

Expires header

Static content like images should be delivered with the caching headers. First see what headers we have in HTTP response for the devoxx-logo.jpg.

curl -v http://www-nginx-1.aws.xebiatechevent.info/img/devoxx-france-logo.jpg > /dev/null
> GET /img/devoxx-france-logo.jpg HTTP/1.1
> Authorization: Basic eGViaWE6cGFzc3dvcmQ=
> User-Agent: curl/7.24.0 (x86_64-redhat-linux-gnu) libcurl/7.24.0 NSS/3.12.10.0 zlib/1.2.5 libidn/1.18 libssh2/1.2.2
> Host: www-nginx-1.aws.xebiatechevent.info
> Accept: */*
> 
< HTTP/1.1 200 OK
< Server: nginx/1.2.1
< Date: Sun, 04 Nov 2012 18:45:06 GMT
< Content-Type: image/jpeg
< Content-Length: 3420
< Last-Modified: Fri, 02 Nov 2012 16:32:51 GMT
< Connection: keep-alive
< Accept-Ranges: bytes
< 

Now add expires directive to the image location previously defined (in mirror on demand).

location ~ \.(gif|png|jpg|jpeg)$ {
  root /opt/xebiatechevent.info;
  error_page 404 = @fetch;
  # Define the expiration to 31 December 2037 23:59:59 GMT Time can be defined if you want something else
  expires max;
}

Reload nginx configuration and get the logo using curl.

curl -v http://www-nginx-1.aws.xebiatechevent.info/img/devoxx-france-logo.jpg > /dev/null
> GET /img/devoxx-france-logo.jpg HTTP/1.1
> Authorization: Basic eGViaWE6cGFzc3dvcmQ=
> User-Agent: curl/7.24.0 (x86_64-redhat-linux-gnu) libcurl/7.24.0 NSS/3.12.10.0 zlib/1.2.5 libidn/1.18 libssh2/1.2.2
> Host: www-nginx-1.aws.xebiatechevent.info
> Accept: */*
> 
< HTTP/1.1 200 OK
< Server: nginx/1.2.1
< Date: Sun, 04 Nov 2012 18:49:46 GMT
< Content-Type: image/jpeg
< Content-Length: 3420
< Last-Modified: Fri, 02 Nov 2012 16:32:51 GMT
< Connection: keep-alive
< Expires: Thu, 31 Dec 2037 23:55:55 GMT
< Cache-Control: max-age=315360000
< Accept-Ranges: bytes
<

Expires and cache control are now defined in far future.

Proxy cache

We want Nginx to work as a proxy cache so it will need a directory to store cached files. First you need to create the cache directory and set proper rights.

sudo mkdir -p /var/cache/nginx/tmp
sudo mkdir -p /var/cache/nginx/xebiatechevent
sudo chown -R nginx /var/cache/nginx

We should then tell Nginx to store its cache in this directory. So edit nginx root configuration file and add the following lines in the http block.

# Proxy cache settings
proxy_cache_path /var/cache/nginx/xebiatechevent levels=1:2 keys_zone=xebiatechevent:10m max_size=100m inactive=5m;
proxy_temp_path /var/cache/nginx/tmp;

Levels defines the directory structure, keys_zone is the name of the cache to use in locations.

The final touch is to tell nginx to cache files from the upstream servers in our vhost. So we need to modify the vhost configuration file and add the following to the / location:

  • proxy_cache directive pointing to the new cache zone.
  • proxy_cache_valid directive to choose codes that can be cached 200 is a good start point.
  • proxy_cache_use_stale directive to tell Nginx when it should ensure to deliver cached content (cache update, upstream error, ...).

Since the cache is global you might use short timeout to ensure cache will be refreshed frequently.

For debugging purpose you can also add in the / location a new header describing the cache status.

add_header X-Cache $upstream_cache_status;

Reload the Nginx configuration, and browse the cocktail site. The cache directory will now contain files.

sudo ls -aulhr /var/cache/nginx/*
/var/cache/nginx/c:
total 12K
drwx------ 2 nginx nginx 4.0K Nov  4 22:13 60
drwxr-xr-x 4 nginx root  4.0K Nov  4 19:16 ..
drwx------ 3 nginx nginx 4.0K Nov  4 22:13 .

/var/cache/nginx/1:
total 12K
drwx------ 2 nginx nginx 4.0K Nov  4 22:37 62
drwxr-xr-x 4 nginx root  4.0K Nov  4 19:16 ..
drwx------ 3 nginx nginx 4.0K Nov  4 22:37 .

Using curl you can also check the cache status

curl -v http://www-nginx-1.aws.xebiatechevent.info/ > /dev/null
< HTTP/1.1 200 OK
< Server: nginx/1.2.1
< Date: Sun, 04 Nov 2012 23:16:40 GMT
< Content-Type: text/html;charset=ISO-8859-1
< Content-Length: 3313
< Connection: keep-alive
< Content-Language: en-US
< X-Served-Backend: 46.137.85.160:80
< X-Cache: EXPIRED

Here the cache was EXPIRED for the index page. Please appreciate also how the load duration differs when resource is delivered from the cache.


The End

Thanks for travelling with Xebia, we wish you a good journey in Nginx land.