Makina Blog

Le blog Makina-corpus

Bien débuter avec Nginx


Vous avez longtemps été un utilisateur plus ou moins averti de Apache HTTP Server et vous voulez commencer à tester nginx ? Voici quelques règles assez simples pour partir dans la bonne direction.

Règle numéro 1 : Évitez les convertisseurs de règles

Si vous faîtes l'erreur de taper « convert apache to nginx » dans Google vous tomberez sur un certain nombre de convertisseurs automatiques de règles Apache vers nginx (voir de convertisseurs de règles mod_rewrite en fait).

Ces outils sont des pièges. Nginx et Apache httpd ont des comportements et des règles parfois très similaires, mais il y a des façons modernes de penser la majorité des cas d'utilisation dans nginx qui, justement, permettent d'éviter d'écrire des règles de configuration spéciales pour un très grand nombre de cas. Utiliser un convertisseur va traduire des règles qui n'ont en fait pas du tout besoin d'être traduites et va générer une configuration très complexe, peu performante, et sans doute aussi fausse.

La vraie chose à faire est de prendre quelques minutes pour se familiariser avec les bonnes pratique de configuration nginx, pour découvrir, ravi, que l'immense majorité des cas sont en fais déjà prévus et se règlent sans instructions bizarres.

Pour cela deux pistes pour commencer, ce présent billet, avec quelques règles importantes, et le wiki nginx sur les erreurs de conf courantes (en anglais).

Règle numéro 2 : Pas de fichier .htaccess

J'aime bien cette règle parce que, personnellement, j'aime peu l'idée des fichiers .htaccess sur Apache. Ces fichiers, à ne pas confondre avec les fichiers .htpasswd qui servent à mettre en place de l'authentification HTTP, servent à déposer des éléments de configuration d'Apache dans des dossiers physiques. Ainsi si je créé un fichier /var/www/monsite/foo/.htaccess et que /var/www/monsite est le DocumentRoot d'un site Apache, je peux stocker des informations de configuration Apache qui s'appliquent au sous-dossier foo/ de ce site. Cela revient à la même chose (à quelques exceptions et restrictions près) qu'un lot d'instruction stockées dans une section <Directory /var/www/monsite/foo>, avec des performances légèrement moindres.

