Un serveur web avec Nginx, PHP et MySQL

L'hébergement virtuel proposant des "tranches" de serveur devient de plus en plus populaire. Vous trouverez un peu partout des offres comparables à celles proposées par Gandi ou Slicehost et, de mon point de vue, elles représentent l'avenir de l'hébergement de sites web.

En regardant les offres de près, le premier réflexe est généralement d'être effrayé par les tarifs. Surtout en France, où certaines sociétés laissent croire qu'on peut fournir un hébergement de qualité pour 2 EUR par mois.

Pour une somme allant de 14 à 20 EUR par mois, vous pourrez trouver un hébergement disposant de 256 Mo de RAM, un puissance CPU raisonnable et une flexibilité que vous ne trouverez pas ailleurs. Et vous serez administrateur de votre machine ce qui n'est pas le dernier des avantages.

256 Mo

Il y a de cela 8 ans, j'avais un vieux PC dans ma cuisine, avec 192 Mo de RAM, un CPU poussif et un débit montant de 128 kbits/s. Ça ne m'empêchait pas de faire tourner un serveur web, un serveur de mail. Les début de dotclear.net se sont faits sur cette machine, à côté du frigo.

Du coup, je me suis dit, qu'il n'y avait aucune raison de ne pas faire fonctionner correctement un serveur web avec 256 Mo de RAM, un meilleur CPU et une bande passante bien plus confortable.

Afin de tester mon idée, j'ai créé une machine virtuelle avec vmware et une image Ubuntu server. Cette liste propose un grand nombre d'images de VM prêtes à l'emploi, ça vous fera gagner du temps si vous voulez faire des essais.

Au démarrage la machine virtuelle n'utilise que 16% de ses 256 Mo de mémoire. (Avec uniquement un serveur SSH).

MySQL

Sur Ubuntu, la configuration par défaut de MySQL est réputée pour consommer un peu trop de ressources. Nous pouvons améliorer ceci en modifiant un peu le fichier /etc/mysql/my.cnf. Faites en sorte d'avoir ces lignes dans le fichier :

key_buffer           = 16K
max_allowed_packet   = 1M
thread_stack         = 64K
thread_cache_size    = 8
sort_buffer_size     = 64K
read_buffer_size     = 256K
read_rnd_buffer_size = 256K
net_buffer_length    = 2K

Si vous n'avez pas l'intention d'installer Dotclear ou plus généralement, ne comptez pas utiliser InnoDB, désactivez le en ajoutant la ligne :

skip_innodb

Cette configuration me donne une utilisation de mémoire d'environ 30 Mo. Ça dépendra, bien sûr, de vos applications.

Oublions Apache

De l'ensemble LAMP, nous voulons conserver Linux, MySQL et PHP. Nous allons remplacer Apache par un autre serveur : Nginx. Nginx (on dit Engine X) est ce qu'on pourrait appeler la nouvelle génération de serveurs web. Il peut remplacer Apache dans de nombreuses circonstances. Je vous laisse lire la présentation du serveur ainsi que ce billet.

Pour installer Nginx, apt, yum ou votre gestionnaire de paquet feront l'affaire. Assurez vous simplement d'avoir une version 0.7.x. Nous verrons ensuite comment le configurer.

PHP et PHP Xcache

Nginx, contrairement à Apache, ne propose pas d'équivalent à mod_php. Vous ne pouvez pas non plus utiliser PHP simplement en CGI (tant mieux). Vous allez devoir utiliser FastCGI et indiquer à Nginx comment y accéder.

Pour faire simple, nous allons installer spawn-fcgi et écrire un script pour lancer nos processus PHP. Sur Ubuntu/Debian :

apt-get install php5-cgi spawn-fcgi

Le script permettant de lancer les processus PHP est le suivant, vous devez le créer dans /etc/init.d/php5-fcgi :

#!/bin/sh

### BEGIN INIT INFO
# Provides:       php5-fcgi
# Required-Start: $remote_fs $syslog
# Required-Stop:  $remote_fs $syslog
# Default-Start:  2 3 4 5
# Default-Stop:   0 1 6
# Short-Description: PHP5 FastCgi Spawned processes
### END INIT INFO

