I. Introduction au Virtual Hosting

L'hébergement virtuel (virtual hosting) est le fait de servir plusieurs sites web sur une même instance de serveur. Ainsi, une machine serveur possède un et un seul serveur Apache et elle pourra servir (virtuellement) une infinité de sites qui n'ont aucunement besoin de "se connaître" l'un l'autre. La flexibilité d'Apache fait que chaque site pourra être configuré de manière très différente de son voisin. Aussi, les clients (en grande partie des navigateurs web) n'auront aucune idée du fait qu'il existe plusieurs sites sur le serveur qu'ils ont contacté. Sauf erreurs de configuration, ils n'auront aucun moyen de le savoir.

L'hébergement virtuel est très utilisé chez les hébergeurs proposant des serveurs dits "mutualisés". Dans ce cas là, un même Apache sert un (très) grand nombre de sites web différents et ses ressources sont donc entièrement partagées entre tous les sites de manière équitable.
Il est possible de régler la charge par hôte virtuel, mais il faut pour cela compiler un module externe.

Mais, comment héberge-t-on plusieurs applications webs sur un seul serveur Apache ? Il existe globalement 2 grandes méthodes de multi-hébergement (4 manières réelles) :

  1. tout d'abord, l'hébergement virtuel par adresse : la machine serveur possède plusieurs adresses IP, et chacune mène vers un site distinct ;
  2. ensuite l'hébergement virtuel par nom : la machine ne possède qu'une seule adresse IP et ce sont les noms des sites qui vont aiguiller la requête ;
  3. puis enfin des manières qui découlent des deux précédentes : la machine possède une ou plusieurs adresses IP, mais elle peut différencier les sites par port TCP.
    En dernier lieu on peut tout mixer : hébergement par adresse, port et nom. Dans ce cas, la machine possède plusieurs adresses IP et écoute sur chacune d'elles plusieurs ports TCP, tout en distinguant les noms des sites web.

Nous allons détailler le processus de routage des hôtes virtuels d'Apache, de manière claire afin qu'aucune confusion ne puisse être faite.
Ca n'est pas complexe, c'est même simple et totalement logique, mais il faudra bien retenir certains points. La version d'Apache considérée est 2.2.x, mais il n'existe que très peu de différences avec les versions supérieures ou égales à 1.3.
Enfin nous parlerons des modules externes, à récupérer et compiler, qui peuvent rendre de gros services.

Dans cet article, nous parlons hôtes virtuels et nous supposons que vous savez déjà configurer un serveur Apache de manière fonctionnelle. Ainsi, nous n'allons pas à chaque fois détailler la configuration complète du serveur, mais juste ce qui nous intéressera. Nous supposerons le reste de la configuration (qui peut demeurer tout à fait "classique" et minimale néanmoins) présent et fonctionnel.

II. Théorie générale du Virtual Host et d'Apache

Apache utilise des sockets TCP pour servir ses requêtes. Indépendamment du concept même de l'hébergement virtuel, il convient de préciser à Apache quelle(s) IP il doit écouter et quel(s) port(s).
Une seule directive entre en jeu pour cela : Listen. Elle est définie pour tout le serveur Apache.

Le cas nous intéressant est "une seule instance d'Apache sert plusieurs sites". Tous les sites tourneront donc avec le même utilisateur Unix, ce qui peut créer des problèmes de sécurité.
Certains modules peuvent résoudre ceci, l'utilisation de scripts CGI peut aussi être épargnée, sinon il faudra démarrer autant d'instances d'Apache que de sites. Cette idée est assez idiote, car démarrer X instances d'Apache, c'est augmenter la charge mémoire de la machine car beaucoup de modules d'Apache sont compilés statiquement, et auront donc X images en mémoire, gaspillant d'autant plus celle-ci. Le mieux serait qu'une seule instance d'Apache puisse gérer X sites, chacun sous un utilisateur Unix différent. Certains modules (non officiels) permettent cela, mais pas Apache par défaut.