Cela met à disposition des administrateurs Apache une délégation de droits de configuration, où les responsables du déploiement des sources applicatives sont autorisés à altérer la configuration du serveur HTTP directement au sein des sources et non au sein de la configuration de ce serveur HTTP. Avec plusieurs gros défauts, le premier en terme de sécurité (on peut altérer la configuration depuis des fichiers déposés dans des dossiers avec des droits d'écriture très larges), mais aussi en terme d'organisation (on obtient difficilement une image globales de toutes les règles actives) ou de performance (la configuration devient par nature dynamique et Apache doit parser des éléments dynamiques de configuration en permanence).

** Cette fonctionnalité n'existe pas dans nginx**. Ne cherchez pas.

Donc si vous partiez en vous disant "je vais copier le contenu de mon .htaccess dans un convertisseur nginx", vous êtes en fait mal partis. Mais pas d'inquiétudes, nginx n'est pas si compliqué que cela.

Comme dans Apache on va retrouver plusieurs niveaux de configuration, niveau global, niveau HTTP (celui-là n'existe pas dans Apache, c'est parce que nginx peut supporter d'autres protocoles, pour faire proxy smtp par exemple), niveau Virtualhost (server en langage nginx), niveau Location… mais pas de fichier dynamique de configuration par répertoire.

Règle numéro 3 : Travailler sur les locations plutôt que sur les directory

Avec Apache il est possible de travailler sa configuration au niveau Location plutôt qu'au niveau Directory. Mais les usages sont plus souvent inverses. Sans doute à cause de l'importance des fichiers .htaccess dans la culture Apache. Ceux-ci se traduisent naturellement dans des configurations qui s'attachent à décrire les comportements en fonctions des répertoires physiques ciblés et non en fonction des locations visées dans les urls.

Avec nginx il faut prendre l'habitude de décrire sa configuration en partant des locations. C'est d'ailleurs plus naturel. Un directory Apache s'exprime avec des chemins absolus identifiants des emplacements physiques sur le disque, comme <Directory /var/www/monsite/foo>. Une section <Location /foo> est en fait plus naturelle, pour identifier tous les fichiers correspondant à un pattern fonctionnel du site.

Avec nginx vous allez retrouver des instructions de ce type:

location ~* /files/styles/ {
    (...)
}
location ~* ^.+\.(?:css|js|jpe?g|gif)$ {
    (...)
}
location = /index.php {
    (...)
}

Règle numéro 3-bis : Apprenez qu'il y a un ordre dans la lecture des locations

Et oui, ces instructions étant au coeur de la configuration il vaut mieux à minima se souvenir qu'il y a un ordre dans l'interprétation de ces règles par nginx. Retenir cet ordre est moins important, vous y reviendrez quand vous essaierez de déboguer les problèmes. Je vous le mets quand même :

Nginx essaye de prioriser le chemin le plus spécifique (/foo/bar/baz est plus spécifique que /foo/bar), tout en permettant de capturer un chemin dans une expression régulière. Et il est toujours possible de forcer un traitement pour un chemin précis, sans tenir compte des expressions régulières. Dans les faits cela se traduit ainsi :

  1. =. Les directives qui utilisent l'égalité sont prioritaires (et entre deux directives de ce type le chemin le plus spécifique est prioritaire) ;
  2. Nginx examine alors les règles de chemins exprimées sans égalité, en priorisant les chemins les plus spécifiques d'abord ;
  3. Les règles exprimées avec l'opérateur ^~, si elles correspondent, arrêtent la recherche de correspondances (fin de l'algo). Pour les autres règles il peut y avoir une règle correspondante, avec un chemin le plus spécifique possible, mais nginx ne s'arrête pas là, il passe au point suivant pour revenir ici au cas où le prochain point ne donnerait rien ;
  4. les règles contenant des expressions régulières sont examinées (~* - insensible à la casse - ou ~), dans l'ordre de lecture de la configuration (non plus sur le chemin le plus spécifique ici) ;
  5. si aucune expression régulière ne fonctionnait on utilise le résultat trouvé avant la recherche par expression régulière, au point 2, (souvent on retombe le location /, catch-all par nature).

La documentation officielle de l'instruction location est la référence en la matière (en anglais).

Cela veut dire que vous pouvez faire une configuration avec des chemins, sans opérateurs, forcer un chemin à s'appliquer sans autre forme de procès en utilisant le =, ajouter des expressions régulières pour capturer certains chemins, et éviter que certains de ces chemins ne soient matchés sur les expressions régulières en utilisant l'opérateur ^~ (qui ressemble aux opérateurs ~* et ~).

Et quelques petites règles qui sauvent la vie avec :

  • Nginx utilise des ; en fin d'instructions, n'oubliez pas ;
  • mettez des quotes " autour des expressions régulières qui contiennent des caractères spéciaux comme } (et attention, c'est également à faire dans les instructions rewrite) ;
  • on peut inclure des blocs location dans des blocs location ;
  • comme avec mod_rewrite dans Apache, les règles s'appliquent sans tenir compte du query string, le query string étant la partie de l'url qui contient des arguments (tout ce qu'il y a après le ?) ;
  • location / est un catch-all mais par contre location = / est une optimisation dédiée à éviter le traitement de recherche de la meilleur section de configuration pour les requêtes qui arrivent vraiment sur /.

Règle numéro 4 : Pour PHP il faut utiliser php-fpm

PHP-fpm est un service PHP qui tourne en dehors de serveur HTTP. Il n'y a pas de module du type mod_php pour nginx. PHP-fpm est aussi utilisable avec Apache httpd, et c'est en fait un outil très pratique pour mesurer les impacts CPU et mémoire du serveur HTTP d'un côté et PHP de l'autre.

La communication entre les deux services se fait au travers du protocole fastcgi, via une socket réseau ou une socket fichier (plus rapide mais uniquement locale). La communication via le pipe unix (socket fichier) est très difficile à mettre en place via Apache, et globalement la communication avec php-fpm est très difficile à mettre en œuvre avec les versions Apache 2.2 (qui cela dit commencent à être très anciennes). Au niveau de nginx, l'utilisation de php-fpm se fait sans aucun problème.

Règle numéro 5 : évitez les rewrite, try-files est votre ami

Pour faire des configurations avancées dans Apache, il est très courant d'utiliser mod_rewrite. Nginx possède aussi une sorte de trousse à outil nommée rewrite. Mais en fait la plupart des cas d'usages sont déjà prévus par d'autres types d'instructions (ce qui est aussi en partie le cas dans Apache, regardez "quand ne pas utiliser mod_rewrite" sur la doc apache).

Plus encore dans nginx que dans Apache, vous devriez garder rewrite pour des cas extrềmes ou de vraies redirections. Il ne faut surtout pas tenter de traduire cette horrible façon de dire « Regarde si le fichier existe, ou bien un dossier, et s'il n'existe pas alors c'est une url applicative et il faut transférer le tout à PHP » (extrait de la configuration Drupal) :

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]

On est bien là (modulo quelques variations subtiles) sur une tache de base de la plupart des applications, prendre en charge un plan d'adressage virtuel dans une application mais laisser la possibilité au serveur HTTP de déservir les fichiers qui correspondent effectivement à l'url s'ils existent dans le Document Root. Ce qu'on appelle couramment le "clean url", "SEO url", etc. Tellement courant qu'il existe en fait un moyen simple de l'écrire dans nginx :

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

Ou plutôt dans le cas présent:

location / {
    try_files $uri $uri/ /index.php?q=$uri&$args;
}

Et non cette horrible règle de traduction automatique:

location / {
    if (!-e $request_filename){
        rewrite ^(.*)$ /index.php?q=$1 break;
    }
 }

try_files comme son nom l'indique va tenter plusieurs choix. D'abord on va tester $uri en regardant si l'uri du fichier demandé correspond à un chemin vers un vrai fichier dans le doc root du site, puis $uri/ pour voir si en fait ce ne serait pas un directory (auquel cas la recherche du fichier d'index par défaut du dossier sera lancée) et si rien n'est trouvé on redirige (en interne) vers /index.php$is_args$args;. Le $is_args$args gère la query string, si il y a des arguments $is_args vaut ? et $args contient la liste des arguments ("foo=42&bar=toto" ou "/foo/423/bar/toto", ou encore "foo/42/bar/toto?page=2").

