Nginx Library

A repository of Nginx-related articles

Hotlink protection in Nginx

Hotlinked files can be a major cause for bandwidth leeching for some sites. Here’s how you can hotlink protect your images and other file types using a simple location directive in your Nginx configuration file :

location ~ \.(jpe?g|png|gif)$ {
     valid_referers none blocked mysite.com *.mysite.com;
     if ($invalid_referer) {
        return   403;
    }
}

Use the pipe (“|”) to separate file extensions you want to hotlink protect.

The valid_referers directive contains the list of site for whom hotlinking is allowed. Here is an explanation of the parameters for the valid_referers directive :

  • none - Matches the requests with no Referrer header.
  • blocked - Matches the requests with blocked Referrer header.
  • *.mydomain.com - Matches all the sub domains of mydomain.com. Since v0.5.33, * wildcards can be used in the server names.

You can also tweak the location directive for blocking files from a specific directory. Like this :

location /images/ {
     valid_referers none blocked mysite.com *.mysite.com;
     if ($invalid_referer) {
        return   403;
    }
}

This will hotlink protect every file in any images directory.

Hide Nginx version

If you are using an old version of Nginx or if you want to explicitly hide the Nginx version in your HTTP headers and your default error pages, you can use the server_tokens directive in your Nginx configuration. The server_tokens directive can be used either in the http, server or location block.

Just set server_tokens to off (it is set to on by default) to hide your Nginx version in all externally visible places.

server_tokens off;

Just like using Apache’s ServerSignature and ServerTokens directive, your Nginx version should now be hidden in the server signature and the default error pages. To check the server headers, use the HTTP Header Checker utility from Webconfs.com.

Check Nginx version

We can retrieve the version of Nginx currently installed by calling the Nginx binary with some command-line parameters. We can use the -v parameter to display the Nginx version only, or use the -V parameter to display the version, along with the compiler version and configuration parameters.

Example outputs :

$ nginx -v
nginx version: nginx/0.8.54

$ nginx -V
nginx version: nginx/0.8.54
TLS SNI support enabled
configure arguments: --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-log-path=/var/log/nginx/access.log --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --lock-path=/var/lock/nginx.lock --pid-path=/var/run/nginx.pid --with-debug --with-http_addition_module --with-http_dav_module --with-http_geoip_module --with-http_gzip_static_module --with-http_image_filter_module --with-http_realip_module --with-http_stub_status_module --with-http_ssl_module --with-http_sub_module --with-http_xslt_module --with-ipv6 --with-sha1=/usr/include/openssl --with-md5=/usr/include/openssl --with-mail --with-mail_ssl_module --add-module=/build/buildd/nginx-0.8.54/debian/modules/nginx-upstream-fair

Nginx 1.0.5 has been released

Nginx 1.0.5 has been released.

Download it : http://nginx.org/en/download.html

The changes include :

  • Change: now default SSL ciphers are “HIGH:!aNULL:!MD5”. Thanks to Rob Stradling.
  • Feature: the “referer_hash_max_size” and “referer_hash_bucket_size” directives. Thanks to Witold Filipczyk.
  • Feature: $uid_reset variable.
  • Bugfix: a segmentation fault might occur in a worker process, if a caching was used. Thanks to Lanshun Zhou.
  • Bugfix: worker processes may got caught in an endless loop during reconfiguration, if a caching was used; the bug had appeared in 0.8.48. Thanks to Maxim Dounin.
  • Bugfix: “stalled cache updating” alert. Thanks to Maxim Dounin.

Setting up PHP FastCGI

In a previous post, we had written about setting up Perl-FastCGI on Nginx. This article is about setting up PHP-FastCGI. As always, this article assumes that you are running on Debian Squeeze (or above) and uses aptitude/apt-get for fetching and installing packages.

Install these packages :

apt-get install nginx php5-cgi

We’ll now replace Nginx’s package maintainer’s vhosts file with our own.

