NGinx automatic vhosts configuration with subdomains, SSL and authentication support

Jean Baptiste FAVRE

janvier 2010

<UPDATE> : this document has been updated. You still can and should read it, but target configuration is described here: NGinx automatic vhosts configuration with subdomains, SSL and authentication support - second version.</UPDATE>

La version Française est disponible ici: Configuration d'hôtes virtuels sur NGinx avec support automatique des sous-domaines, du SSL et de l'authentification.

Introduction

NGinx webserver installation, configuration and managment can be time consuming. You have to adapt configuration when adding new subdomain, new script language support and so on... Lets discover how you can make it easier: new subdomain ? create directory; make SSL mandatory ? create a symlink; make authentication mandatory ? another symlink.

Virtual hosts basis

NGinx webserver, like any modern webserver, is able to handle many virtual hosts, means that you can host many domainname on same IP adress. Choice will be done by webserver, based on informations found into HTTP request (Host field).

This is quite easy, except when you want to use SSL. In SSL, cypher negotiation occurs before HTTP request is sent. Therefore, the webserver has no way to guess which domainname you want to connect to. Starting, it does not know which SSL key he has to choose. One solution could be to create an SSL certificate with all hosted domains in it. That's not a suitable solution, since you'll have to modify your certificate each time you want to add or remove a domainname. Another solution is to use wildcard certificate (as an example for *.domain.tld). But what if you want to add *.domain.anothertld ? Simple: you can't.

Finally, you can create and maintain your own certificate but people connecting to your website will have an alert until they validate your own certificate.

For now, we assume that 1 IP address = 1 domainname.

Environment preparation

Automating vhosts managment require some organisation. Here is basically what I choose as directory tree:

tree /var/www/
domain.tld/
|-- config
|   |-- auth
|   `-- ssl
|-- logs
|   |-- access.log
|   |-- error.log
`-- www
    |-- subdir1
    |-- subdir2
    |-- subdir3
    `-- subdir4

As you can guess, Directory usage is as follow:

config
Will host all "magic" configuration:
auth
Symlinks with subdomains make authentication mandatory.
ssl
Symlinks with subdomains make SSL mandatory.
logs
You'll find here both access and error logs files for the domainname
www
Document root for web hosting. Subdomains will be created as subdirectories.

Now that we have the structure, let's have a look on how the system should behave.

NGinx installation and configuration

NGinx installation under Debian
aptitude install nginx

Following NGinx configuration should only be used for tests. It certainly won't scale well, as well as won't fit your needs. But it's pretty simple which is always a good thing to understand what happen.

NGinx Configuration
user www-data;
worker_processes  1;

error_log  /var/log/nginx/error.log;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format vhosts '$host $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"';

    access_log  /var/log/nginx/access.log;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;
    tcp_nodelay        on;

    gzip  on;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Notice log_format option: I just added $host field so that I can see in log file which vhost has been called. This is important since there will be only one log file per domain and every associated subdomains.

domain.tld configuration

Lets continue with domain.tld configuration. Add following lines into file /etc/nginx/sites-available/mondomaine.com:

domain.tld configuration
server {
    listen   80;
    server_name  mondomaine.com *.mondomaine.com;

    access_log  /var/www/mondomaine.com/logs/nginx_access.log vhosts;
    error_log  /var/www/mondomaine.com/logs/nginx_error.log;

    location / {
        root   /var/www/mondomaine.com/www/;
        index  index.html index.htm;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /var/www/nginx-default;
    }
    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    location ~ /\.ht {
        deny  all;
    }
}

Here again, I modified access_log option so that it will use globally defined format vhosts.

domain.tld HTTPS configuration
server {
    listen   443;
    server_name  mondomaine.com *.mondomaine.com;

    access_log  /var/www/mondomaine.com/logs/nginx_access.log vhosts;
    error_log  /var/www/mondomaine.com/logs/nginx_error.log;

    root   /var/www/mondomaine.com/www/;

########## SSL Directives
    ssl  on;
    ssl_certificate  /var/www/mondomaine.com/config/mondomaine.com.pem;
    ssl_certificate_key  /var/www/mondomaine.com/config/mondomaine.com.pem;
    ssl_session_timeout  5m;
    ssl_protocols  SSLv2 SSLv3 TLSv1;
    ssl_ciphers  ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
    ssl_prefer_server_ciphers   on;

    location / {
        root   /var/www/mondomaine.com/www/;
        index  index.html index.htm;
    }
}

I won't detail SSL certificate creation here.

domain.tld NGinx activation
cd /etc/nginx/sites-enabled/
ln -s /etc/nginx/sites-available/mondomaine.com
NGinx restart
/etc/init.d/nginx restart

Default vhost

A default vhost is like other vhosts, except that It should deal with all requests for which webserver does not find specific vhost. Just for that, it must be defined before others vhosts.

Create /etc/nginx/site-available/0000-default file and add following configuration:

Default vhost configuration
server {
    listen 80 default;
    server_name _;
    rewrite ^(.*)$ //www.domain.tld$1 permanent;
}

With this example, I assume you've your own dedicated server. You can safely redirect all traffic against you website. As an example, all request to //server_ip_addr will be redirect to //www.domain.tld/

Then you have to enable vhost:

Default vhost activation
cd /etc/nginx/sites-enabled
ln -s /etc/nginx/sites-available/0000-default
NGinx restart
/etc/init.d/nginx restart

Behaviour rules

Now that we have a basic working configuration, we can have a look on rewrites rules:

We need to host domain domain.tld.

  1. domain.tld must be redirected to www.domain.tld.
  2. We want to easily deal with subdomains. As an example:
    • blog.domain.tld shal be reachable with SSL. Authentication is integrated into application.
    • webmail.domain.tld must be reachable with SSL only. Authentication is integrated into application.
    • protected.domain.tld requires authentication, can be reached without SSL.
    • masteroftheworld.domain.tld require authentication and SSL.
  3. As we alos registered domain.anothertld, we want to have both domains using same hosting space.

For last point, you'll use ServerAlias option. If you forget that, then requests to domain.anothertld will be managed by default vhost.

Rewrite Rules

Now that we have our behaviour rules, we can now work on the implementation.

Each request to //domain.tld will be redirected to //www.domain.tld

This avoid duplicate content.

Rewrite rules 1
if ($host ~* ^domain\.tld$) {
    rewrite ^(.*) //www.domain.tld$1 permanent;
    break;
}
Every request to //sslsubdir.domain.tld will be redirected to https://sslsubdir.domain.tld

You just have to make sure that directory /var/www/mondomaine.com/www/sslsubdir exists and that symlink sslsubdir exists in /var/www/mondomaine.com/config/ssl/.

Rewrite rules 2
if ($host !~* ^www\.domain\.tld$) {}
if ($host ~* ^(.*)\.domain\.tld$) {
    set $ssl_subdomain $1;
}
if (-e /var/www/domain.tld/config/ssl/$ssl_subdomain) {
    rewrite ^(.*) https://$ssl_subdomain.domain.tld$1 permanent;
    break;
}
Every request to //www.domain.tld/subdir will be redirected to //subdir.domain.tld/

Here again, you have to avoid duplicate content. Just have to make sure that directory /var/www/domain.tld/www/subdir exists.

Rewrite rules 3
if ($host ~* ^www\.domain\.tld$) {}
if ($uri ~* ^/$) {
    set $dir_subdomain "www";
    set $dir_filename "";
}
if ($uri ~* ^/([^/]+)(.*)$) {
    set $dir_subdomain $1;
    set $dir_filename $2;
}
if (-d /var/www/domain.tld/www/$dir_subdomain) {
    rewrite ^(.*)$ //$dir_subdomain.domain.tld$dir_filename permanent;
    break;
}
Every request to //subdir.domain.tld will be rewritten in /var/www/domain.tld/www/subdir.

You just need /var/www/domain.tld/www/subdir to exist.

Rewrite rules 5
if ($host !~* ^www\.domain\.tld$) {}
if ($host ~* ^([^.]+)\.domain\.tld$) {
    set $auto_subdomain $1;
}
if (-d /var/www/domain.tld/www/$auto_subdomain) {}
if (-f /var/www/domain.tld/www/$auto_subdomain$uri) {
    rewrite ^(.*)$ /$auto_subdomain$uri;
    break;
}

You certainly noticed there is no fourth rewrite rules. That's normal because this rule will handle authentication and we're just about to talk about that.

Authentication configuration

It's now time to protect some directories access. We have to create users first:

User creation
htpasswd -c /var/www/domain.tld/config/htpasswd user1

For next users:

Next users creation
htpasswd /var/www/domain.tld/config/htpasswd usern

Please not deletion of -c option. If you maintain it, you will override authentication file.

Finall, we just have to setup authentication and define rewrite rule #4:

Authentication configuration
location ~* /auth {
    root   /home/www/domain.tld/www/;
    auth_basic            "Restricted";
    auth_basic_user_file  /home/www/domain.tld/config/passwords;
}
Rewrite rules 4
if ($host ~* ^([^.]+)\.domain\.tld$) {
    set $auth_subdomain $1;
}
if (-d /home/www/domain.tld/www/$auth_subdomain) {}
if (-e /home/www/domain.tld/config/auth/$auth_subdomain) {
    rewrite ^(.*)$ /auth/$auth_subdomain$1;
    break;
}

Rewrite rules order

Rules order is very important: you have to redirect SSL subdomains, then perform optionnal authentication before you determine and serve correct ressource. Bad order can result in protected.domain.tld exposed.

Subdomains definition

As a summary:

blog.domain.tld neither require SSL nor authentication.
Storage creation
mkdir -p /var/www/mondomaine.com/www/blog
webmail.domain.tld requires SSL, not authentication.
Storage creation
mkdir -p /var/www/mondomaine.com/www/webmail
Force SSL
ln -s /var/www/mondomaine.com/www/webmail /var/www/mondomaine.com/config/ssl/
protected.domain.tld requires authentication, not SSL.
Storage creation
mkdir -p /var/www/mondomaine.com/www/protected
Force authentication
ln -s /var/www/mondomaine.com/www/webmail /var/www/mondomaine.com/config/auth/
masteroftheworld.domain.tld requires SSL and authentication.
Storage creation
mkdir -p /var/www/mondomaine.com/www/maitredumonde
Force authentication
ln -s /var/www/mondomaine.com/www/maitredumonde /var/www/mondomaine.com/config/auth/
Force SSL
ln -s /var/www/mondomaine.com/www/maitredumonde /var/www/mondomaine.com/config/ssl/

Once more, to keep things as simple as it can, I won't talk about filesystem access grants.

Final configuration for domain.tld

Here is the result:

tree /var/www/
domain.tld/
|-- config
|   |-- htpasswd
|   |-- auth
|       |-- protected -> ../../www/protected
|   |   `-- masteroftheworld -> ../../www/masteroftheworld
|   `-- ssl
|       |-- masteroftheworld -> ../../www/masteroftheworld
|       `-- webmail -> ../../www/webmail
|-- logs
`-- www
    |-- blog
    |-- maitredumonde
    |-- protected
    `-- webmail