Si l'argument reçu est "foo/42/bar/toto?page=2&nb=100" on obtient:

  • $uri : "foo/42/bar/toto"
  • $args : "page=2&nb=100"

Et donc /index.php?q=$uri&$args donne bien une redirection interne vers: /index.php?q=foo/42/bar/toto&page=2&nb=100;

Règle numéro 6 : évitez les if

Il y a un point dans le wiki de nginx qui se nomme If is Evil, if est un mot clef qui existe dans la configuration nginx mais qui n'est qu'une variante très très particulière de ce que les gens s'attendent à trouver quand ils voient le mot if. Ce n'est pas un "si" classique. Tellement peu classique qu'il devrait être retiré ou renommé, mais les risques de casser des installations existantes sont trop forts.

Je cite :

Directive if has problems when used in location context, in some cases it doesn’t do what you expect but something completely different instead. In some cases it even segfaults. It’s generally a good idea to avoid it if possible.

Et je traduis (parce que je suis sympa) :

La directive if pose des problèmes quand elles est utilisée dans un contexte 'location', dans certains cas cela ne fait pas ce que vous attendez mais quelque chose de complètement différent à la place. Dans certain cas cela peut même planter. il est généralement préférable d'éviter d’utiliser if si possible.

Si vous utilisez if au sein d'une directive location vérifiez que le résultat est

  • soit un rewrite (...) last
  • soit un return (...)