COMMAND=/usr/bin/spawn-fcgi
ADDRESS=127.0.0.1
PORT=9000
USER=www-data
GROUP=www-data
PHPCGI=/usr/bin/php5-cgi
PIDFILE=/var/run/fastcgi-php.pid
RETVAL=0

PHP_FCGI_MAX_REQUESTS=500
PHP_FCGI_CHILDREN=2

start() {
    export PHP_FCGI_MAX_REQUESTS PHP_FCGI_CHILDREN
    $COMMAND -a $ADDRESS -p $PORT -u $USER -g $GROUP -f $PHPCGI -P $PIDFILE
}

stop() {
    /usr/bin/killall -9 php5-cgi
}

case "$1" in
    start)
      start
      RETVAL=$?
  ;;
    stop)
      stop
      RETVAL=$?
  ;;
    restart|reload)
      stop
      start
      RETVAL=$?
  ;;
    *)
      echo "Usage: fastcgi {start|stop|restart}"
      exit 1
  ;;
esac
exit $RETVAL

Pour activer votre script (avec Ubuntu ou Debian) :

update-rc.d php5-fcgi defaults

Maintenant vos processus PHP se lanceront au démarrage. Un petit mot sur deux paramètres importants. Les processus PHP en CGI ont une tendance connue à planter de manière régulière. C'est à ceci que sert à la variable PHP_FCGI_MAX_REQUESTS que nous avons mis à 500. Tous les 500 cycles, chaque processus PHP sera relancé. Enfin, PHP_FCGI_CHILDREN=2 indique de lancer deux processus PHP. Vous pouvez en mettre plus mais n'oubliez pas que plus il y en a plus vous consommerez de mémoire.

Une fois en production, vous pourriez constater que c'est un peu lent. L'installation du paquet php5-xcache améliore grandement les performances de PHP. Consultez le site de Xcache pour en savoir plus. J'ai doublé la taille du cache (xcache.size dans /etc/php5/conf.d/xcache.ini) mais ne perdez pas de vue que cette taille est appliquée à chaque processus PHP (soit 2 fois 32M dans mon cas).

Maintenant, vous pouvez lancer votre processus à l'aide de /etc/init.d/php5-fcgi start.

Notez également que PHP FPM peut remplacer spawn-fcgi. J'en ai lu le plus grand bien mais ne l'ai pas testé.

Configuration de Nginx

Vous avez bien entendu installé Nginx avec un classique apt-get install nginx.

Nginx pour Ubuntu ou Debian fourni un fichier de configuration pour FastCGI. Nous allons le compléter. Le fichier est /etc/nginx/fastcgi_params et doit contenir ceci :

fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param  REDIRECT_STATUS    200;