L'hébergement par nom se base sur l'en-tête Host: de la requête HTTP, afin de sélectionner le bon site. Cet en-tête n'existe qu'en HTTP1.1 et aujourd'hui, certains (rares) clients parlent toujours en HTTP1.0 et sont donc en théorie incompatibles avec l'hébergement virtuel par nom. Apache introduit cependant une directive pour tenter d'enrayer ce problème.

Les hôtes virtuels sont supportés par défaut par Apache, il n'y a aucun module spécial à préciser à la compilation car il est compilé statiquement par défaut : il s'agit de mod_core, le module minimal nécessaire à Apache pour tourner.

Apache sait gérer nativement les hôtes virtuels, il n'y a rien de spécial à faire pour ça.

III. Virtual Host par adresse réseau (IP / port TCP)

L'hébergement par réseau consiste à disposer de plusieurs ports TCP et/ou de plusieurs adresses IP (physiques ou virtuelles) sur la machine serveur. La commande Linux ifconfig permet de configurer les interfaces réseaux.
Nous allons supposer que la machine utilise 2 adresses IP : 192.168.0.1 et 192.168.0.2.
Dans le cas de plusieurs adresses, il faut spécifier à Apache sur lesquelles écouter, et sur quels ports TCP, ceci grâce à la directive Listen

httpd.conf, directive Listen
Sélectionnez

Listen 80
Listen 192.168.0.2:81

Le port est obligatoire, l'adresse est facultative, si on ne la précise pas (en ne précisant qu'un numéro de port), Apache utilisera toutes les addresses IP disponibles sur la machine, c'est donc plutôt déconseillé pour des raisons de sécurité (oubli lors de l'ajout d'une interface sur la machine par exemple).
Le port utilisé par HTTP est 80 par défaut, ainsi les navigateurs web et autres clients HTTP l'utilisent sans devoir le préciser. Si un autre port est employé, il conviendra de le préciser sur le client.
Ici, notre serveur écoute le port 81 mais uniquement sur l'adresse 192.168.0.2. Les commandes Linux netstat et lsof permettent de vérifier qu'Apache écoute bien les adresses/ports spécifiés.

Rappel : Toute ouverture de socket en écoute sur un port inférieur à 1024 nécessite les droits root sur les systèmes *nix.

