Configuration d'hôtes virtuels sur NGinx avec support automatique des sous-domaines, du SSL et de l'authentification

Jean Baptiste FAVRE

janvier 2010

<UPDATE> : ce document a été mis à jour. Vous pouvez (et devriez) le lire, mais la configuration finale est décrite là: Configuration d'hôtes virtuels sur NGinx avec support automatique des sous-domaines, du SSL et de l'authentification.</UPDATE>

English version is available here: NGinx automatic vhosts configuration with subdomains, SSL and authentication support.

Introduction

L'installation, la configuration et l'exploitation d'un serveur web peut devenir chronophage Il faut souvent adapter la configuration lorsque l'on souhaite ajouter un hôte virtuel. Découvrez comment cela peut être automatique. Un vhost à créer ? Un répertoire. Un vhost pour lequel l'utilisation du SSL est obligatoire ? Un lien symbolique. Un vhosts où l'authentification est obligatoire ? Encore un lien symbolique.

Compliqué ? Pas tant que ça. Suivez le guide.

Hôtes virtuels, principes

Le serveur web NGinx est capable de gérer des hôtes virtuels, c'est-à-dire que l'on peut héberger plusieurs domaines différents sur la même IP. Le choix s'effectue en fonction du contenu de la requète HTTP. Le champ Host détermine l'hôte virtuel qui devra traiter la requète.

Ceci est très facile, sauf lorsque l'on souhaite travailler en SSL. En effet, dans le cas de SSL, la négotiation de la clef de chiffrement intervient avant tout envoi de requète HTTP. Ce qui est logique d'un point de vue confidentialité de l'information pose néanmoins un problème: à moins d'avoir un certificat SSL contenant tous les nom de domaines gérés par le serveur, il devient impossible d'héberger plusieurs noms de domaine sur la même adresse IP. Il existe bien quelques exceptions (on obtient ainsi un certificat "wildcard" pour *.mondomaine.tld), mais celles-ci sont généralement limitées aux sous-domaines d'un domaine particulier (ici mondomaine.tld). Si vous souhaitez ajouter mondomaine.fr au même certificat par exemple, et bien sachez qu'aucun marchand de certificat ne vous le propose.

Vous pouvez bien sûr générer votre propre certificat (c'est ce que j'ai fait), mais vos visiteurs auront alors un avertissement car soit votre certificat est auto-signé, soit, plus rare, vous avez votre propre CA qui n'est évidemment pas intégrée dans les navigateurs.

En attendant, nous partirons du principe que 1 IP = 1 domaine SSL.

Préparation de l'environnement

Naturellement, l'automatisation demande un minimum d'organisation. Personnellement, j'ai choisi l'organisation suivante:

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

Comme vous l'aurez deviné, le rôle des répertoires est le suivant:

config
Contiendra toute la configuration "magique":
auth
Les liens symboliques vers les sous-domaines (subdir*) nécessitant une authentification.
ssl
Les liens symboliques vers les sous-domaines (subdir*) nécessitant un accès SSL.
logs
Ici, nous aurons les logs d'accès ainsi que les logs d'erreur pour le domaine concerné
www
La racine de l'arborescence web où se trouveront nos pages et scripts éventuels et les sous-domaines (subdir*).

Maintenant que nous avons notre structure de répertoires, prenons quelques instants pour définir comment notre système devra se comporter.

Installation et configuration de NGinx

Installer NGinx sous Debian
aptitude install nginx

La configuration de NGinx ci-dessous est une configuration de tests. Elle ne tiendra certainement pas la charge et ne correspondra vraisemblablement pas à vos besoins. Mais sa simplicité permet de mieux comprendre ce qui se passe.