fastcgi_split_path_info ^(.+\.php)(.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

Les derniers paramètres permettent d'utiliser PHP en mode PATH_INFO (à condition de bien configurer Nginx). Le tout dernier paramètre est le plus important, c'est celui qui va dire à PHP quel script exécuter.

Maintenant, testons ceci. Créez un fichier /var/www/nginx-default/test.php dans lequel vous pouvez par exemple mettre un appel à phpinfo() ou ce que vous voulez.

Ensuite, modifiez le fichier /etc/nginx/site-enabled/default (il peut se trouver ailleurs si vous n'êtes pas sur Ubuntu/Debian). Vous pouvez aussi créer un nouveau fichier, vous faites comme vous voulez.

server {
    listen   80;
    server_name localhost;
    root /var/www/nginx-default;
    index index.php index.html;

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

    location ~ ^(.+\.php)(/.*)?$ {
        fastcgi_pass  localhost:9000;
        include /etc/nginx/fastcgi_params;
    }
}

Relancez le serveur avec /etc/init.d/nginx restart et rendez vous sur votre nouveau site pour pointer sur le fichier test.php. Votre script doit s'exécuter.

Ça ne marche pas ? Vous obtenez le très informatif "No input file specified" ? Quelque chose est mal configuré. Ça peut être un problème de permission. Commencez par essayer d'enlever la partie se chargeant d'interpréter PHP et de charger votre script. Vous pouvez également utiliser strace pour repérer quelle valeur de SCRIPT_FILENAME est passée à PHP.

Petit bonus : installation de Dotclear

Dotclear, avec la configuration que nous venons de voir, s'installe très bien. Vous pourrez même l'utiliser en PATH_INFO avec une URL du genre /index.php/...

Peut-on faire mieux ? Yes we can.

Avec Apache, vous aviez la possibilité d'utiliser Mod Rewrite pour lui dire quelque chose comme : "Si ce n'est pas un fichier ou un répertoire transforme l'URL vers tel script". Nginx propose des options de rewrite du même type avec une limite de taille ; on ne peut pas imbriquer les tests. On peut donc tester si l'URL demandée pointe vers un fichier, ou vers un répertoire mais pas les deux. C'est gênant. On peut s'en sortir en écrivant une configuration très complexe et difficile à maintenir. Sinon on peut se rappeler que Nginx n'est pas Apache et qu'il existe peut-être un autre moyen.

Nginx fournit une directive appelée try_files qui va faire exactement ce que nous ferions avec Apache rewrite en testant si le fichier et le répertoire n'existe pas.

Donc, pour votre Dotclear à la racine pointant vers /dotclear/index.php, voici la recette :

location / {
    try_files $uri $uri/ /dotclear/index.php$uri?$args;
}

La même chose est possible pour Wordpress, Drupal et consort.

Quelques tests et conclusion

Ma configuration est une machine virtuelle VMWare avec 256 Mo de RAM utilisant un seul core de CPU sur un MacBook Pro. C'est sans doute un peu plus que ce dont dispose une part d'hébergement mais ça donne une idée.

Voici quelques chiffres :

Requêtes (simultanées) Temps par requête Utilisation Mémoire
500 65ms 52%
500 (5) 342ms 56%
500 (20) 1330ms 56%
500 (50) 3436ms 56%

C'est tout à fait honorable pour du PHP avec aussi peu de ressources. L'autre aspect intéressant est que même avec 50 visiteurs au même moment, vos fichiers statiques sont servis en moins d'une demie-seconde. Et contrairement à Apache, l'utilisation mémoire n'augmente pas avec le nombre de requêtes simultanées.

La conclusion est que, oui, avec 256 Mo de RAM, vous pouvez disposer d'un serveur web tout à fait performant et vous disposez même d'une marge de manoeuvre permettant d'installer un serveur SMTP et IMAP.

N'étant pas non plus un expert sur le sujet, n'hésitez pas à signaler les erreurs. Et si vous avez des astuces concernant PostgreSQL avec une telle configuration, ça m'intéresse.

Comments

Yann 7 years, 7 months ago

Salut, oublie pas de jeter un oeil au php.ini. A mon avis tu peux gagner pas mal de mémoire en désactivant les modules PHP dont tu n'as pas besoin. Dans les dernières versions de Ubuntu, la taille maximale allouée à un process PHP a aussi explosée (max_memory_qqchose) et se situe actuellement à genre 128. Deux requêtes simultanées sur la mauvaise page, et avec deux process PHP, ca te fait deja 256MB. Penser à mettre quelque chose de plus raisonnable, dotclear tourne surement dans moins de 50MB.

Yann 7 years, 7 months ago

Salut, oublie pas de jeter un oeil au php.ini. A mon avis tu peux gagner pas mal de mémoire en désactivant les modules PHP dont tu n'as pas besoin. Dans les dernières versions de Ubuntu, la taille maximale allouée à un process PHP a aussi explosée (max_memory_qqchose) et se situe actuellement à genre 128. Deux requêtes simultanées sur la mauvaise page, et avec deux process PHP, ca te fait deja 256MB. Penser à mettre quelque chose de plus raisonnable, dotclear tourne surement dans moins de 50MB.

Olivier 7 years, 7 months ago

Merci de rappeler qu'il y a des leviers côtés PHP. Dotclear n'a pas besoin de plus de 8Mo :)

Nico 7 years, 6 months ago

2 petites questions :

Que penses tu lighttpd par rar rapport à nginx ? Que penses tu des offres OVH RPS par rapport aux machines virtuelles de Gandi & co ?

Ce sont les technos que j'ai retenues pour un site perso et j'en suis satisfait.

padawan 7 years, 6 months ago

Attention, la variable $document_root ne fonctionnera que si vous avez une directive 'root' avec le bon chemin et visible du bloc où se trouve $document_root, ce qui est le cas dans la configuration ci-dessus. Mais dans la configuration standard de Nginx sur Ubuntu, ce n'est pas nécessairement le cas car 'root' est redéfini dans chacun des blocs 'location' et n'est pas visible des autres, en particulier pas du bloc 'location' pour traiter PHP qui est livré par défaut (et qui d'ailleurs consiste à passer le bébé à... Apache !).

padawan 7 years, 6 months ago

@Nico : regarde les dernières statistiques Netcraft sur les parts des différents serveurs web. En résumé, Nginx sert presque 30 fois plus de sites actifs que Lighttpd, et ce dernier n'a jamais réellement décollé. Attention ça n'est pas un jugement sur la techno, qui peut répondre parfaitement à ton besoin, juste une indication de la réception de l'un et l'autre par les hébergeurs et par extension donner une idée du sens du vent et de la taille des communautés respectives ;-).

daks 7 years, 6 months ago

Bonjour,

dans ton exemple tu utilises fastcgi en le bindant sur un port TCP on dirait, tout en étant en local. Selon toute vraisemblance il est tout à fait possible de faire la même chose en utilisant des sockets UNIX ce qui doit moins coûter de ressources. Je vais essayer cette solution, et merci pour cet article très intéressant... et qui me fait me lancer enfin dans l'aventure NGINX ;)

JB 7 years, 6 months ago

Bonjour Olivier, D'abord merci pour ce papier, ça ouvre des tas de perspectives. Concernant les tests que tu as réalisé, pourrais-tu en dire un peu plus, comme par exemple les commandes employées. Est-ce ab, apache-jmeter ou autre achose encore ?

Cordialement, JB

Olivier 7 years, 6 months ago

@daks, on peut évidemment mettre son process FCGI dans un socket, je ne l'ai pas fait ici par flemme et pour simplifier l'explication :)