Rappel : Apache doit dans la grande majorité des cas, être démarré par root (à cause du port qu'il doit écouter, par défaut 80, inférieur à 1024 donc). Cependant, les processus qu'il va faire naître pour traiter les requêtes des clients tourneront sous un autre utilisateur ayant beaucoup moins de droits ("www", "httpd" ou encore "daemon", ça dépend de votre système). Cet utilisateur et son groupe sont à préciser dans la configuration globale d'Apache, via les directives User et Group. Ces utilisateurs sont globaux à tout Apache, donc à tous les sites/hôtes virtuels qu'il va servir.

Un hôte virtuel se configure grâce à la directive <VirtualHost>. Cette directive ouvre un bloc, dans lequel toutes les directives de contexte virtualhost peuvent être écrites.
La liste est grande, voyez le manuel pour plus d'informations. Voyez cette page pour une explication des différents contextes de configuration Apache

Chaque site se présente sous la forme d'un bloc <VirtualHost> et celui-ci doit préciser un port et éventuellement une adresse d'écoute, en théorie en correspondance avec la directive Listen ou du moins en corrélation avec.

Exemple d'hôtes virtuels Apache basés sur le réseau : IP/ports
Sélectionnez

Listen 80
Listen 192.168.0.2:81
 
<VirtualHost 192.168.0.1:80>
DocumentRoot /var/www1
ServerName server1
</Virtualhost>
 
<VirtualHost 192.168.0.2:80>
DocumentRoot /var/www2
ServerName server2
</VirtualHost>
 
<VirtualHost 192.168.0.2:81>
DocumentRoot /var/www3
ServerName server3
</VirtualHost>

Tant que l'ensemble des blocs <VirtualHost> "attrapent" tout ce qui peut entrer via Listen, tout va bien.
Les directives minimales à préciser sont DocumentRoot et ServerName, c'est à dire la liaison entre cette adresse/port, et le système de fichiers : c'est la fonction centrale et la définition même du serveur HTTP. Le nom (ServerName) est ici "facultatif" au niveau du choix du bloc à exécuter, c'est-à-dire que quel que soit le nom de domaine qui pointe sur l'IP, ServerName n'est pas utilisé pour aiguiller la requête dans le cas d'un hébergement par IP.

Important : seul le couple IP/Port est utilisé pour aiguiller la requête dans le cas d'hôtes virtuels basés sur le réseau.

Que se passe-t-il si aucun bloc <VirtualHost> ne correspond à la demande ? En effet, imaginons ceci :

Il semble manquer des blocs VirtualHost?
Sélectionnez

Listen 80
Listen 81
 
<VirtualHost 192.168.0.1>
    DocumentRoot /var/www1
    ServerName server1
</VirtualHost>

Qu'advient-il d'une requête vers http://192.168.0.2:81 par exemple ? La règle est simple : toute requête qui ne peut être satisfaite par un hôte virtuel tombe sur la configuration classique
La configuration classique, c'est tout simplement l'ensemble DocumentRoot/ServerName qui est écrit hors des blocs <VirtualHost> et qui pour rappel définit ce que l'on appelle le "serveur par défaut" : ces directives sont obligatoires pour lancer Apache.

le serveur par défaut reçoit la requête que les blocs VirtualHost n'attrapent pas
Sélectionnez

Listen 80
Listen 81
 
# attrape tout ce qui échappe aux blocs <VirtualHost>
# dans notre exemple 192.168.0.1:80 sera attrapé ici
DocumentRoot /var/www
ServerName ServerParDefaut
 
# attrape 192.168.0.1:80 uniquement
<VirtualHost 192.168.0.1:80>
    DocumentRoot /var/www1
    ServerName server1
</VirtualHost>
 
# attrape le port 81 de toutes les adresses de la machine
<VirtualHost *:81>
    DocumentRoot /var/www2
    ServerName server2
</VirtualHost>
 

Voila, vous venez de comprendre le fonctionnement des hôtes virtuels par IP sous Apache, il n'y a rien à ajouter. A vous d'écrire dans chaque bloc <VirtualHost> la configuration de votre serveur. Toute directive écrite en dehors des blocs sera alors écrasée par celle écrite dans le bloc, les autres seront traitées normalement. Voila qui devrait donner un sens à la notion "d'hôte virtuel".

Attention tout de même, il y a des choses qu'Apache n'aime pas : les situations ambiguës. Son statut est alors indéfini et il finira souvent par planter.
Voici une situation ambiguë :

une situation ambiguë qu'Apache n'aime pas
Sélectionnez

Listen 192.168.0.1:80
Listen 81
 
DocumentRoot /var/www
ServerName ServerParDefaut
 
<VirtualHost 192.168.0.1>
    DocumentRoot /var/www1
    ServerName server1
</VirtualHost>
 
<VirtualHost *:80>
    DocumentRoot /var/www2
    ServerName server2
</VirtualHost>
 

Avec une telle configuration, une requête vers le port 81 mène vers le serveur par défaut car aucun bloc <VirtualHost> ne peut l'attraper, mais quid d'une requête vers 192.168.0.1:80 ?
Une telle requête peut être attrapée par 2 blocs <VirtualHost> (dans notre cas un précise l'adresse sans port, et l'autre le port sans adresse), Apache ne sait pas lequel choisir et plantera souvent face à un tel cas.

Veiller toujours à ce qu'il n'y ait aucune ambiguïté lors de la correspondance de la requête sur la socket d'entrée et les blocs <VirtualHost>.
Si tel est le cas, Apache entre dans un état indéfini. Pour éviter cela, il suffit que les <VirtualHost> renseignent une IP et un port afin d'être le plus précis possible. Si possible, évitez le joker *.

IV. Virtual Host par noms

L'hébergement virtuel par noms a été rendu possible par HTTP1.1. En effet, si vous révisez vos connaissances d'HTTP (par ici par exempleHTTP), vous remarquerez que HTTP1.1 introduit l'en-tête de requête Host:, qui précise le nom de l'hôte que le client souhaite contacter. Apache va utiliser ce nom pour aiguiller la requête sur le bon bloc <VirtualHost> grâce à ServerName.

Important : la directive ServerName va servir à aiguiller la requête sur le bon hôte. Le DNS est donc important dans un tel cas. Nous allons y revenir.

Prenons un exemple simple. Notre serveur dispose d'une seule adresse IP et n'écoute qu'un seul port : 192.168.0.1:80. Le serveur DNS a été configuré pour relier à cette adresse IP 2 noms de domaines différents : server1 et server2.
La configuration va alors ressembler à ceci :

 
Sélectionnez

Listen 192.168.0.1:80
 
ServerName DefaultServer
DocumentRoot /var/www
 
NameVirtualHost 192.168.0.1:80
 
<VirtualHost 192.168.0.1:80>
ServerName server1
DocumentRoot /var/www1
</VirtualHost>
 
<VirtualHost 192.168.0.1:80>
ServerName server2
DocumentRoot /var/www2
</VirtualHost>

Important : dans le cas d'un hébergement virtuel par noms, la directive NameVirtualHost est obligatoire.
Inversement : la présence d'une directive NameVirtualHost signifie qu'Apache va utiliser l'en-tête Host: de la requête, dans une partie du routage vers le bon hôte virtuel. Les adresses renseignées dans les blocs <VirtualHost> doivent être exactement les mêmes que celles écrites dans NameVirtualHost

Dans notre exemple, une requête vers http://server1 mènera dans le premier bloc, une requête vers http://server2 vers le deuxième.
Rappelons le schéma d'une requête HTTP :

Exemple simple d'une requête HTTP1.1
Sélectionnez

GET /foo/bar HTTP/1.1
Host: server1
{autres en-têtes ici}

C'est bien la valeur de Host: qui va déterminer le bloc <VirtualHost> à choisir, en fonction de la directive ServerName logée à l'intérieur
A quoi peut bien servir cette directive NameVirtualHost ? C'est très simple : elle sert à router l'IP et le port dans le cas où le serveur en écoute plusieurs.
Apache effectue donc un double routage de la requête, ce qui nous amène à la section immédiatement suivante de cet article.

IV-A. Virtual Host par noms et par IP

Ce chapitre nécessite que vous aiyez compris le principe de l'hébergement virtuel par réseau (IP/port). Relisez le chapitre si nécessaire.

Un serveur Apache peut à la fois écouter plusieurs IP/ports et différencier les noms des serveurs. Il s'agit alors d'un hébergement virtuel par IP et par noms.
Prenons l'exemple d'un serveur qui écoute 192.168.0.1:80 et 192.168.0.2:80. Il doit servir les noms server1, server2, server3 et server4. En jouant avec NameVirtualHost, on va indiquer à Apache le premier aiguillage : celui de l'IP/port. Une fois cet aiguillage effectué, Apache va analyser le nom dans l'en-tête Host:, et l'aiguiller dans le bloc <VirtualHost> qui le précise avec la directive ServerName

Exemple de virtual host par IP et par nom en même temps
Sélectionnez

Listen 192.168.0.1:80
Listen 192.168.0.2:80
 
ServerName DefaultServer
DocumentRoot /var/www
 
# Voici les hôtes virtuels à considérer si la requête arrive
# sur 192.168.0.1:80
NameVirtualHost 192.168.0.1:80
 
<VirtualHost 192.168.0.1:80>
ServerName server1
DocumentRoot /var/www1
</VirtualHost>
 
<VirtualHost 192.168.0.1:80>
ServerName server2
DocumentRoot /var/www2
</VirtualHost>
 
# Voici les hôtes virtuels à considérer si la requête arrive
# sur 192.168.0.2:80
NameVirtualHost 192.168.0.2:80
 
<VirtualHost 192.168.0.2:80>
ServerName server3
DocumentRoot /var/www3
</VirtualHost>
 
# server4 n'est pas précisé, nous allons voir ce qui se passe

Dans cet exemple, http://server1 est supposé être routé dans le DNS vers 192.168.0.1. La requête arrive donc vers 192.168.0.1:80 et Apache entre donc dans la première section NameVirtualHost. Une fois dedans, il analyse l'hôte demandé dans la requête : il s'agit de server1. server1 est bien précisé dans le premier bloc <VirtualHost>, il sera donc choisi comme second aiguillage, et /var/www1 sera servi.

la valeur de NameVirtualHost doit être systématiquement la même que celle de tous les blocs <VirtualHost> qui vont suivre.
la valeur de NameVirtualHost doit être un couple IP (* possible) et port.
L'ensemble des directives NameVirtualHost se comportent comme dans le cas de l'hébergement par IP : il ne doit pas y avoir ambiguïté pour Apache.

Continuons sur le même exemple. Imaginons que server4 soit routé dans le DNS vers 192.168.0.2. Comme dans la section NameVirtualHost 192.168.0.2:80 il n'existe aucun bloc dont ServerName vaut "server4", une telle requête sera aiguillée vers le serveur par défaut qui sera server3.

Voici les règles du choix du serveur par défaut lorsque des noms interviennent :

  1. dans le cas d'un serveur faisant intervenir des noms (NameVirtualHost), le serveur par défaut n'est pas celui déclaré hors des blocs <VirtualHost> (comme c'est le cas pour l'hébergement virtuel par IP) si et seulement si une directive NameVirtualHost a déjà aiguillé la requête ;
  2. le serveur par défaut dans un hébergement virtuel par noms est le premier bloc <VirtualHost> qui suit le premier filtre : NameVirtualHost si et seulement si celui-ci a déjà pu aiguiller la requête par son IP/port ;
  3. dans le cas d'un hébergement virtuel par noms, le serveur déclaré hors des blocs <VirtualHost> est le serveur par défaut si aucun couple IP/port n'a trouvé de correspondance dans les directives NameVirtualHost déclarées.

Nous avons vu dans la partie "Virtual Host par réseau (IP/ports)" qu'il ne faut pas créer de situation ambiguë. Dans les hébergements virtuels par noms, plusieurs blocs <VirtualHost> possèdent exactement la même syntaxe, ce qui a première vue crée une confusion pour Apache. Il n'en est rien, car ces blocs ne seront interrogés que lorsque la directive NameVirtualHost du même nom qu'eux aura trouvé une correspondance vis à vis de la requête en entrée. Cette directive est donc indispensable dans le cas d'un hébergement virtuel se basant sur les noms et elle ne doit pas amener à des situations ambiguës car elle est testée dès l'entrée de la requête.

Une ou plusieurs IP, un ou plusieurs ports, peu importe : si un hébergement basé sur le nom d'hôte est utilisé, NameVirtualHost est indispensable.
Même avec une seule IP, on peut aussi parler "d'hébergement par IP et par nom", une IP, il en existe de toute façon toujours au moins une.

Voyons un dernier exemple plus complexe d'un serveur disposant de deux adresses IP : 192.168.0.1 et 192.168.0.2. En appliquant les principes décrits auparavant, tout est clair :

Exemple plus complexe d'hébergement virtuel à plusieurs IP/ports et plusieurs noms
Sélectionnez

Listen 80
Listen 81
 
# Le serveur DefaultServer sera utilisé si et seulement si
# aucune des directives NameVirtualHost futures ne trouvent correspondance
# par rapport au couple IP/port de la requête en entrée
ServerName DefaultServer
DocumentRoot /var/www
 
# Si la requête arrive sur 192.168.0.1:80
NameVirtualHost 192.168.0.1:80
 
# premier site : server1, *mais aussi* le site qui sera servi par défaut
# pour le couple 192.168.0.1:80 si un nom mène dessus sans être déclaré dans un VirtualHost
<VirtualHost 192.168.0.1:80>
ServerName server1
DocumentRoot /var/www1
</VirtualHost>
 
# deuxième site : server2, le DNS devrait alors le faire pointer vers 192.168.0.1, 
# sinon il ne sera jamais atteignable à moins de le deviner
<VirtualHost 192.168.0.1:80>
ServerName server2
DocumentRoot /var/www2
</VirtualHost>
 
# Si la requête arrive sur 192.168.0.1:81
NameVirtualHost 192.168.0.1:81
 
# premier site : server3, *mais aussi* le site qui sera servi par défaut
# pour le couple 192.168.0.1:81 si un nom mène dessus sans être déclaré dans un VirtualHost
<VirtualHost 192.168.0.1:81>
ServerName server3
DocumentRoot /var/www3
</VirtualHost>
 
<VirtualHost 192.168.0.1:81>
ServerName server4
DocumentRoot /var/www4
</VirtualHost>
 
# Si la requête arrive sur 192.168.0.2:81
NameVirtualHost 192.168.0.2:81
 
# premier site : server5, *mais aussi* le site qui sera servi par défaut
# pour le couple 192.168.0.2:81 si un nom mène dessus sans être déclaré dans un VirtualHost
<VirtualHost 192.168.0.2:81>
ServerName server5
DocumentRoot /var/www5
</VirtualHost>
 

Suivons le guide : http://server1 mène vers /var/www1 à condition que le domaine server1 soit routé vers 192.168.0.1 dans le DNS.
http://server2 mène vers /var/www2 à condition que le domaine server2 soit routé vers 192.168.0.1 dans le DNS.
http://server3:81 mène vers /var/www3 à condition que le domaine server3 soit routé vers 192.168.0.1 dans le DNS.
http://server3 mène vers /var/www1 (server1) car server3 est pointé par le DNS sur 192.168.0.1 menant à la requête "192.168.0.1:80", c'est donc cette directive NameVirtualHost qui la prend en charge http://server4:81 mène vers /var/www4 à condition que le domaine server4 soit routé vers 192.168.0.1 dans le DNS.
http://server5:81 mène vers /var/www5 à condition que le domaine server5 soit routé 192.168.0.2 dans le DNS.
http://server5 mène vers /var/www (DefaultServer) car le couple IP/port 192.168.0.2:80 n'a aucune correspondance NameVirtualHost
etc etc. C'est tout à la fois simple et logique.

Un fichier "hosts" (ou un enregistrement dans le DNS) comme le suivant est donc correct :

un fichier hosts en accord avec la configuration d'Apache
Sélectionnez

192.168.0.1 server1 server2 server3 server4
192.168.0.2 server5

Dès que le nom d'hôte intervient (hébergement virtuel basé sur les noms), le DNS possède une place primordiale. Il doit être en toute logique en corrélation avec le mapping que fait Apache. Pour le "simuler" lors de tests, le fichier /etc/hosts peut être édité en conséquence.
Si le DNS ne pointe pas comme il faut, rappelez vous qu'Apache aiguille d'abord l'IP/port via NameVirtualHost puis cherche un bloc <VirtualHost> en regardant les ServerName, si aucun ne correpond : le premier bloc <VirtualHost> est sélectionné.

Dans cet exemple-là, une requête vers http://une_ip_que_le_serveur_ecoute mène systématiquement vers le serveur par défaut, car même si une des directives NameVirutalHost va aiguiller cette IP, aucun bloc <VirtualHost> sous-jacent ne déclare un ServerName correspondant exactement à cette IP.
Rappelons qu'une requête HTTP utilisant l'IP directement, possède comme en-tête Host: (donc comme "nom de serveur") l'IP elle-même.

IV-B. Récapitulatif et considérations de sécurité

Image non disponible

Une erreur de DNS, ou l'oubli du fait que quelqu'un peut accéder à http://une_adresse_ip et non pas http://un_nom_de_domaine peut mener à des catastrophes.
N'oubliez jamais qu'une simple session telnet permet d'écrire à la main la requête HTTP et donc de forger n'importe quel "Host:" pour toute IP

Regardez cette configuration :

un exemple peu sécurisé, sauf si on sait parfaitement ce que l'on fait
Sélectionnez

Listen *:80
 
ServerName DefaultServer
DocumentRoot /var/www
 
NameVirtualHost 192.168.0.1:80
 
<VirtualHost 192.168.0.1:80>
ServerName server1
DocumentRoot /var/www1
</VirtualHost>
 
<VirtualHost 192.168.0.1:80>
ServerName server2
DocumentRoot /var/www2
</VirtualHost>
 
NameVirtualHost 192.168.0.2:80
 
<VirtualHost 192.168.0.2:80>
ServerName server3
DocumentRoot /var/www3
</VirtualHost>

L'administrateur doit clairement se focaliser sur le paramètre IP/port et supposer que le DNS peut tomber, être modifié, ou que la requête HTTP peut être forgée. Ce qui suit n'est pas une bizarrerie : http://server2, dont le reverse DNS donne comme IP 192.168.0.1 mènera sur server2, mais http://192.168.0.1 mènera sur server1, elle. On peut aussi écrire la requête suivante:

Requête HTTP forgée
Sélectionnez

# telnet 192.168.0.1 80
HEAD / HTTP/1.1
Host: foo

Et on arrive sur server1 aussi, puisqu'aucun site n'a comme nom "foo".
Plus brutal : si helloworld.server2 existe dans le DNS et pointe vers 192.168.0.1, http://helloworld.server2 mènera vers server1.
Pensez aussi HTTP1.0. Cette version du protocole (parlée très très rarement mais à prendre en compte), n'inclut pas l'en-tête Host: nécessaire au fonctionnement des hôtes virtuels par noms. Apache définit ServerPath pour pallier ce problème, mod_rewrite peut aussi jouer de sa magie dans une moindre mesure.

L'exemple que l'on traite possède aussi une grosse faiblesse : la directive Listen écoute toutes les IP. Si la machine venait à changer d'IP pour une raison quelconque, c'est DefaultServer qui sera interrogé ! (Aucun NameVirtualHost ne matche autre chose que 192.168.0.1). Attention là encore.

Ces "petites faiblesses" du système d'hébergement virtuel par noms peuvent être compensées par Apache via la directive ServerAlias qui permet de donner d'autres noms au serveur. Corrigeons notre exemple :

exemple plus sécurisé
Sélectionnez

 
# Nous déclarons clairement le mapping réseau
# utilisé par Apache, pas d'embrouille ou de "magie" possible,
# pas de caractère * assez risqué
Listen 192.168.0.1:80
Listen 192.168.0.2:80
 
ServerName DefaultServer
DocumentRoot /var/www
 
NameVirtualHost 192.168.0.1:80
 
# catchall : celui-ci attrape l'accès par "http://192.168.0.1"
# et le redirige vers une page 403 (directive order)
<VirtualHost 192.168.0.1:80>
ServerName 192.168.0.1
DocumentRoot /var/www
 <Directory /var/www>
    order allow, deny
 </Directory>
</VirtualHost>
 
#server1 mais aussi "nimporte-quoi.server1"
<VirtualHost 192.168.0.1:80>
ServerName server1
ServerAlias *.server1
DocumentRoot /var/www1
</VirtualHost>
 
#server2 mais aussi "nimporte-quoi.server2"
<VirtualHost 192.168.0.1:80>
ServerName server2
ServerAlias *.server2
DocumentRoot /var/www2
</VirtualHost>
 
NameVirtualHost 192.168.0.2:80
 
# catchall
<VirtualHost 192.168.0.2:80>
ServerName 192.168.0.2
DocumentRoot /var/www
 <Directory /var/www>
    order allow, deny
 </Directory>
</VirtualHost>
 
<VirtualHost 192.168.0.2:80>
ServerName server3
ServerAlias *.server3
DocumentRoot /var/www3
</VirtualHost>
 

On s'assure que les sous-domaines sont interprétés comme il faut et aussi que les accès par l'adresse IP mènent vers une page d'erreur (403).

V. Exemple concret - complet

Exemple pratique concret. Le serveur considéré doit se comporter différemment selon qu'il soit interrogé en local ou via le réseau. Aussi, il est serveur Web et proxy sur le port 8080.
L'exemple n'est pas si complet que cela : la configuration Apache prend rapidement beaucoup de pages, cet exemple est simplement un peu plus détaillé.
N'oubliez pas que tout ne doit pas se trouver dans le fichier global httpd.conf, la directive Include permet de tout séparer et de tout ranger convenablement.

 
Sélectionnez

# le serveur n'écoute 81 que pour des requêtes locales
Listen 127.0.0.1:81
Listen 80
Listen 8080
 
# PHP est configuré de manière globale
LoadModule php5_module modules/libphp5.so
AddType application/x-httpd-php .php
 
# catch-all server
NameVirtualHost *:80
 
<VirtualHost *:80> 
    ServerName julien
    DocumentRoot /media/www/external
 
    # PHP n'est pas interprété
    AddType text/plain .php
 
    DirectoryIndex none
    ReadmeName /apache/footerext.shtml
    HeaderName /apache/headerext.shtml
</VirtualHost>
 
# server pour 127.0.0.1 : accès local
NameVirtualHost 127.0.0.1:80
 
<VirtualHost 127.0.0.1:80>
    ServerName jp
    DocumentRoot /media/www
    setEnv APP_MODE dev
    Alias /sql /media/data/phpmyadmin
    Script PUT /put.php
    ReadmeName /apache/footer.shtml
    HeaderName /apache/header.shtml
 
    <Location /server-status>
        SetHandler server-status
        Order deny,allow
        Deny from all
 
        # 2 sécurités valent mieux qu'une
        Allow from 127.0.0.1
    </Location>
 
    <Location /server-info>
        SetHandler server-info
        Order deny,allow
        Deny from all
 
        # 2 sécurités valent mieux qu'une
        Allow from 127.0.0.1
    </Location>
</VirtualHost>
 
# serveur pour 127.0.0.1 port 81
NameVirtualHost 127.0.0.1:81
 
<VirtualHost 127.0.0.1:81> 
    ServerName jp    
    DocumentRoot /media/www
 
    # PHP5.3 tourne en CGI sur ce serveur
    ScriptAlias /php/ "/usr/local/php53/bin/"
    Action php5-fastcgi "/php/php-cgi53"
    AddHandler php5-fastcgi .php
 
    ReadmeName /apache/footer.shtml
    HeaderName /apache/header.shtml
</VirtualHost>
 
# server sur le port 8080 : un proxy forward
NameVirtualHost *:8080
 
<VirtualHost *:8080> 
    ServerName proxy.jp
 
    ProxyRequests On
    ProxyVia On
    AllowCONNECT 443 563
 
    <Proxy *>
        Order deny,allow
        Allow from 192.168
    	Allow from 127.0.0.1
	    Deny from all
    </Proxy>
 
    # l'interrogation directe mènera vers un accès interdit
    <directory />
        Order allow,deny
        Deny from all
    </directory>
</VirtualHost>
			

VI. Les configurations plus complexes et les modules tiers

Apache propose un module : mod_vhost_alias, dont le but est de créer des VirtualHosts dynamiques en fonction de paramètres dans l'URL.
Pour aller plus loin, il faudra se tourner vers des modules tiers. Au plus simple, mod_vhost_limit définit une seule directive : MaxVhostClients dont le nom est plus qu'explicite. Si le nombre de clients par virtualhost est dépassé, une erreur 503 est retournée.

Pour des besoins plus pointus, je vous invite à lire les documentations de mod_qos ou mod_cband qui permettent notamment de contrôler très finement les ressources que chaque VirtualHost consomme.
D'autres modules existent encore "dans la nature", vous n'avez qu'à les télécharger, les compiler et les tester, ils ne demandent que cela ;-) Rappelons aussi que leurs codes sources sont ouverts.

Remerciements à Hédhili Jaïdane pour ses relectures.