Configuration de NGinx
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/*;
}

Vous noterez l'apparition de la directive log_format: je suis parti de la référence combined et y ai ajouté $host afin d'avoir le champ HTTP Host dans le fichier de log. C'est important car chaque domaine (et tous les sous-domaines associés) n'aura qu'un seul fichier de logs.

Configuration du domaine mondomaine.com

Passons à la configuration du domaine mondomaine.com. Ajoutez les 2 configurations suivantes dans le fichier /etc/nginx/sites-available/mondomaine.com

Configuration HTTP du domaine mondomaine.com
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;
    }
}

Là encore, j'ai adapté la directive access_log afin de lui faire prendre en compte le format défini dans le fichier global.

Configuration HTTPS du domaine mondomaine.com
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;
    }
}

Je laisse volontairement de côté la génération du certificat SSL. Ceci fera l'objet d'une autre publication.

Activation du domaine mondomaine.com
cd /etc/nginx/sites-enabled/
ln -s /etc/nginx/sites-available/mondomaine.com
Redémarrage de NGinx
/etc/init.d/nginx restart

Cas du vhost par défaut

Selon que vous montez un hébergement mutualisé ou dédié, la solution diffèrera. L'objectif, en tout cas, est de gérer le cas, pas si improbable que cela, où vous recevez une requète à destination d'un domaine que vous n'hébergez pas. Il est enfantin de forger une requète HTTP de toute pièce et vous devez le prendre en compte.

Pour cela, il faut définir un vhost par défaut. Il sera utilisé par défaut, car il sera défini en premier.

Créez un fichier /etc/nginx/site-available/0000-default et ajoutez-y la configuration suivante:

Configuration du vhost par défaut
server {
    listen 80 default;
    server_name _;
    rewrite ^(.*)$ //www.mondomaine.com$1 permanent;
}

Ici, je considère que nous sommes sur un serveur dédié. Il est donc envisageable de rediriger tout le traffic vers notre "vrai" domaine. Par exemple, toute requète vers //ip_du_serveur sera traitée par ce vhost et le visiteur redirigé vers //www.mondomaine.com/

Enfin, il faut activer le vhost

Activation du vhost par défaut
cd /etc/nginx/sites-enabled
ln -s /etc/nginx/sites-available/0000-default
Redémarrage de NGinx
/etc/init.d/nginx restart

Règles de comportement

Maintenant que nous avons une configuration fonctionnelle, il est temps de s'intéresser au comportement que nous attendons de notre serveur web.

Nous souhaitons héberger le domaine mondomaine.com.

  1. Nous devons rediriger mondomaine.com vers www.mondomaine.com.
  2. Nous voulons pouvoir obtenir, le plus simplement possible, différents sous-domaines. Par exemple:
    • blog.domaine.com doit être accessible en HTTP ou en SSL. L'authentification est gérée par l'application elle-même.
    • webmail.domaine.com doit être obligatoirement en SSL. L'authentification est gérée par l'application elle-même.
    • protected.domaine.com ne sera accessible qu'après authentification, mais l'accès n'est pas forcé en SSL.
    • maitredumonde.domaine.com ne sera accessible qu'après authentification, mais l'accès est en plus forcé en SSL.
  3. Comme nous avons également loué le domaine mondomaine.fr, il faut que les 2 pointent sur le même espace d'hébergement.

En ce qui concerne les alias de domaines (différentes extensions tld), il est nécessaire de modifier la configuration du vhost en ajoutant les directives ServerAlias. En effet, les domaines n'appazraissant ni en ServerName, ni en ServerAlias seront traités par le vhost par défaut.

Règles de ré-écriture

Maintenant que nous avons défini nos règles de comportement, il est temps de préparer nos rewrite Rules. C'est en effet grâce à cette fonctionnalité de NGinx que nous pourrons parvenir à nos fins. Naturellement, nous ne nous arrêtrons pas aux quelques sous-domaines que nous avons identifié. Il faut faire des règles qui puissent être le plus générique possible.

Toute requète au domaine //mondomaine.com est redirigée vers //www.mondomaine.com

Ceci permet d'éviter les duplicate contents et d'optimiser un peu le référencement Google.

Rewrite rules 1
if ($host ~* ^mondomaine\.com$) {
    rewrite ^(.*) //www.mondomaine.com$1 permanent;
    break;
}
Toute requète au domaine //sslsubdir.mondomaine.com est redirigée vers https://sslsubdir.mondomaine.com

Il suffit que le répertoire /var/www/mondomaine.com/www/sslsubdir existe et que le lien symbolique sslsubdir soit détecté dans le répertoire /var/www/mondomaine.com/config/ssl/.

Rewrite rules 2
if ($host !~* ^www\.mondomaine\.com$) {}
if ($host ~* ^(.*)\.mondomaine\.com$) {
    set $ssl_subdomain $1;
}
if (-e /var/www/mondomaine.com/config/ssl/$ssl_subdomain) {
    rewrite ^(.*) https://$ssl_subdomain.mondomaine.com$1 permanent;
    break;
}
Toute requète au domaine //www.mondomaine.com/subdir est redirigée vers //subdir.mondomaine.com

Là encore, il est question d'éviter la duplication de contenu. Il suffit que le répertoire /var/www/mondomaine.com/www/subdir existe.

Rewrite rules 3
if ($host ~* ^www\.mondomaine\.com$) {}
if ($uri ~* ^/$) {
    set $dir_subdomain "www";
    set $dir_filename "";
}
if ($uri ~* ^/([^/]+)(.*)$) {
    set $dir_subdomain $1;
    set $dir_filename $2;
}
if (-d /var/www/mondomaine.com/www/$dir_subdomain) {
    rewrite ^(.*)$ //$dir_subdomain.mondomaine.com$dir_filename permanent;
    break;
}
Toute requète au domaine //subdir.mondomaine.com est ré-écrite en /var/www/mondomaine.com/www/subdir.

Il suffit que le répertoire /var/www/mondomaine.com/www/subdir existe.

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

Vous aurez remarqué qu'il n'y a pas de règle n°4. C'est normal, il s'agit de la règle qui va gérer l'authentification. Nous l'ajouterons plus tard.

Configuration de l'authentification

Il est temps maintenant de protéger nos répertoires sensibles. Pour cela, il faut tout d'abord créer le fichier qui contiendra les couples nom d'utilisateur et mot de passe. Pour ce faire, nous allons tout simplement créer le premier utilisateur:

Créer le premier utilisateur authentifié
htpasswd -c /var/www/mondomaine.com/config/htpasswd user1

Ensuite, pour les utilisateurs suivants, il faudra faire:

Créer les utilisateurs suivants
htpasswd /var/www/mondomaine.com/config/htpasswd user1

Notez bien la suppression de l'option -c. Si vous la laissez, htpasswd va commencer par vider le fichier password. Vous perdrez alors vos utilisateurs précédents.

Enfin, il ne nous reste plus qu'à configurer l'authentification d'une part, et à prévoir la nouvelle règle de ré-écriture d'autre part:

Configurer l'authentification
location ~* /auth {
    root   /home/www/mondomaine.com/www/;
    auth_basic            "Restricted";
    auth_basic_user_file  /home/www/mondomaine.com/config/passwords;
}
Rewrite rules 4
if ($host ~* ^([^.]+)\.mondomaine\.com$) {
    set $auth_subdomain $1;
}
if (-d /home/www/mondomaine.com/www/$auth_subdomain) {}
if (-e /home/www/mondomaine.com/config/auth/$auth_subdomain) {
    rewrite ^(.*)$ /auth/$auth_subdomain$1;
    break;
}

Ordre des règles de ré-écriture

Vous aurez compris que l'ordre des règles de ré-écriture est particulièrement important. En effet, il faut d'abord commencer par rediriger les sites SSL, puis gérer l'authentification avant de pouvoir pointer sur le bon fichier.

Un mauvais ordre des règles et voilà votre sous-domaine protected.mondomaine.com exposé. Pas cool. Attention donc si vous décidez de modifier/ajouter des règles de ré-écriture.

Mise en place des sous-domaines

Il est temps maintenant de mettre en place les sous-domaines. Pour mémoire:

blog.domaine.com doit être accessible en HTTP ou en SSL. L'authentification est gérée par l'application elle-même.
Création de l'espace de stockage
mkdir -p /var/www/mondomaine.com/www/blog
webmail.domaine.com doit être obligatoirement en SSL. L'authentification est gérée par l'application elle-même.
Création de l'espace de stockage
mkdir -p /var/www/mondomaine.com/www/webmail
Forcer le SSL
ln -s /var/www/mondomaine.com/www/webmail /var/www/mondomaine.com/config/ssl/
protected.domaine.com ne sera accessible qu'après authentification, mais l'accès n'est pas forcé en SSL.
Création de l'espace de stockage
mkdir -p /var/www/mondomaine.com/www/protected
Forcer l'authentification
ln -s /var/www/mondomaine.com/www/webmail /var/www/mondomaine.com/config/auth/
maitredumonde.domaine.com ne sera accessible qu'après authentification, mais l'accès est en plus forcé en SSL.
Création de l'espace de stockage
mkdir -p /var/www/mondomaine.com/www/maitredumonde
Forcer l'authentification
ln -s /var/www/mondomaine.com/www/maitredumonde /var/www/mondomaine.com/config/auth/
Forcer le SSL
ln -s /var/www/mondomaine.com/www/maitredumonde /var/www/mondomaine.com/config/ssl/

Là encore, pour des questions de simplicités, je laisse volontrairement de côté les problématiques liées, notamment, aux permissions du système de fichier.

Configuration finale du domaine mondomaine.com

Voici à quoi ressemble maintenant notre structure de fichiers:

tree /var/www/
mondomain.com/
|-- config
|   |-- htpasswd
|   |-- auth
|   |   `-- maitredumonde -> ../../www/maitredumonde
|   `-- ssl
|       |-- maitredumonde -> ../../www/maitredumonde
|       `-- webmail -> ../../www/webmail
|-- logs
`-- www
    |-- blog
    |-- maitredumonde
    |-- protected
    `-- webmail
/etc/nginx/sites-available/mondomaine.com
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;

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

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

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

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

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

    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;
    }
}

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;

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

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

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

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

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

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

Bien entendu, dans la définition du vhost SSL, la règle numéro 3 a disparu. C'est normal, puisque c'est celle qui est chargée de la ré-écriture d'URL en HTTPS. Si nous sommes déjà en HTTPS, pas besoin de rediriger la requète.

Conclusion

Bien entendu, ce système n'est pas "ultime". Par exemple, vous devrez redémarrer NGinx à chaque fois que vous voudrez ajouter un domaine. Dans le même esprit, vous devrez modifier les règles de ré-écriture à 2 endroits différents à chaque fois. Vous pouvez toutefois condenser ces règles dans un fichier particulier et l'inclure dans la déclaration de vos vhosts.

Mais ceci ouvre des perspectives intéressantes, que je vous laisse creuser. Par exemple: est-il possible de placer un nginx avec cette configuration en frontal d'un Apache ? De cette manière, on peut effectuer une séparation de trafic statique / dynamique.

Format

Ce document est disponible aux formats suivants:

À propos de Jean Baptiste FAVRE

Je suis responsable d'exploitation dans le domaine de l'hébergement. Je travaille, entre autres, sur la virtualisation et l'amélioration des performances web. De temps en temps, j'arrive à décrocher de mon clavier pour lire un bon bouquin en écoutant de la musique.

License

Creative Commons License Cette publication est publiée sous contrat Creative Common by-nc-sa

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

Table des matières

  1. Introduction
  2. Hôtes virtuels, principes
  3. Préparation de l'environnement
  4. Installation et configuration de NGinx
  5. Configuration du domaine mondomaine.com
  6. Cas du vhost par défaut
  7. Règles de comportement
  8. Règles de ré-écriture
  9. Configuration de l'authentification
  10. Ordre des règles de ré-écriture
  11. Mise en place des sous-domaines
  12. Configuration finale du domaine mondomaine.com
  13. Conclusion
  14. Format
  15. À propos ...
  16. License