@JB, J'ai fait les tests avec un simple ab.

Aranno 7 years, 6 months ago

Notez que Gnu/Linux Mag consacre un article au serveur Nginx dans son numéro d'avril (126) et pour ceux que le sujet des serveurs web alternatifs intéresse, il y aussi un article à propos de Cherokee dans le numéro précédent. Les liens vers les numéros : http://www.unixgarden.com/index.php/news/gnulinux-magazine-n%C2%B0126-avril-2010-chez-votre-marchand-de-journaux http://www.unixgarden.com/index.php/news/gnulinux-magazine-n%C2%B0125-mars-2010-chez-votre-marchand-de-journaux Voili voilà.

Alex~ 7 years, 6 months ago

Je ne suis pas convaincu, un vrai serveur dédié coûte sensiblement moins cher que son équivalent virtuel.

patricK pakard 7 years, 1 month ago

Merci pour ces infos excellentes. Que penser du couple nginx(proxy)+Apache ? J'ai entendu dire que c'était encore mieux "so many things are designed to work with Apache that don't work very well (or at ll) as standalone FCGIs" :) Il y a même des gens qui génèrent les pages dynamiques en amont (en cache ?) pour pouvoir les servir en statique via nginx, geeeeeeeeeee...

@Alex~ osef, poseur.

Vince 5 years, 3 months ago

Bonjour,

Je rencontre le message "No input file specified. ".

Quand je remplace mon fichier index.php par index.html, le contenu HTML est bien affiché.

Je vois dans cet article qu'il peut s'agir d'un problème de droit, mais à quel niveau svp ?

Merci

Vince

Karaoké 4 years, 9 months ago

Merci pour ce tuto.
J'ai pu lancer mon premier serveur nginx avec. Je me lance aussi dans la configuration de l'url rewriting puis de l'installation de MongoDB pour faire une appli qui tourne bien et qui soit scalable.

En tout cas Nginx à l'air prometteur.

kit 4 years, 8 months ago

Bonjour,

Ne pas oublier de rendre le script executable:

chmod u+x /etc/init.d/php5-fcgi

puis :

update-rc.d php5-fcgi defaults

cordialement

Thales0796 4 years, 6 months ago

Juste à propos de l'hébergement, pour 3€ / mois il y a des VPS avec 250 Mo de RAM et un CPU correct, et pour qq euros supplémentaires on peu avoir 450 ou plus de RAM. LA game au dessus est les kimsufi de ovh, le premier à 9€ pour 2 go de RAM : http://www.kimsufi.com/fr/