On remarquera que le if renvoyé par le traducteur automatique dans la partie précédente terminait sur un rewrite break, qui ne fait pas partie de cette liste.

Essayez d'utiliser là encore le try_files. Utilisez sinon un des deux modes sûrs (le return et le rewrite last), regardez la doc du wiki, avec par exemple des return de code d'erreurs fictifs comme 418 pour envoyer vers des points de conf spécifiques liés à cette gestion d'erreur 418. Ce qui n'est pas forcément hyper élégant, mais qui par contre fonctionne dans tous les cas, contrairement au if.

Règle numéro 7 : map et geo sont vos amis

Si vous voulez utiliser nginx avec drupal, un projet est à étudier absolument: https://github.com/perusio/drupal-with-nginx

Dans ce projet vous disposez d'une configuration nginx très complète (trop, peut être), qui peut s'adapter à de nombreuses variations de Drupal. Personnellement j'ai plutôt tendance à picorer des morceaux de-ci de-là dans ce projet plutôt qu'à tenter un déploiement complet des tous ces fichiers.

Donc, en dehors du fait que ce projet est remarquable, je voulais en profiter pour vous signaler deux instructions très pratique, map et geo, dont on peut lire quelques exemples sur les configurations de ce projet.

Ces directives s'utilisent dans le contexte http, donc avant d'être dans le contexte server du virtualhost. Elle vont permettre de fixer des valeurs contextuelles à des variables que vous pourrez utiliser dans le virtualhost pour avoir une configuration dynamique. geo ne travaille que sur les adresses IP du client HTTP, map peut prendre en paramètre beaucoup plus d'éléments dynamiques.

Examinez l'usage de map dans le projet nginx Drupal par exemple : exemples de map

map $request_method $not_allowed_method {
    default 1;
    GET 0;
    HEAD 0;
    POST 0;
}

Vous disposez dès lors d'une variable $not_allowed_method qui ne vaut 0 que si $request_method est GET, HEAD ou POST. Variable que l'on retrouve plus tard dans un if au niveau du virtualhost (if c'est mal, mais pas là, on l'utilise bien avec un return)

 if ($not_allowed_method) {
    return 405;
 }

D'autres exemples plus diversifié sont présent dans la définition des variables liées à la gestion du cache.

Conclusion

Munis de ces quelques règles vous pouvez maintenant vous armer de votre moteur de recherche préféré et consulter moult exemples de configuration nginx qui vous aideront à mettre en place votre super serveur Web. Nginx est aussi un trés bon Reverse Proxy, il peut aussi faire office de Reverse Proxy Cache (un peu comme Varnish, si les règles restent raisonnables). Il y a forcément de nombreuses choses à étudier et à lire pour arriver à une configuration optimale, mais si au moins j'ai pu vous aider à éviter les convertisseurs automatiques de configuration alors cet article aura été utile.

Actualités en lien

Image
Migration Drupal 7 à Drupal 10
04/04/2024

Migration d'un site Drupal 7 en Drupal 10

Trucs, astuces et "bouts" de code pour migrer votre site web de Drupal 7 à Drupal 10. Compte-rendu d'une conférence donnée au Drupalcamp Rennes 2024.

Voir l'article
Image
Formation Migration Drupal 10
03/04/2024

Du nouveau dans notre gamme de forma­tions Drupal

Maîtri­sez le CMS Drupal de bout en bout avec notre panel complet de forma­tions couvrant la migra­tion (notre petite dernière), l’ad­mi­nis­tra­tion, le déve­lop­pe­ment et l’in­té­gra­tion Drupal. Pour deve­nir expert, plon­gez dans l’uni­vers Drupal !

Voir l'article
Image
Encart article DrupalCamp 2024
06/03/2024

Makina Corpus, parte­naire du Drupal­Camp 2024

Nous sommes fiers d’an­non­cer que Makina Corpus est le spon­sor du Drupal­Camp à Rennes. Notre expert vous y propose une confé­rence « migrer de Drupal 7 à Drupal 10 ».

Voir l'article

Inscription à la newsletter

Nous vous avons convaincus