/etc/nginx/sites-available/domain.tld
server {
        listen   80;
        server_name  domain.tld *.domain.tld;

        access_log  /var/www/domain.tld/logs/nginx_access.log vhosts;
        error_log  /var/www/domain.tld/logs/nginx_error.log;

    ########## Rewrite rules 1 ##########
        if ($host ~* ^domain\.com$) {
            rewrite ^(.*) //www.domain.tld$1 permanent;
            break;
        }

    ########## Rewrite rules 2 ##########
        if ($host !~* ^www\.domain\.tld$) {}
        if ($host ~* ^(.*).domain\.tld$) {
            set $ssl_subdomain $1;
        }
        if (-e /var/www/domain.tld/config/ssl/$ssl_subdomain) {
            rewrite ^(.*) https://$ssl_subdomain.domain.tld$1 permanent;
            break;
        }
    
    ########## Rewrite rules 3 ##########
        if ($host ~* ^www\.domain\.tld$) {}
        if ($uri ~* ^/$) {
            set $dir_subdomain "www";
            set $dir_filename "";
        }
        if ($uri ~* ^/([^/]+)(.*)$) {
            set $dir_subdomain $1;
            set $dir_filename $2;
        }
        if (-d /var/www/domain.tld/www/$dir_subdomain) {
            rewrite ^(.*)$ //$dir_subdomain.domain.tld$dir_filename permanent;
            break;
        }

    ########## Rewrite rules 4 ##########
        if ($host ~* ^([^.]+)\.domain\.tld$) {
            set $auth_subdomain $1;
        }
        if (-d /var/www/domain.tld/www/$auth_subdomain) {}
        if (-e /var/www/domain.tld/config/auth/$auth_subdomain) {
            rewrite ^(.*)$ /auth/$auth_subdomain$1;
            break;
        }

    ########## Rewrite rules 5 ##########
        if ($host !~* ^www\.domain\.tld$) {}
        if ($host ~* ^([^.]+)\.domain\.tld$) {
            set $auto_subdomain $1;
        }
        if (-d /var/www/domain.tld/www/$auto_subdomain) {}
        if (-f /var/www/domain.tld/www/$auto_subdomain$uri) {
            rewrite ^(.*)$ /$auto_subdomain$uri;
            break;
        }

  ##### Authentication configuration
        location ~* /auth {
            root   /var/www/domain.tld/www/;
            auth_basic            "Restricted";
            auth_basic_user_file  /var/www/domain.tld/config/passwords;
        }

    location / {
        root   /var/www/domain.tld/www/;
        index  index.html index.htm;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /var/www/nginx-default;
    }
    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    location ~ /\.ht {
        deny  all;
    }
}

