Mettez un turbo dans votre serveur web avec NginX
Le serveur web Apache reste le serveur d’application http de premier choix pour le PHP5, du fait de sa grande fiabilité, de son ancienneté, et de ses performances éprouvées y compris pour des fortes charges sur des serveurs de production en environnement distribué. Sa grande sophistication et la complexité de certaines configurations utilisant de nombreux modules optionnels en font en revanche un grand consommateur de mémoire RAM, ce qui est moins problématique qu’auparavant sur des serveurs modernes. Il reste qu’Apache en solo n’est désormais plus la meilleure solution pour des sites web pour lesquels les performances ultimes sont recherchées. Je vous propose ici une architecture combinant Apache, eAccelerator et NginX sur un même serveur dédié qui apporte un excellent rapport performances/simplicité de maintenance et d’installation.
NginX, le serveur venu de l’Est
Est-il encore besoin de présenter NginX, le serveur “léger et rapide” développé par le russe Igor Sysoev ? Cet outil fait partie d’une gamme de serveurs web ultra-optimisés (le concurrent le plus connu est lighttpd) qui ont révolutionné les architectures de plateforme d’hébergement web depuis quelques années, et viennent souvent bousculer le monopole d’Apache 2. Leurs atouts sont une empreinte mémoire réduite au minimum, et une gestion simplifiée des processus dont la philosophie est de servir au plus vite chaque requête pour éviter d’avoir à gérer un parallélisme trop gourmand en charge système et en mémoire RAM.
Quand il s’agit de servir les fichiers “statiques” (images, feuilles de style CSS, fichiers javascript,…) qui constituent la grande majorité des requêtes http que reçoit un serveur web typique, les serveurs web “légers” sont imbattables.
Le meilleur des deux mondes
Je garde une préférence pour Apache2 en matière de serveur d’application PHP5 avant tout parce que j’apprécie ses possibilités d’optimisation très fines, la diversité des modules disponibles, et aussi parce qu’il s’agit pour moi d’une solution très largement éprouvée, que j’ai vu fonctionner de façon fiable sous de très fortes charges et dans des environnements très divers, couplé au cache d’opcode eAccelerator.
Même si les derniers développements de modules php5 pour NginX viennent rivaliser avec les performances d’Apache selon certaines études, j’ai tendance à faire confiance à l’outil qui a fait ses preuves et que je connais le mieux. Il faut également noter que la gestion des règles de ré-écriture par le module rewrite, et l’utilisation des fichiers de configuration distribués (mécanisme .htaccess) comptent parmi les atouts indéniables d’Apache. Enfin, la documentation disponible sur le web est bien pus vaste pour Apache que pour tous ses concurrents plus récents, encore trop peu répandus pour bénéficier du même engouement dans les communautés en ligne.
Il y a cependant moyen d’améliorer énormément les performances d’une plateforme d’hébergement web sous Apache en la couplant avec un serveur NginX qui agit comme frontal en traitant directement les requêtes qui concernent les fichiers statiques, mais passe la main à Apache pour le traitement des fichiers dynamiques (PHP), en jouant le rôle d’un reverse proxy (serveur mandataire inversé).
Voici comment procéder.
Installation de la plateforme LAMP
Se référer à mon guide de configuration pas-à-pas d’un serveur LAMP sous Debian 6 pour toute la configuration du système depuis le partitionnement jusqu’à l’installation des services de monitoring. Pour simplifier la suite de la configuration, l’installation d’Apache, du module PHP5 pour Apache et du cache d’opcode eAccelerator doit avoir été faite préalablement, et testée. Mon guide de compilation et mise à jour d’eAccelerator est ici. Mon guide de configuration d’Apache est ici.
Installation de NginX sous GNU/Linux Debian
L’installation du serveur web NginX se fait le plus simplement du monde à partir des paquets Debian :
apt-get install nginx
Puisque Apache est déjà présent et configuré pour “ecouter” sur le port http par défaut, NginX ne peut pas démarrer correctement comme on peut le constater en tentant de le relancer :
/etc/init.d/nginx restart
Restarting nginx: [emerg]: bind() to 0.0.0.0:80 failed (98: Address already in use) [emerg]: bind() to [::]:80 failed (98: Address already in use) [emerg]: bind() to 0.0.0.0:80 failed (98: Address already in use) [emerg]: bind() to [::]:80 failed (98: Address already in use) [emerg]: bind() to 0.0.0.0:80 failed (98: Address already in use) [emerg]: bind() to [::]:80 failed (98: Address already in use) [emerg]: bind() to 0.0.0.0:80 failed (98: Address already in use) [emerg]: bind() to [::]:80 failed (98: Address already in use) [emerg]: bind() to 0.0.0.0:80 failed (98: Address already in use) [emerg]: bind() to [::]:80 failed (98: Address already in use) [emerg]: still could not bind() nginx.
Nous allons remédier ce problème en reconfigurant Apache, puis NginX.
Re-configuration d’Apache
sudo vi /etc/apache2/ports.conf
->
NameVirtualHost *:8080 Listen 8080
On demande à Apache de ne plus “écouter” sur le port TCP par défaut pour http (80), mais de répondre sur le port 8080 afin de libérer le port 80 pour NginX. NginX passera les requêtes qui le concernent à Apache via ce port 8080.
sudo vi /etc/apache2/sites-enabled/000-default
-> <VirtualHost *:8080>
On indique également à la configuration du serveur web par défaut que c’est désormais le port 8080 qu’il faut servir.
sudo /etc/init.d/apache2 restart
-> Si vous faites un test de connexion http, le serveur Apache ne répond plus. Il répond maintenant sur le port 8080 (http://<votre_serveur>:8080/)
NB : nous autorisons Apache à répondre à toutes les requêtes extérieures pendant la phase de configuration et de tests, mais en configuration de production il faudra restreindre cet accès en indiquant à Apache de ne répondre qu’à des requêtes “locales” issues de l’adresse localhost (127.0.0.1) – car le seul flux autorisé arrivera de NginX qui joue le rôle de mandataire pour toutes les connexions http.
Configuration de NginX
Si on redémarre NginX avec la configuration par défaut, il va maintenant pouvoir démarrer correctement (le port 80 est libéré par Apache), et se mettre à répondre :
sudo /etc/init.d/nginx restart
http://<votre_serveur>
Si on observe le header de réponse renvoyé par le serveur (par exemple avec la “Web Developer Toolbar” pour Firefox ou Chrome/Chromium), on vérifie que c’est maintenant bien NginX qui répond sur le port 80 :
Date: Fri, 20 May 2011 08:57:01 GMT Content-Encoding: gzip Last-Modified: Thu, 19 May 2011 20:26:11 GMT Server: nginx/0.7.67 Content-Type: text/html 200 OK
Alors que c’est Apache qui répond sur 8080 :
http://<votre_serveur>:8080
Date: Fri, 20 May 2011 08:52:17 GMT Content-Encoding: gzip Content-Length: 146 Last-Modified: Thu, 19 May 2011 20:26:11 GMT Server: Apache ETag: "880002-b1-4a3a6d0debec0" Vary: Accept-Encoding Content-Type: text/html Accept-Ranges: bytes 200 OK
Nous allons maintenant adapter la configuration de NginX pour lui faire jouer les deux rôles qui nous intéressent, à savoir servir directement les fichiers statiques le plus vite possible, et passer toutes les autres requêtes à Apache en jouant le rôle de frontal. Au passage nous ferons quelques réglages sur la configuration par défaut de NginX.
sudo vi /etc/nginx/nginx.conf
-> worker_processes 2;
(augmente le parallélisme pour les serveurs multi-processeurs soumis à de fortes charges. Modification inutile sur des serveurs dont la charge est faible, qui sont très rapides ou qui n’ont qu’un processeur… il faut expérimenter pour juger de l’utilité ou non d’augmenter le nombre de processus parallèles. Régler ce chiffre sur le nombre de processeurs est une bonne option en général.)
Pas d’autre modification sur la configuration par défaut de NginX.
Servir directement les statiques
sudo vi /etc/nginx/sites-enabled/default
Ajouter la directive location suivante vers le haut du fichier : elle indique que tous les fichiers statiques doivent être servis directement par NginX à partir du répertoire racine du site web (/var/www par défaut) en ajoutant l’en-tête expires avec un délai de mise en cache par le navigateur d’un an (365 jours), et en désactivant la journalisation (on s’intéresse rarement au nombre d’images vues sur un site, les métriques importantes étant plutôt le nombre de pages vues et le nombre de visiteurs).
location ~* \.(jpg|jpeg|gif|css|png|js|ico|swf|mp3)$ { root /var/www; expires 365d; access_log off; }
Mode reverse-proxy pour les fichiers PHP
Remplacer la directive location responsable du traitement par défaut (on a commenté les deux lignes qui figurent par défaut dans cette directive et ajouté les nôtres à la suite) :
location / { #root /var/www; #index index.html index.htm; proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; access_log off; }
Cette directive indique à NginX que par défaut, tous les types de requêtes qui n’ont pas été interceptés par la première directive ci-dessus (donc toutes les requêtes à l’exception des fichiers identifiés comme statiques) doivent être passées à Apache sur le port local 8080 en mode proxy. Au passage, on ajoute l’en-tête Host qui permettra à Apache de savoir à quelle adresse est destinée la requête (important dans le cas où on a plusieurs virtual hosts). On ajoute également les entêtes “Real IP” et “Forwarded for” pour permettre à Apache de retrouver l’adresse IP d’origine du visiteur (voir ci-dessous). Là non plus, on n’effectue pas de journalisation, car on laissera cette tâche à Apache (inutile de logger de façon redondante les mêmes visites !).
Ajouter enfin cette directive, afin d’optimiser la façon dont les navigateurs web mettront les pages en cache :
add_header Cache-Control public;
…c’est tout pour la configuration de NginX.
sudo /etc/init.d/nginx restart
-> On peut tester que les fichiers PHP sont bien exécutés, par exemple :
http:<votre_serveur>/my_phpinfo.php (traité par Apache comme on peut le voir en vérifiant les en-têtes http)
…par contre, phpinfo() nous indique toujours dans la variable d’environnement REMOTE_ADDR que l’adresse IP du visiteur est toujours celle du proxy local NginX (127.0.0.1). Idem dans les logs Apache. Nous allons maintenant remédier à ce problème.
Récupérer l’adresse IP d’origine côté Apache avec mod-rpaf
Le module pour Apache RPAF (Reverse Proxy Add Forward) s’occupe de récupérer l’information passée par NginX via l’en-tête X-Forwarded-For pour que les autre modules Apache (dont le PHP) “voient” l’adresse du visiteur d’origine, et pas l’adresse du serveur proxy (127.0.0.1).
Il s’installe très facilement sous Debian 6 :
sudo apt-get install libapache2-mod-rpaf
Normalement, l’installation automatique du paquet Debian va rajouter la configuration nécessaire pour Apache dans le fichier /etc/apache2/mods-enabled/rpaf.conf. Si ce n’est pas le cas, vous pouvez modifier vous-même la configuration du virtual host d’Apache afin d’indiquer les adresses des passerelles mandataires (proxies).
sudo vi /etc/apache2/sites-enabled/000-default
Ajouter les lignes suivantes ( uniquement si elles ne sont pas déjà présentes dans /etc/apache2/mods-enabled/rpaf.conf ) :
<IfModule mod_rpaf.c> RPAFenable On RPAFsethostname On RPAFproxy_ips 127.0.0.1 </IfModule>
Redémarrer le serveur Apache :
sudo /etc/init.d/apache2 restart
…le tour est joué, si on vérifie par exemple avec phpinfo(), on remarque que dans la section “Apache Environment“, la variable “REMOTE_ADDR” indique maintenant l’adresse IP réelle du visiteur, et plus l’adresse 127.0.0.1 comme auparavant. Le système de proxy NginX est donc devenu dans les faits “invisible” pour Apache. Notre configuration est bouclée. Nous sommes passés au moteur hybride !
Liens utiles
- Configuration d’Apache pour l’utilisation de la mémoire et les performances
- NginX How To (en anglais – avec de nombreux exemples)
- NginX proxying to Apache install guide @Joyent (en anglais)
- NginX http core directives (en anglais)
- Module NginX httpProxy (en anglais)
- NginX as a reverse proxy (en anglais)
- En-tête http Cache-Control sur Wikipedia (en anglais)
- NginX and Cache control directive (en anglais)
- NginX log request speed (pas utilisé ici mais utile pour l’optimisation de performances – en anglais)
- Un exemple de configuration NginX (serveur polonais !)
- RPAF : site web officiel du module
- Package Debian RPAF