mv /etc/nginx/sites-available/default /etc/nginx/sites-available/default.old
vi /etc/nginx/sites-available/default

Put this in the file :

server {
    listen   [::]:80;
    server_name  example.com; # Replace this with your own
    root   /var/www/example.com;
    index  index.html index.htm index.php;
    access_log  /var/www/logs/example.com.access.log;  

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_pass   unix:/tmp/php.socket;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include fastcgi_params;
    }   
}

Now, there are some tutorials on the internet which wrongly configure Nginx and introduce an arbitrary code execution bug. Note that Nginx passes a request to the php-cgi daemon if the REQUEST_URI has a trailing .php. It does not check whether the file exists or not. Suppose a request is made for /images/dog.jpg/test.php. Nginx will match the trailing .php and pass the request to php-cgi. Now, if test.php does not exist and cgi.fix_pathinfo is set to 1, php-cgi will try to execute /images/dog.jpg with /test.php as pathinfo. This is especially dangerous if you have a public upload directory. In our configuration, this will not happen even if cgi.fix_pathinfo is set to 1. The try_files $uri =404; line checks for the existence of the .php file and returns a 404 response header if it does not exist. Alternatively, cgi.fix_pathinfo can be set to 0, but is is not required as our configuration takes care of it.

Now we’ll use a Debian init script to control the php-cgi daemon which will be running on port 9000.

vi /etc/init.d/php-fcgi

Add this in the file :

#!/bin/bash
### BEGIN INIT INFO
# Provides:          php-cgi
# Required-Start:    networking
# Required-Stop:     networking
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start the PHP FastCGI daemon.
### END INIT INFO  
BIND=/tmp/php.socket
USER=www-data
PHP_FCGI_CHILDREN=2
PHP_FCGI_MAX_REQUESTS=5000  
PHP_CGI=/usr/bin/php-cgi
PHP_CGI_NAME=`basename $PHP_CGI`
PHP_CGI_ARGS="- USER=$USER PATH=/usr/bin PHP_FCGI_CHILDREN=$PHP_FCGI_CHILDREN PHP_FCGI_MAX_REQUESTS=$PHP_FCGI_MAX_REQUESTS $PHP_CGI -b $BIND"
RETVAL=0  
start() {
      echo -n "Starting PHP FastCGI: "
      start-stop-daemon --quiet --start --background --chuid "$USER" --exec /usr/bin/env -- $PHP_CGI_ARGS
      RETVAL=$?
      echo "$PHP_CGI_NAME."
}
stop() {
      echo -n "Stopping PHP FastCGI: "
      killall -q -w -u $USER $PHP_CGI
      RETVAL=$?
      echo "$PHP_CGI_NAME."
}  
case "$1" in
    start)
      start
  ;;
    stop)
      stop
  ;;
    restart)
      stop
      start
  ;;
    *)
      echo "Usage: php-fcgi {start|stop|restart}"
      exit 1
  ;;
esac
exit $RETVAL

This can also be done in one line if you have shell access on your server :

wget http://nginxlibrary.com/downloads/php-fcgi/php-fcgi -O /etc/init.d/php-fcgi

Give the file executable permissions and make it start during booting :

chmod +x /etc/init.d/php-fcgi && insserv php-fcgi

Finally, start the fastcgi daemon :

invoke-rc.d php-fcgi start

To check that it is working properly, create this file :

vi /var/www/example.com/info.php

Put this in it :

<?php phpinfo(); ?>

Now go to http://example.com/info.php and you’ll get the PHP information page showing information about the loaded modules.

Now, there are a couple of options you can change in the PHP-FastCGI init script, namely, PHP_FCGI_CHILDREN and PHP_FCGI_MAX_REQUESTS. PHP_FCGI_CHILDREN specifies the number of child processes the php-cgi master process will spawn. Usually, two, or, even one is sufficient for a medium- traffic site. PHP_FCGI_MAX_REQUESTS specifies the number of requests each child process will serve before being terminated and respawned.

Installing the latest version of Nginx on Debian

