A place to cache linked articles (think custom and personal wayback machine)
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

index.md 38KB

il y a 8 mois
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. title: Optimisez Nginx pour de meilleurs performances
  2. url: https://buzut.net/optimiser-nginx/
  3. hash_url: b33059055c307477e43390a51f9a104a
  4. archive_date: 2024-03-13
  5. og_image: https://d33wubrfki0l68.cloudfront.net/f87f6e0bd31d5f46d08cde68fbb8ee51bb3a9e91/969de/img/logo.png
  6. description: Nginx est réputé pour sa rapidité, mais quand votre serveur fait face à un trafic énorme, il est possible d'en tirer encore plus. Voyons comment !
  7. favicon: https://buzut.net/img/favicon.png
  8. language: fr_FR
  9. <p>Nginx de part sa robustesse, sa structure minimaliste et son fonctionnement événementiel asynchrone est un serveur web plébiscité pour ses performances. Mais ce n’est pas parce qu’on a une Porsche qu’il ne faut pas tenter de la rendre encore plus puissante !</p>
  10. <p>Pour des sites à fort trafic, un serveur bien optimisé signifie deux choses : des requêtes servies plus vite et un besoin en machines (scaling horizontal) inférieur. Alors pourquoi s’en priver ?</p>
  11. <nav class="toc">
  12. <p>Au programme</p>
  13. <ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#Les-workers"><span class="toc-text">Les workers</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Les-buffers"><span class="toc-text">Les buffers</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Les-timeouts"><span class="toc-text">Les timeouts</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#La-compression"><span class="toc-text">La compression</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Le-cache-statique"><span class="toc-text">Le cache statique</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#L%E2%80%99open-file-cache"><span class="toc-text">L’open file cache</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Le-logging"><span class="toc-text">Le logging</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#La-speciale-TLS"><span class="toc-text">La spéciale TLS</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Optimisations-TCP"><span class="toc-text">Optimisations TCP</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#La-thread-pool"><span class="toc-text">La thread pool</span></a></li></ol>
  14. </nav>
  15. <h2 id="Les-workers">Les workers</h2><p>La première chose à explorer est le fichier de configuration général de Nginx : <code>/etc/nginx/nginx.conf</code>. Nous allons ici définir le nombre de workers ainsi que le nombre de connexions pour chacun d’entre eux.</p>
  16. <p>Concentrons nous d’abord sur <code>worker_processes</code>. Cette directive spécifie le nombre total de workers à créer au démarrage de Nginx. La valeur optimale est d’en avoir un par <em>cpu core</em>. Si vous avez un VPS – lesquels ont souvent un vCPU avec un seul vCore – il arrive souvent que la valeur par défaut soit supérieure au nombre total de cores. Ce n’est pas très grave, néanmoins, les processus supplémentaires vont un peu se tourner les pouces… pas bien utile. Pour définir cette valeur, il nous suffit donc de déterminer le nombre total de cores :</p>
  17. <pre><code># grep -c ^processor /proc/cpuinfo
  18. 12</code></pre><p>Nous avons notre réponse, ici : 12.</p>
  19. <p>Passons maintenant à la directive <code>worker_connections</code>. Elle spécifie combien de connections simultanées chaque worker est en mesure d’établir. Étant donné qu’une connexion nécessite un <em>file descriptor</em> au minimum, une bonne base est d’établir ce nombre en fonction des limites de notre système. Dans le cas de Nginx en reverse proxy, il faut un <em>file descriptor</em> pour la connexion client et un autre vers le serveur proxifié, soit deux par connexion.</p>
  20. <pre><code># ulimit -n
  21. 1024</code></pre><p>Par défaut, la limite du nombre de fichiers ouverts est souvent de 1024. Selon la configuration du système, cette limite peut être très largement augmentée. Voyez mon <a href="/optimiser-gestion-ressources-systeme-ulimit/">article sur <em>ulimit</em></a> afin de modifier ces configurations.</p>
  22. <p>Vous lirez peut-être dans certains articles qu’il faut définir la directive <code>worker_rlimit_nofile</code>. Cette dernière a justement pour but de configurer le <em>ulimit</em>. Il faut impérativement que le serveur démarre en root avant de passer à un utilisateur non privilégié, sans quoi il ne pourra augmenter cette limite. Par ailleurs, s’il y a d’autres logiciels qui utilisent le même utilisateur – nginx sous Debian – il est bon de leur en laisser quelques uns. Vous devrez dans ce cas avoir une valeur pour <code>worker_connections</code> légèrement inférieur à <code>worker_rlimit_nofile</code>.</p>
  23. <p>Pour ma part, sauf cas particuliers, je trouve plus fiable de paramétrer le ulimit en utilisant la configuration du système et de se contenter d’attribuer la même valeur au <em>ulimit</em> et à <code>worker_connections</code> (si Nginx est seul à tourner avec ce user).</p>
  24. <p>En faisant <code>worker_processes</code> x <code>worker_connections</code>, on peut déjà se faire une idée du nombre de connexions simultanées que Nginx va pouvoir encaisser. Bien entendu, d’autres paramètres entrent en ligne de compte.</p>
  25. <p>Par ailleurs, il ne s’agit pas non plus de s’obstiner à atteindre le plus grand nombre possible de connexions. Mieux vaut en effet servir 1000 connexions simultanées en 1s et passer aux 1000 suivantes, que d’en servir 2000 en 4s.</p>
  26. <p>En dernier lieu, on permet aux workers d’accepter plusieurs nouvelles connexions de manière simultané en activant <a href="http://nginx.org/en/docs/ngx_core_module.html#multi_accept"><code>multi_accept</code></a>. Cela peut être d’une grande utilité lors de pics de trafic.</p>
  27. <pre><code>multi_accept on;</code></pre><h2 id="Les-buffers">Les buffers</h2><p>Les buffers permettent à Nginx de travailler en RAM plutôt que sur le disque. Je ne vais pas vous faire un dessin, vous savez bien que les accès disque sont infiniment plus lents que le travail en RAM. On va donc configurer les buffers pour que notre serveur puisse travailler en mémoire autant que faire se peut.</p>
  28. <p>Il y a quatre types de buffers :</p>
  29. <dl>
  30. <dt><a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_buffer_size"><code>client_body_buffer_size</code></a></dt>
  31. <dd>Le buffer qui récupère les données clients (typiquement les données POST).</dd>
  32. <dt><a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#client_header_buffer_size"><code>client_header_buffer_size</code></a></dt>
  33. <dd>Celui-ci s'occupe également des données du client, mais concerne l'en-tête. Généralement, 1k suffit ici amplement (la valeur par défaut). Qui plus est, dans le cas où cette limite est dépassée, alors c'est la directive <code>large_client_header_buffers</code> qui s'applique.</dd>
  34. <dt><a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size"><code>client_max_body_size</code></a></dt>
  35. <dd>La taille maximum des requêtes envoyées par le client. Si vous autorisez des uploads de fichiers, il s'agit d'y penser ici. Si cette limite est dépassée, Nginx retourne une erreur 413.</dd>
  36. <dt><a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#large_client_header_buffers"><code>large_client_header_buffers</code></a></dt>
  37. <dd>Taille et nombre maximum que peuvent atteindre les buffers pour les en-têtes. Au delà, une erreur est retournée.</dd>
  38. </dl>
  39. <p>Les réglages des buffers sont plus délicats et subjectifs que ceux des workers. Cela dépend énormément de votre application. <code>client_body_buffer_size</code> définit la quantité maximale des données <code>GET</code> ou <code>POST</code> qui seront stockées en RAM. La question à se poser est donc la suivante : quelle proportion de vos requêtes sont supérieur à une taille donnée ? Ces requêtes nécessitent-elles d’être traitée très rapidement ? Par exemple, pour l’upload de gros fichiers, il est tout à fait tolérable d’écrire les fichiers sur le disque. En effet, le temps supplémentaire liée à l’écriture sur le disque est dérisoire par rapport au temps d’upload.</p>
  40. <p>En revanche, il faut bien avoir à l’esprit que plus cette limite est élevée, plus votre serveur prête le flanc aux attaques DoS, puisqu’il allouera potentiellement plus de mémoire aux requêtes et arrivera donc potentiellement plus rapidement à saturation en RAM.</p>
  41. <p>La logique prévalente pour les buffers est souvent d’essayer et d’aviser ! Dans une application permettant l’upload de fichiers, je laisserais <code>client_body_buffer_size</code> à sa valeur par défaut de <code>16k</code> (sur processeurs 64 bits), idem pour <code>client_header_buffer_size</code> qui est de <code>1k</code>.</p>
  42. <p><code>client_max_body_size</code> aura ici une valeur élevée puisque je veux autoriser l’upload de fichiers (disons <code>20m</code>, là encore tout dépend du type de fichiers !). Enfin, je serais plus conservateur que les défauts (<code>4 8k</code>) pour <code>large_client_header_buffers</code>, et mettrais <code>2 3k</code> car mon applications possède des headers relativement modestes. En résumé :</p>
  43. <pre><code>client_body_buffer_size 16K;
  44. client_header_buffer_size 1k;
  45. client_max_body_size 20m;
  46. large_client_header_buffers 2 3k;</code></pre><p>J’insiste de nouveau sur ce point, tout dépend de votre application. Si votre applicatif soumet de gros articles en POST, il faudra peut-être revoir à la hausse les <code>16k</code> de <code>client_body_buffer_size</code>. Il n’y a ici pas de règle générale.</p>
  47. <h3 id="Proxy-buffers">Proxy buffers</h3><p>Il existe des buffers spécifiquement dédiés aux proxies. Dans le cas où les buffers sont désactivés, Nginx commence l’envoie des données au client aussitôt qu’il les reçoit du backend serveur. Si le client est rapide, tout est pour le mieux. Cependant, si le client est moins véloce, ce fonctionnement oblige à conserver une connexion ouverte entre Nginx – le serveur de proxy – et le backend serveur ; ce qui peut s’avérer dommageable.</p>
  48. <p>L’activation des buffers permet donc à Nginx de d’abord récupérer l’ensemble des données de la requêtes depuis le backend serveur, de libérer ce dernier, puis de servir les données au client.</p>
  49. <dl>
  50. <dt><a href="http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering"><code>proxy_buffering</code></a></dt>
  51. <dd>Contrôle l'activation du buffer pour le proxy (activé par défaut).</dd>
  52. <dt><a href="http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffer_size"><code>proxy_buffer_size</code></a></dt>
  53. <dd>Définie la taille du buffer pour les en-têtes de la réponse. <code>8k</code> par défaut sur systèmes 64 bits. On peut ici laisser la valeur par défaut car les en-têtes dépassent rarement <code>8k</code>.</dd>
  54. <dt><a href="http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffers"><code>proxy_buffers</code></a></dt>
  55. <dd>Détermine la taille et le nombre des buffers pour le corps de la réponse. Une fois n'est pas coutume, la valeur dépendra grandement de votre application. La valeur par défaut (toujours pour les systèmes 64 bits) est de 8 buffers de <code>8k</code>. Il s'agit de paramètres s'appliquant <strong>par requête</strong>. Ainsi, le réglage par défaut permettra de stocker dans les buffers des réponses jusqu'à <code>64kb</code>. À vous de voir si votre application retourne des résultats plus importants (sachant qu'ensuite les fichiers sont <a href="http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_max_temp_file_size">écrits sur le disque</a>).</dd>
  56. </dl>
  57. <pre><code>proxy_buffering on;
  58. proxy_buffer_size 1k;
  59. proxy_buffers 12 4k;</code></pre><h2 id="Les-timeouts">Les timeouts</h2><p>Question existentielle s’il en est, les timeouts peuvent avoir un impact conséquent à la fois sur la vitesse ressentie par les utilisateurs, mais aussi sur la charge du serveur. Les timeouts se divisent également en plusieurs directives :</p>
  60. <dl>
  61. <dt><a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_timeout"><code>client_body_timeout</code></a></dt>
  62. <dd>Ce timeout s'applique au body. Il définit le temps maximum entre deux opérations d'écriture (pas le temps total de transfert donc). Admettons que je veuille transférer de gros fichiers (plusieurs centaines de MB), je pourrais fixer ce timeout à 30s (défaut 60s). Si le client n'envoie aucune donnée dans ce laps de temps, le serveur émet une erreur 408.</dd>
  63. <dt><a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#client_header_timeout"><code>client_header_timeout</code></a></dt>
  64. <dd>Même logique que la directive précédente mais le timeout s'applique bien ici à la totalité de la transaction. Néanmoins, les en-têtes étant beaucoup plus légères, je me contenterai pour ma part d'établir le timeout à 10s (défaut 60s) pour les headers.</dd>
  65. <dt><a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_timeout"><code>keepalive_timeout</code></a></dt>
  66. <dd>Cette directive permet à la fois de spécifier le <em>keepalive timeout</em> mais également le header <em>Keep-Alive: timeout=durée</em>.
  67. Avec des serveurs tels qu'Apache où le serveur conserve un thread par connexion ouverte, de telles connexions impliquent une consommation de mémoire. Cependant, avec des serveurs événementiels comme Nginx, ce coût est relativement faible et il n'est donc pas très impactant en consommation ressources.
  68. Une fois n'est pas coutume, la valeur idéale dépendra de la typologie de votre application. L'effet du keepalive se fait surtout ressentir lorsque de nombreux éléments sont à télécharger puisqu'on gagne <em>nbr_ressources x temps_connexion_tcp</em>. C'est évidemment moins flagrant pour une requête unique. Si votre application nécessite de nombreuses requêtes à intervalles régulières (<em>polling ajax</em> par exemple), il pourra être judicieux de conserver un keepalive long. Au contraire, dans le cas d'un site plus statique, comme un blog, si aucune autre connexion n'est envisagée après le chargement de la page et de ses ressources, on pourra avoir un keepalive assez court.
  69. L'intérêt du keepalive est d'autant plus grand si vous êtes en https. En effet, à chaque ouverture de connexion, il vous faudra renégocier un <a href="https://fr.wikipedia.org/wiki/Three-way_handshake">Three-way handshake TLS</a>, ce qui prend du temps et demande de la puissance au serveur.
  70. Le keepalive est à 75s par défaut, il n'y a vraiment pas de règles en la matière. On peut estimer que 30s est une valeur standard, mais on pourra même l'abaisser à 10s dans notre exemple de blog. HTML5 boilerplate le définit même à 300 dans sa configuration Nginx ! Comme il n'est pas très risqué de laisser la valeur élevée, on peut donc aisément le mettre à plusieurs minutes et ne l'abaisser qu'en cas de problème de performances face à un fort trafic.</dd>
  71. <dt><a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#send_timeout"><code>send_timeout</code></a></dt>
  72. <dd>C'est en quelque sorte le pendant inverse des body et header timeouts. Ici, le s'agit de définir le temps après lequel le serveur coupe la connexion si le client ne reçoit plus la réponse. Par défaut, 60s.</dd>
  73. <dt><a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_requests"><code>keepalive_requests</code></a></dt>
  74. <dd>Cette directive détermine le nombre de requêtes au bout duquel le connexion sera fermée. La valeur par défaut est à 100, ce qui est assez confortable et il n'est souvent pas nécessaire de modifier cette valeur. Néanmoins, si votre application nécessite le chargement de très nombreuses ressources (&gt; 100), il peut être intéressant d'augmenter cette valeur pour qu'elle soit légèrement supérieur au nombre de ressources à charger.</dd>
  75. </dl>
  76. <p>Comme expliqué, étant donné le faible impact de performance sur Nginx, je préfère être généreux au départ et abaisser les timeouts au besoin :</p>
  77. <pre><code>client_body_timeout 30;
  78. client_header_timeout 10;
  79. keepalive_timeout 30;
  80. send_timeout 60;
  81. keepalive_requests 100;</code></pre><h2 id="La-compression">La compression</h2><p>Il ne fait aucun doute que la compression permet d’accélérer les transferts sur le réseau. Il est aussi un fait que compresser les ressources demande un peu de puissance processeur. Comme il existe plusieurs niveau de compression, il s’agit de trouver le juste milieu.</p>
  82. <pre><code>gzip on;
  83. gzip_comp_level 5;
  84. gzip_min_length 1000;
  85. gzip_proxied any;
  86. gzip_types
  87. application/atom+xml
  88. application/javascript
  89. application/json
  90. application/ld+json
  91. application/manifest+json
  92. application/rss+xml
  93. application/vnd.geo+json
  94. application/vnd.ms-fontobject
  95. application/x-font-ttf
  96. application/x-web-app-manifest+json
  97. application/xhtml+xml
  98. application/xml
  99. font/opentype
  100. image/bmp
  101. image/svg+xml
  102. image/x-icon
  103. text/cache-manifest
  104. text/css
  105. text/plain
  106. text/vcard
  107. text/vnd.rim.location.xloc
  108. text/vtt
  109. text/x-component
  110. text/x-cross-domain-policy;</code></pre><p>Voici en général mes réglages. Le niveau de compression 5 est le juste milieu <a href="http://serverfault.com/questions/253074/what-is-the-best-nginx-compression-gzip-level#answer-452642">selon moi</a> – on pourra toujours le baisser si on a trop de charge CPU. On ne compresse pas les fichiers de taille inférieure à 1ko (on n’y gagnera pas grand chose), on compresse sans distinction les éléments qui proviennent d’un proxy et on active la compression pour les fichiers textes, le js, les css, le json, le xml et d’autres un peu moins courants. Vous ne trouverez ici pas les images jpeg et png car ce sont déjà des fichiers compressés.</p>
  111. <p>Sachez également que la directive <a href="http://nginx.org/en/docs/http/ngx_http_gzip_module.html#gzip_buffers"><code>gzip_buffers</code></a> peut s’avérer intéressante. Elle définie le nombre et la taille des buffers alloué à la compression. Par défaut, sur les architectures 64 bits, jusqu’à 16 buffers de 8k sont autorisés.</p>
  112. <p>Tout va ici dépendre de la taille de vos fichiers. Il s’agit, comme <a href="https://forum.nginx.org/read.php?2,239316,239344#msg-239344">expliqué dans ce post</a> d’arbitrer entre le nombre de buffers (gérer de nombreux buffers consomme un peu de CPU) ou attribuer plus d’espace aux buffers (utilise plus d’espace mémoire). Si vous n’avez pas assez d’espace dans les buffers pour contenir l’ensemble de la réponse, Nginx attendra qu’une partie de la réponse soit envoyée au client et utilisera ensuite l’espace libéré.</p>
  113. <h3 id="Pre-compression">Pré-compression</h3><p>Enfin, sachez aussi qu’il est possible, avec le module <a href="http://nginx.org/en/docs/http/ngx_http_gzip_static_module.html">gzip_static</a>, de pré-compresser des fichiers pour que Nginx puisse les servir sans avoir à les compresser à la volée.</p>
  114. <p>Vous en avez peut-être entendu parler, Brotli est un algorithme de compression plus récent que Gzip qui offre de bien meilleurs taux de compression. Le <a href="https://github.com/google/ngx_brotli">module Brotli</a> développé par Google n’est pas nativement intégré à Nginx et nécessite de le recompiler.</p>
  115. <p>Nous ne pourrons donc pas compresser directement en Brotli. Cependant, nous pouvons profiter de la pré-compression en Brotli lors du <a href="/npm-for-everything/">processus de build</a> et servir ces fichiers via quelques directives. C’est la stratégie que <a href="/configuration-dun-serveur-linux-apache2/#compression">j’adopte avec Apache</a>. On va donc créer un fichier de config pour Nginx <code>/etc/nginx/conf.d/static_brotli.conf</code>.</p>
  116. <pre><code>set $extension "";
  117. # si le navigateur accepte brotli
  118. if ($http_accept_encoding ~ br) {
  119. set $extension .br;
  120. }
  121. # et que le fichier .br existe sur le disque
  122. if (-f $request_filename$extension) {
  123. rewrite (.*) $1$extension break;
  124. }
  125. # on désactive gzip et en envoie en .br
  126. location ~ /*.html.br {
  127. gzip off;
  128. types {}
  129. default_type text/html;
  130. add_header Content-Encoding br;
  131. add_header Vary "Accept-Encoding";
  132. }
  133. location ~ /*.css.br$ {
  134. gzip off;
  135. types {}
  136. default_type text/css;
  137. add_header Content-Encoding br;
  138. add_header Vary "Accept-Encoding";
  139. }
  140. location ~ /*.js.br$ {
  141. gzip off;
  142. types {}
  143. default_type application/javascript;
  144. add_header Content-Encoding br;
  145. add_header Vary "Accept-Encoding";
  146. }
  147. location ~ /*.svg.br$ {
  148. gzip off;
  149. types {}
  150. default_type image/svg+xml;
  151. add_header Content-Encoding br;
  152. add_header Vary "Accept-Encoding";
  153. }</code></pre><p>Il ne vous reste plus qu’à l’inclure depuis n’importe quelle directive <code>server</code> ou <code>location</code>. Par exemple, depuis un fichier de configuration d’un site :</p>
  154. <pre><code>server {
  155. listen [::]:443;
  156. listen 443;
  157. server_name super-site.com;
  158. root /var/www;
  159. charset utf-8;
  160. location / {
  161. try_files $uri $uri.html $uri/ =404;
  162. }
  163. # tous vos fichiers seront servis en brotli pour les navigateurs compatibles
  164. include conf.d/static_brotli.conf;
  165. # brotli ne fonctionne qu'en TLS
  166. include conf.d/ssl.conf;
  167. ssl_certificate /etc/letsencrypt/live/super-site.com/fullchain.pem;
  168. ssl_certificate_key /etc/letsencrypt/live/super-site.com/privkey.pem;
  169. ssl_trusted_certificate /etc/letsencrypt/live/super-site.com/chain.pem;
  170. }</code></pre><h2 id="Le-cache-statique">Le cache statique</h2><p>Grand classique du web, permettre aux navigateurs et proxies intermédiaires de placer certaines ressources en cache, c’est éviter au serveur d’avoir à les renvoyer plus tard. Selon la fréquence à laquelle changent vos fichiers, on peut attribuer une période de validité plus ou moins longue au cache. Disons deux mois pour l’exemple :</p>
  171. <pre><code>location ~* .(jpg|jpeg|png|gif|ico|css|js)$ {
  172. expires 60d;
  173. }</code></pre><p>Nous cachons donc pour deux mois tous les fichiers d’extensions suivantes : jpg, jpeg, png, gif, ico, css, js.</p>
  174. <h2 id="L’open-file-cache">L’open file cache</h2><p>Ce type de cache permet de conserver les métadonnées en mémoire, et donc de limiter l’I/O. Voici un exemple de configuration :</p>
  175. <pre><code>open_file_cache max=2000 inactive=5m;
  176. open_file_cache_valid 2m;
  177. open_file_cache_min_uses 2;
  178. open_file_cache_errors on;</code></pre><p>Cette configuration indique au serveur de mettre en cache 2000 <em>open file descriptors</em> et de les fermer si aucune requête les concernant n’est demandé au bout de 5 minutes. La validité des informations en cache est revérifiée après 2 minutes et un fichier doit être requêté un minimum de deux fois afin d’être valide pour le cache. Enfin, les fichiers d’erreurs sont ici également valables pour le cache.</p>
  179. <h2 id="Le-logging">Le logging</h2><p>Demander à NGINX d’écrire des logs, c’est encore lui demander une chose supplémentaire. Évidemment, le plus performant, c’est encore de ne pas logger ! On parle bien de l’<code>access_log</code>, pas de l’<code>error_log</code> qu’il n’est pas question de supprimer. D’autant plus que, théoriquement, il ne devrait pas y avoir trop d’erreurs donc la charge est faible concernant ce dernier.</p>
  180. <p>Pour l’<a href="http://nginx.org/en/docs/http/ngx_http_log_module.html#access_log"><code>access_log</code></a>, si vous ne l’exploitez pas, le mieux est encore de le désactiver complètement avec <code>access_log off;</code>. Cette directive prend sa place dans <code>http</code>, <code>server</code> ou <code>location</code>. Vous pouvez donc définir un comportement global directement dans le <code>nginx.conf</code> et/ou affiner seulement pour certains domaines ou répertoires.</p>
  181. <p>Si vous ne souhaitez pas le désactiver totalement, d’autres options s’offrent à nous. Déjà, autant utiliser un buffer afin de réduire les I/O disque.</p>
  182. <pre><code>access_log /var/log/nginx/access.log combined buffer=10m flush=5m;</code></pre><p>Dans l’exemple précédent, vous écrivez les logs sur le disque soit lorsque le buffer de 10MB est plein, soit toutes les 10mn (première condition remplie). Cela est bien entendu à moduler selon votre trafic. Pour un blog avec quelques centaines de visites pas jour, les 10MB de logs ne seront jamais atteints en 5 minutes. En revanche, pour un site à très fort trafic, à vous de voir s’il n’est pas judicieux d’augmenter la taille du buffer. Gardez cependant à l’esprit qu’en cas de crash, tout ce qui n’est pas écrit sur le disque est perdu.</p>
  183. <p>Pour logger mais logger moins, il est également possible de logger de manière conditionnelle. On peut par exemple n’être intéressé que par les erreurs 4xx ou vouloir écrémer le gros des 2xx et 3xx qui indiquent juste que tout fonctionne bien.</p>
  184. <pre><code># on ne log pas les 2xx &amp; 3xx
  185. map $status $loggable {
  186. ~^[23] 0;
  187. default 1;
  188. }
  189. access_log /var/log/nginx/access.log combined buffer=10m flush=5m if=$loggable;</code></pre><p>Petite précision au passage, la directive <code>map</code> prend sa place dans un bloc <code>http</code>. Si vous la placez dans un fichier de configuration vhost dans server ou location, vous aurez une belle erreur. On place dans notre directive <code>map</code> au niveau <code>http</code> et la directive <code>access_log</code> dans location si elle ne doit pas s’appliquer de manière globale.</p>
  190. <p>Par ailleurs, il arrive que l’on veuille définir une règle en fonction de plusieurs variables. Prenons un exemple concret. Nous avons une api à fort trafic et nous ne voulons donc pas tout logger par défaut. Nous nous contentons de logguer les erreurs avec la règle vue ci-dessus.</p>
  191. <p>Néanmoins, certaines url sont particulièrement sensibles. Par exemple, nous avons un endpoint qui permet de créer un compte utilisateur. Nous ne voulons pas laisser un bot créer des comptes à la volée. Nous allons donc logger cette url en particulier et demander à fail2ban de <a href="/installer-et-parametrer-fail2ban/">bloquer les ips qui y accèdent de manière récurrente</a>.</p>
  192. <pre><code># on ne log pas les 2xx &amp; 3xx
  193. map $status $loggable_status {
  194. ~^[23] 0;
  195. default 1;
  196. }
  197. # ici si l'url contient signup, on logge
  198. map $request $loggable {
  199. ~signup 1;
  200. default $loggable_status;
  201. }
  202. access_log /var/log/nginx/access.log combined buffer=10m flush=5m if=$loggable;</code></pre><h2 id="La-speciale-TLS">La spéciale TLS</h2><p>Bien que Nginx nomme toutes les variables SSL, vous savez très bien que le protocole est définitivement à bannir au profit de TLS. Petite parenthèse, si la sécurité vous intéresse, sachez qu’au delà du protocole, c’est aussi la suite de chiffrement qui assure la sécurité. Vous pouvez tester la sécurité de votre site avec ce <a href="https://www.ssllabs.com/ssltest/">scanner SSL/TLS</a> et générer vos configurations serveurs avec le <a href="https://mozilla.github.io/server-side-tls/ssl-config-generator/">générateur de Mozilla</a>.</p>
  203. <p>Bref, revenons-en à nos moutons. Nous l’avons dit, le three-way handshake prend du temps et est couteux en ressources. Puisque le client et le serveur ce sont déjà entendus, il est possible de dire à Nginx de mettre en cache cette session. Ainsi, lorsqu’il voudra de nouveau établir une connexion, le client n’aura plus qu’à se rappeler aux bons souvenirs de Nginx. Le three-way handshake ne compte plus que deux allez-retours entre client et serveur et Nginx s’épargne quelques calculs cryptographiques. Bien que datant de 2011, ce <a href="http://vincent.bernat.im/en/blog/2011-ssl-session-reuse-rfc5077.html">post</a> vous éclairera sur la théorie derrière cette histoire de cache.</p>
  204. <p>Trois paramètres sont à considérer :</p>
  205. <dl>
  206. <dt><a href="http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_cache"><code>ssl_session_cache</code></a></dt>
  207. <dd>Il permet de définir si on active le cache on non, de spécifier le type de cache (<code>builtin</code> ou <code>shared</code>) ainsi que la taille de ce dernier. Il est à <code>none</code> par défaut. La doc Nginx établit que 1MB permet de stocker 4000 sessions. À vous de juger combien de à sessions vous estimez devoir faire face et la durée de rétention que vous leur accordez. N'ayez crainte, au pire des cas Nginx invalidera les sessions prématurément, il n'y aura pas d'effusion de sang.</dd>
  208. <dt><a href="http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_timeout"><code>ssl_session_timeout</code></a></dt>
  209. <dd>Précise la durée au bout de laquelle la session est invalidée.</dd>
  210. <dt><a href="http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_buffer_size"><code>ssl_buffer_size</code></a></dt>
  211. <dd>Permet de déterminer pour l'envoie des données. <a href="https://github.com/igrigorik/istlsfastyet.com/issues/63">Gros débat</a> sur la question de la taille de ce dernier, une fois de plus, tout dépend du type de contenus que vous servez.</dd>
  212. </dl>
  213. <p>Et bien entendu quelques valeurs d’exemples :</p>
  214. <pre><code>ssl_session_cache shared:SSL:10m;
  215. ssl_session_timeout 24h;
  216. ssl_buffer_size 1400;</code></pre><p>En plus des caches, il y a une dernière optimisation qu’il est possible de réaliser, elle a pour doux nom l’<a href="https://fr.wikipedia.org/wiki/Agrafage_OCSP"><em>OSCP stapling</em></a>. Lorsqu’un serveur fourni un certificat, le client en vérifie la validité en interrogeant l’autorité émettrice du certificat. Évidemment, cela demande une requête supplémentaire au client. On peut éviter cela en demandant au serveur de joindre directement l’autorité émettrice et d’ainsi “agraffer” (d’où le <em>stampling</em>) la réponse signée et horodatée de l’autorité afin de prouver la validité du certificat.</p>
  217. <pre><code>ssl_stapling on;
  218. ssl_stapling_verify on;
  219. resolver 8.8.8.8 8.8.4.4 216.146.35.35 216.146.36.36 valid=60s;
  220. resolver_timeout 2s;</code></pre><p>Il est possible de préciser un resolver, en cas d’absence de la directive <code>resolver</code>, le serveur DNS par défaut sera interrogé. C’est d’ailleurs souvent avantageux car dans le cas d’un serveur, il se trouve en général dans le même datacenter.</p>
  221. <h2 id="Optimisations-TCP">Optimisations TCP</h2><p>On arrive là dans le domaine de l’optimisation de pointe ! Il est possible de spécifier à Nginx la manière dont quelques options de TCP doivent être gérées. De manière synthétique, ces réglages vont nous permettre de :</p>
  222. <ul>
  223. <li><code>sendfile</code> : d’optimiser la copie des données depuis le <em>file descriptors</em> vers le buffer du kernel (ok, c’est compliqué !),</li>
  224. <li><code>tcp_nopush</code> : n’envoyer les données que lorsque le paquet IP est rempli (quantité de données égale au <a href="https://fr.wikipedia.org/wiki/Maximum_Segment_Size">MSS</a>),</li>
  225. <li><code>tcp_nodelay</code> : diminuer la latence de 200ms avant l’envoie des données sur le réseau (on attend ici pas que le paquet soit rempli. Utilisés conjointenenement avec <code>tcp_nopush</code>, NGINX n’active l’option <code>tcp_nodelay</code> que lorsqu’on atteint la fin des données à envoyer et qu’il n’est pas possible de remplir un paquet).</li>
  226. </ul>
  227. <p>Pour comprendre tout cela dans les détails, vous pouvez vous référer à ce <a href="https://t37.net/optimisations-nginx-bien-comprendre-sendfile-tcp-nodelay-et-tcp-nopush.html">très bon article</a> qui plonge littéralement dans les spécificité du noyau.</p>
  228. <pre><code>sendfile on;
  229. tcp_nopush on;
  230. tcp_nodelay on;</code></pre><p>Dans le cas de très nombreuses connexions entre le proxy et le serveur backend, on peut faire face à un problème. Le <em>TCP Maximum Segment Lifetime</em> définit la durée de vie maximale d’une connexion TCP et impacte directement le <code>TIME_WAIT</code> – temps à attendre avant de fermer une connexion TCP, égale à <em>2*MSL</em>.</p>
  231. <p>Là où le bas peut blesser, c’est qu’un grand nombre de connexions en <code>TIME_WAIT</code> peut empêcher d’en ouvrir de nouvelles si on arrive à épuisement du nombre de connexions ouvertes entre deux ip – défini par le couple port/ip, soit le nombre de ports définis dans <code>/proc/sys/net/ipv4/ip_local_port_range</code>, en général ± 30 000. Dans cette configuration, on ne pourra effectuer plus de <em>30000/(2 x MSL)</em> requêtes par secondes, soit <em>30000/(2 x 30)</em>, soit 500 req/sec.</p>
  232. <p>Plusieurs solutions sont possibles :</p>
  233. <ul>
  234. <li>activer keepalive pour maintenir les connexions ouvertes,</li>
  235. <li>utiliser <code>net.ipv4.tcp_tw_reuse</code> afin de recycler plus rapidement les connexions en <code>TIME_WAIT</code>,</li>
  236. <li>diminuer le <code>tcp_fin_timeout</code>.</li>
  237. </ul>
  238. <p>Le plus efficace et le moins risqué selon moi est d’utiliser keepalive. Cependant, tous les serveurs ne le prennent pas en charge (Haproxy par exemple). Il vous reste donc au choix de diminuer le <code>tcp_fin_timeout</code> ou d’utiliser <code>net.ipv4.tcp_tw_reuse</code> (ou les deux). Je vous invite à lire <a href="http://vincent.bernat.im/fr/blog/2014-tcp-time-wait-state-linux.html">cet article de qualité</a>, lequel traite du problème de connexions en <code>TIME_WAIT</code> et des remèdes potentiels (dont <code>tcp_fin_timeout</code>).</p>
  239. <p>Vous pourrez modifier ces paramètres respectivement dans <code>/proc/sys/net/ipv4/tcp_fin_timeout</code> et <code>/proc/sys/net/ipv4/tcp_tw_reuse</code>.</p>
  240. <p>En dernier lieu, nous allons nous pencher sur quelques optimisations qui s’effectuent au niveau du block <code>server</code>, elles se placeront donc en général dans un VHOST.</p>
  241. <p>Nous l’avons déjà vu plus haut, une connexion TCP s’effectue en trois temps, cela porte le doux nom de <a href="https://fr.wikipedia.org/wiki/Three-way_handshake">Three-way handshake</a>. En gros, cela demande trois échanges de trames TCP/IP entre le client et le serveur. Le premier envoie un paquet SYN (demande de connexion [<em>synchronize</em>]), le second répond SYN/ACK (en gros : 5/5, Roger! [synchronize acknowledge]) et enfin, le client confirme que tout est ok avec un ACK.</p>
  242. <p>À partir de là, la connexion est ouverte, et théoriquement, un socket est créé et le processus en écoute sur ce socket est réveillé : attribution de ressources Nginx dans le cas présent. Pour autant, tant qu’aucune vraie requête n’est effectuée, Nginx n’a rien à faire. L’option <a href="https://unix.stackexchange.com/questions/94104/real-world-use-of-tcp-defer-accept"><code>TCP_DEFER_ACCEPT</code></a> du kernel permet donc de ne réveiller le processus que lors de l’envoie effectif de données. Cette option se traduit dans nginx par <code>deferred</code>.</p>
  243. <p>Dans un autre registre, ce n’est pas tout à fait lié à TCP, mais j’en parle ici quand même, activer le HTTP/2 peut avoir un impact significatif sur les performances. Pour plus de détails sur le protocole, je vous laisse jeter un œil sur <a href="https://fr.wikipedia.org/wiki/Hypertext_Transfer_Protocol/2">l’article Wikipedia</a>.</p>
  244. <p>Les deux optimisations dont nous venons de parler se définissent dans le bloc <code>server</code> et au niveau de la directive <code>listen</code>.</p>
  245. <pre><code>server {
  246. # on active deferred
  247. # pour ipv6 sur le port 80 (http)
  248. listen [::]:80 default_server deferred;
  249. # pour ipv4 sur le port 80 (http)
  250. listen 80 default_server deferred;
  251. # pour ipv6 sur le port 443 (https)
  252. listen [::]:443 ssl http2 deferred;
  253. # pour ipv4 sur le port 443 (https)
  254. listen 443 ssl http2 deferred;
  255. return 444;
  256. }</code></pre><p>Quelques précisions supplémentaires sur ce morceau de config. Vous noterez que l’on active HTTP/2 seulement pour le TLS. Il est possible de l’activer en HTTP non sécurisé, mais les navigateurs ne le supportent pas.</p>
  257. <p>Vous remarquez aussi peut-être le <code>default_server</code>. Cela indique à Nginx d’utiliser ce VHOST si l’en-tête <code>Host</code> n’est pas passée avec le requête. Dans le cas d’un accès direct via l’ip par exemple. Et en dernier lieu, <code>return 444</code> signifie que dans ces cas là, la connexion sera réinitialisée puisque si plusieurs sites sont hébergés sur cette même ip:port, en l’absence de <code>Host</code>, il n’est pas possible de savoir lequel on doit servir.</p>
  258. <p>Il y a de nombreuses optimisations potentielles au niveau de la pile TCP/IP, non spécifiques à Nginx, nombre d’entre elles sont détaillées dans cette <a href="http://kaivanov.blogspot.com.es/2010/09/linux-tcp-tuning.html">très bonne ressource</a> dont j’encourage la lecture.</p>
  259. <h2 id="La-thread-pool">La thread pool</h2><p>Il arrive que certaines opérations bloquantes soient lentes (comme la lecture de fichiers sur le disque). Les requêtes de fichiers de taille importante qui ne tiennent pas en mémoire par exemple, bloqueront un thread de Nginx jusqu’à ce que le disque retourne le fichier en question. Admettons-le, c’est assez dommage. Heureusement, Nginx a une solution pour ça !</p>
  260. <p>Il s’agit de placer cette requête dans une “file d’attente” et de traiter d’autres requêtes en attendant que le disque ait fini son opération de lecture. Ainsi, on ne bloque pas plusieurs requêtes en attendant une I/O pour l’une d’entre elle, on place cette dernière en attente et on utilise le thread ainsi libéré pour servir d’autres requêtes.</p>
  261. <p>Un article sur <a href="https://www.nginx.com/blog/thread-pools-boost-performance-9x/">le blog de Nginx</a> détaille le fonctionnement de la thread pool et présente un benchmark où les performances en charge sont multipliées par 9 ! Il faut pour cela utiliser l’option <a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#aio"><code>aio</code></a> :</p>
  262. <pre><code>location / {
  263. root /var/www;
  264. aio threads;
  265. directio 5m;
  266. }</code></pre><p>Ici, vous remarquez aussi l’option <code>directio</code>. Cette dernière s’utilise toujours conjointement à <code>aio</code>. Lorsque vous activez aussi l’option <code>sendfile</code> vue plus haut, <code>aio</code> est utilisé pour les fichiers de taille égale ou supérieure à <code>directio</code>, tout le reste utilise <code>sendfile</code>.</p>
  267. <p><em>N’hésitez pas à me faire part de vos remarques et compléments d’infos en commentaires. Avez-vous été confronté à des problèmes ? Avez-vous pu augmenter les performances ? Faites-nous part de vos expériences !</em></p>