server {
    listen   443;
    server_name  domain.tld *.domain.tld;

    access_log  /var/www/domain.tld/logs/nginx_access.log vhosts;
    error_log  /var/www/domain.tld/logs/nginx_error.log;

    root   /var/www/domain.com/www/;

########## SSL Directives
    ssl  on;
    ssl_certificate  /var/www/domain.tld/config/domain.tld.pem;
    ssl_certificate_key  /var/www/domain.tld/config/domain.tld.pem;
    ssl_session_timeout  5m;
    ssl_protocols  SSLv2 SSLv3 TLSv1;
    ssl_ciphers  ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
    ssl_prefer_server_ciphers   on;

    ########## Rewrite rules 1 ##########
        if ($host ~* ^mondomaine\.tld$) {
            rewrite ^(.*) //www.domain.tld$1 permanent;
            break;
        }

    ########## Rewrite rules 3 ##########
        if ($host ~* ^www\.domain\.tld$) {}
        if ($uri ~* ^/$) {
            set $dir_subdomain "www";
            set $dir_filename "";
        }
        if ($uri ~* ^/([^/]+)(.*)$) {
            set $dir_subdomain $1;
            set $dir_filename $2;
        }
        if (-d /var/www/domain.tld/www/$dir_subdomain) {
            rewrite ^(.*)$ //$dir_subdomain.domain.tld$dir_filename permanent;
            break;
        }

    ########## Rewrite rules 4 ##########
        if ($host ~* ^([^.]+)\.domain\.tld$) {
            set $auth_subdomain $1;
        }
        if (-d /var/www/domain.tld/www/$auth_subdomain) {}
        if (-e /var/www/domain.tld/config/auth/$auth_subdomain) {
            rewrite ^(.*)$ /auth/$auth_subdomain$1;
            break;
        }

    ########## Rewrite rules 5 ##########
        if ($host !~* ^www\.domain\.tld$) {}
        if ($host ~* ^([^.]+)\.domain\.tld$) {
            set $auto_subdomain $1;
        }
        if (-d /var/www/domain.tld/www/$auto_subdomain) {}
        if (-f /var/www/domain.tld/www/$auto_subdomain$uri) {
            rewrite ^(.*)$ /$auto_subdomain$uri;
            break;
        }

  ##### Authentication configuration
        location ~* /auth {
            root   /var/www/domain.tld/www/;
            auth_basic            "Restricted";
            auth_basic_user_file  /home/www/domain.tld/config/passwords;
        }

    location / {
        root   /var/www/domain.tld/www/;
        index  index.html index.htm;
    }
}

Of course, in SSL vhost, we don't need third rule: as we already are in SSL, we don't need to redirect request to HTTPS.

Conclusion

It does not pretend to be exhaustive. Of course, you'll still need to add new domains to NGinx configuration. You will also have to integrate and adapt rewrite rules at 2 places, though you can concentrate it in one file only and use include configuration option.

But, that opens some interesting way of exploration. As an example: can we use this configuration as proxy in front of an Apache to be able to separate static content from dynamic ?.

About Jean Baptiste FAVRE

I spend most of my free time on the Internet working on GNU/Linux with Debian or CentOS, virtualization with Xen and KVM technology, as well as cluster stacks with corosync and OpenAIS. Particularly interested in Linux, Netfilter, virtualization, monitoring and clusters, most of my personal works are published on this website and others should not delay. By way professional, I manage servers running RedHat or CentOS and VMware ESXi farm.
From time to time, I manage to drop my keyboard and read a book while listening to music, but it never lasts long.

License

Creative Commons License This document is published under Creative Common by-nc-sa license.

Valid XHTML 1.0 Strict |  Valid CSS |  license Creative Common by-nc-sa

Table des matières

  1. Introduction
  2. Virtual hosts basis
  3. Environment preparation
  4. NGinx installation and configuration
  5. domain.tld configuration
  6. Default vhost
  7. Behaviour rules
  8. Rewrite rules
  9. Authentication configuration
  10. Rules order
  11. Subdomains definition
  12. Final configuration for domain.tld
  13. Conclusion
  14. About ...
  15. License