The Nginx package in the Debian stable repositories can be quite outdated. It is often several version behind the latest, whereas the Nginx package from the testing/unstable repositories is updated frequently with newer versions of Nginx. We can use apt-pinning to get the a newer version of Nginx (the one present in the testing/unstable repositories) and it’s dependencies (if required) will be met from the testing/unstable repositories as well.

Let us first add the testing/unstable repositories to the software sources list.

vi /etc/apt/sources.list

Add these lines at the end of the file :

deb http://ftp.debian.org/debian/ testing main contrib non-free
deb-src http://ftp.debian.org/debian/ testing main contrib non-free

Replace testing with unstable if you want to add the unstable repositories instead of testing.

Now we need to pin the nginx package in testing/unstable to a higher priority so that it gets selected. Actually, in Debian, nginx is a meta-package that selects the nginx-full package. So, if you want to use nginx-light or nginx-extras, use them instead of just “nginx”.

vi /etc/apt/preferences

Add these lines :

Package: nginx
Pin: release a=testing
Pin-Priority: 900

As explained before, you can use nginx-light or nginx-extra instead of nginx here. You can also use unstable instead of testing.

Now, run :

apt-get update

Check whether the newer version of nginx is selected for install by running :

apt-cache policy nginx

Finally, install/upgrade nginx by running :

apt-get install nginx

or

apt-get upgrade nginx

You’ll have a relatively newer version of Nginx installed after finishing this.

Nginx 1.0.4 has been released

Nginx 1.0.4 has been released.

Download it : http://nginx.org/en/download.html

The changes include :

  • Change: now regular expressions case sensitivity in the “map” directive is given by prefixes “~” or “~*”.
  • Feature: now shared zones and caches use POSIX semaphores on Linux. Thanks to Denis F. Latypoff.
  • Bugfix: “stalled” cache updating” alert.
  • Bugfix: nginx could not be built –without-http_auth_basic_module; the bug had appeared in 1.0.3.

Nginx 1.0.3 has been released

Nginx 1.0.3 has been released.

Download it : http://nginx.org/en/download.html

The changes include :

  • Feature: the “auth_basic_user_file” directive supports “$apr1”, “{PLAIN}”, and “{SSHA}” password encryption methods. Thanks to Maxim Dounin.

  • Feature: the “geoip_org” directive and $geoip_org variable. Thanks to Alexander Uskov, Arnaud Granal, and Denis F. Latypoff.

  • Feature: ngx_http_geo_module and ngx_http_geoip_module support IPv4 addresses mapped to IPv6 addresses.

  • Bugfix: a segmentation fault occurred in a worker process during testing IPv4 address mapped to IPv6 address, if access or deny rules were defined only for IPv6; the bug had appeared in 0.8.22.

  • Bugfix: a cached reponse may be broken if proxy/fastcgi/scgi/ uwsgi_cache_bypass and proxy/fastcgi/scgi/uwsgi_no_cache directive values were different; the bug had appeared in 0.8.46.

Converting phpBB’s default .htaccess for nginx

phpBB ships with a small .htaccess file in the root that has rules to deny access to the config.php and common.php files in the absence of a PHP interpreter. I’ll duplicate those rules for an Nginx configuration.

Here’s how the .htaccess file looks like :

Order Allow,Deny
Deny from All

Order Allow,Deny
Deny from All

And here’s what you need to add to your Nginx configuration file in order to duplicate that behaviour :

location ~ /config.php|/common.php {
    deny all;
}

This will deny access to all instances of config/common.php located in the document root, regardless of where they are located.

Nginx 1.0.2 has been released

Nginx 1.0.2 has been released.

Download it : http://nginx.org/en/download.html

The changes include :

  • Feature: now shared zones and caches use POSIX semaphores.
  • Bugfix: in the “rotate” parameter of the “image_filter” directive. Thanks to Adam Bocim.
  • Bugfix: nginx could not be built on Solaris; the bug had appeared in 1.0.1.