Éviter les 10 erreurs de configuration NGINX les plus courantes

NGINX | 22 février 2022

Lorsque nous aidons des utilisateurs de NGINX qui rencontrent des problèmes, nous constatons souvent les mêmes erreurs de configuration que celles déjà observées à maintes reprises dans les configurations d’autres utilisateurs, parfois même dans celles rédigées par d’autres ingénieurs NGINX ! Dans cet article, nous examinons 10 des erreurs les plus courantes, en expliquant ce qui ne va pas et comment y remédier.

  1. Nombre insuffisant de descripteurs de fichiers par processus worker
  2. La directive « error_log off »
  3. Ne pas activer les connexions persistantes vers les serveurs en amont
  4. Oublier le fonctionnement de l’héritage des directives
  5. La directive « proxy_buffering off »
  6. Utilisation incorrect de la directive « if »
  7. Vérifications d’intégrité excessives
  8. Accès non sécurisé aux métriques
  9. Utilisation de ip_hash lorsque tout le trafic provient du même bloc CIDR /24
  10. Ne pas tirer parti des groupes de serveurs en amont

Erreur n° 1 : nombre insuffisant de descripteurs de fichiers par worker

La directive worker_connections définit le nombre maximal de connexions simultanées qu’un processus worker NGINX peut maintenir ouvertes (la valeur par défaut est de 512). Tous les types de connexions (par exemple, les connexions avec des serveurs mandataires) sont pris en compte dans cette limite, et pas seulement les connexions clientes. Il est toutefois important de garder à l’esprit qu’il existe une autre limite au nombre de connexions simultanées par worker : la limite imposée par le système d’exploitation au nombre maximal de descripteurs de fichiers (FD) alloués à chaque processus. Dans les distributions UNIX modernes, la limite par défaut est de 1 024.

Pour tous les déploiements NGINX, à l’exception des plus petits, une limite de 512 connexions par processus worker est probablement trop faible. En effet, le fichier nginx.conf par défaut que nous distribuons avec les binaires NGINX Open Source et NGINX Plus porte cette limite à 1 024.

Une erreur de configuration courante consiste à ne pas augmenter la limite du nombre de descripteurs de fichiers à au moins deux fois la valeur de worker_connections. Pour y remédier, définissez cette valeur à l’aide de la directive worker_rlimit_nofile dans le contexte de configuration principal.

Voici pourquoi davantage de descripteurs de fichiers (FD) sont nécessaires : chaque connexion d’un processus de travail NGINX à un client ou à un serveur en amont consomme un FD. Lorsque NGINX fait office de serveur web, il utilise un FD pour la connexion au client et un FD par fichier servi, soit au minimum deux FD par client (mais la plupart des pages web sont constituées de nombreux fichiers). Lorsqu’il fait office de serveur proxy, NGINX utilise un FD pour la connexion au client et un autre pour la connexion au serveur en amont, ainsi qu’éventuellement un troisième FD pour le fichier servant à stocker temporairement la réponse du serveur. En tant que serveur de cache, NGINX se comporte comme un serveur web pour les réponses mises en cache et comme un serveur proxy si le cache est vide ou a expiré.

NGINX utilise également un descripteur de fichier par fichier journal, ainsi que quelques descripteurs de fichier pour communiquer avec le processus maître. Toutefois, ces nombres sont généralement faibles par rapport au nombre de descripteurs de fichier utilisés pour les connexions et les fichiers.

UNIX offre plusieurs moyens de définir le nombre de descripteurs de fichiers (FD) par processus :

  • La commande « ulimit » si vous démarrez NGINX depuis un shell
  • Les variables du script init ou du manifeste de service systemd si vous démarrez NGINX en tant que service
  • Le fichier « /etc/security/limits.conf »

Cependant, la méthode à utiliser dépend de la façon dont vous démarrez NGINX, tandis que « worker_rlimit_nofile » fonctionne quelle que soit la façon dont vous démarrez NGINX.

Il existe également une limite à l’échelle du système concernant le nombre de descripteurs de fichiers, que vous pouvez définir à l’aide de la commande sysctl fs.file-max du système d’exploitation. Elle est généralement suffisamment élevée, mais il convient de vérifier que le nombre maximal de descripteurs de fichiers que tous les processus de travail NGINX sont susceptibles d’utiliser (worker_rlimit_nofile * worker_processes) est nettement inférieur à fs.file‑max. Si NGINX utilise d’une manière ou d’une autre tous les descripteurs de fichiers disponibles (par exemple, lors d’une attaque par déni de service), il devient même impossible de se connecter à la machine pour résoudre le problème.

Erreur n° 2 : la directive « error_log off »

L’erreur courante consiste à penser que la directive error_log off désactive la journalisation. En réalité, contrairement à la directive access_log, error_log n’accepte pas le paramètre off. Si vous incluez la directive error_log off dans la configuration, NGINX crée un fichier journal d’erreurs nommé off dans le répertoire par défaut des fichiers de configuration de NGINX (généralement /etc/nginx).

Nous déconseillons de désactiver le journal des erreurs, car il constitue une source d’informations essentielle pour diagnostiquer les problèmes liés à NGINX. Toutefois, si l’espace de stockage est si limité que le volume de données journalisées risque d’épuiser l’espace disque disponible, il peut être judicieux de désactiver la journalisation des erreurs. Ajoutez cette directive dans le contexte de configuration principal :

error_log /dev/null emerg;

Notez que cette directive ne s’applique qu’une fois que NGINX a lu et validé la configuration. Ainsi, à chaque démarrage de NGINX ou rechargement de la configuration, les erreurs peuvent être consignées dans l’emplacement par défaut du journal d’erreurs (généralement /var/log/nginx/error.log) jusqu’à ce que la configuration soit validée. Pour modifier le répertoire des journaux, ajoutez le paramètre -e <error_log_location> à la commande nginx.

Erreur n° 3 : ne pas activer les connexions persistantes vers les serveurs en amont

Par défaut, NGINX ouvre une nouvelle connexion à un serveur en amont (backend) pour chaque nouvelle requête entrante. Cette méthode est sûre, mais inefficace, car NGINX et le serveur doivent échanger trois paquets pour établir une connexion, puis trois ou quatre pour y mettre fin.

Lorsque le trafic est élevé, l’ouverture d’une nouvelle connexion pour chaque requête peut épuiser les ressources système et rendre toute nouvelle connexion impossible. En voici la raison : pour chaque connexion, le quadruplet composé de l’adresse source, du port source, de l’adresse de destination et du port de destination doit être unique. Pour les connexions de NGINX à un serveur en amont, trois de ces éléments (le premier, le troisième et le quatrième) sont fixes, de sorte que seul le port source peut varier. Lorsqu’une connexion est fermée, le socket Linux reste dans l’état TIME‑WAIT pendant deux minutes, ce qui, lorsque le trafic est élevé, augmente le risque d’épuiser la plage de ports sources disponibles. Si cela se produit, NGINX ne peut plus ouvrir de nouvelles connexions aux serveurs en amont.

La solution consiste à activer les connexions persistantes entre NGINX et les serveurs en amont : au lieu d’être fermée une fois la requête terminée, la connexion reste ouverte afin d’être réutilisée pour d’autres requêtes. Cela réduit le risque d’épuisement des ports sources et améliore les performances.

Pour activer les connexions persistantes :

  • Incluez la directive keepalive dans chaque bloc upstream{} afin de définir le nombre de connexions keepalive inactives aux serveurs en amont conservées dans le cache de chaque processus worker. Notez que la directive keepalive ne limite pas le nombre total de connexions aux serveurs en amont qu’un processus worker NGINX peut ouvrir — il s’agit d’une idée reçue courante. Le paramètre de keepalive n’a donc pas besoin d’être aussi élevé que vous pourriez le penser. Nous recommandons de définir ce paramètre sur le double du nombre de serveurs répertoriés dans le bloc upstream{}. Cette valeur est suffisamment élevée pour permettre à NGINX de maintenir des connexions keepalive avec tous les serveurs, tout en restant assez faible pour que les serveurs en amont puissent également traiter de nouvelles connexions entrantes. Notez également que lorsque vous spécifiez un algorithme d’équilibrage de charge dans le bloc upstream{} — à l’aide de la directive hash, ip_hash, least_conn, least_time ou random — celle-ci doit apparaître avant la directive keepalive. Il s’agit de l’une des rares exceptions à la règle générale selon laquelle l’ordre des directives dans la configuration NGINX n’a pas d’importance.
  • Dans le bloc location{} qui transmet les requêtes à un groupe en amont, incluez les directives suivantes avec la directive proxy_pass :
proxy_http_version 1.1;
proxy_set_header   "Connection" "";
  • Par défaut, NGINX utilise HTTP/1.0 pour les connexions aux serveurs en amont et ajoute donc l’en-tête Connection: close aux requêtes qu’il transmet aux serveurs. Par conséquent, chaque connexion est fermée une fois la requête terminée, malgré la présence de la directive keepalive dans le bloc upstream{}. La directive proxy_http_version indique à NGINX d’utiliser plutôt HTTP/1.1, tandis que la directive proxy_set_header supprime la valeur close de l’en-tête Connection.

Erreur n° 4 : oublier le fonctionnement de l’héritage des directives

Les directives NGINX sont héritées vers le bas, autrement dit « de l’extérieur vers l’intérieur » : un contexte enfant – imbriqué dans un autre contexte (son contexte parent) – hérite des paramètres des directives définies au niveau parent. Par exemple, tous les blocs server{} et location{} du contexte http{} héritent de la valeur des directives définies au niveau http, et une directive définie dans un bloc server{} est héritée par tous les blocs location{} enfants qu’il contient. Toutefois, lorsque la même directive est définie à la fois dans un contexte parent et dans son contexte enfant, les valeurs ne sont pas cumulées : la valeur du contexte enfant remplace celle du contexte parent.

L’erreur consiste à oublier cette « règle de remplacement » pour les directives de type tableau, qui peuvent être incluses non seulement dans plusieurs contextes, mais aussi plusieurs fois au sein d’un même contexte. On peut notamment citer proxy_set_header et add_header – la présence du terme « add » dans le nom de la seconde rend la règle de remplacement particulièrement facile à oublier.

Nous pouvons illustrer le fonctionnement de l’héritage avec cet exemple pour add_header :

http {    
    add_header X-HTTP-LEVEL-HEADER 1;
    add_header X-ANOTHER-HTTP-LEVEL-HEADER 1;

    server {
        listen 8080;
        location / {
            return 200 "OK";
        } 
    }

    server {
        listen 8081;
        add_header X-SERVER-LEVEL-HEADER 1;

        location / {
            return 200 "OK";
        }

        location /test {
            add_header X-LOCATION-LEVEL-HEADER 1;
            return 200 "OK";
        }

        location /correct {
            add_header X-HTTP-LEVEL-HEADER 1;
            add_header X-ANOTHER-HTTP-LEVEL-HEADER 1;

            add_header X-SERVER-LEVEL-HEADER 1;
            add_header X-LOCATION-LEVEL-HEADER 1;
            return 200 "OK";
        } 
    }
}

Pour le serveur qui écoute sur le port 8080, il n’y a aucune directive « add_header » dans les blocs « server{} » ou « location{} ». L’héritage est donc simple, et nous voyons les deux en-têtes définis dans le contexte « http{} » :

% curl -i localhost:8080
HTTP/1.1 200 OK
Server: nginx/1.21.5
Date: Mon, 21 Feb 2022 10:12:15 GMT
Content-Type: text/plain
Content-Length: 2
Connection: keep-alive
X-HTTP-LEVEL-HEADER: 1 X-ANOTHER-HTTP-LEVEL-HEADER: 1
OK

Pour le serveur qui écoute sur le port 8081, une directive add_header est présente dans le bloc server{}, mais pas dans son bloc enfant location / . L’en-tête défini dans le bloc server{} remplace les deux en-têtes définis dans le contexte http{} :

% curl -i localhost:8081
HTTP/1.1 200 OK
Server: nginx/1.21.5
Date: Mon, 21 Feb 2022 10:12:20 GMT
Content-Type: text/plain
Content-Length: 2
Connection: keep-alive
X-SERVER-LEVEL-HEADER: 1
OK

Dans le bloc enfant location /test, une directive add_header est présente et remplace à la fois l’en-tête du bloc parent server{} et les deux en-têtes du contexte http{}.

% curl -i localhost:8081/test
HTTP/1.1 200 OK
Server: nginx/1.21.5
Date: Mon, 21 Feb 2022 10:12:25 GMT
Content-Type: text/plain
Content-Length: 2
Connection: keep-alive
X-LOCATION-LEVEL-HEADER: 1
OK

Si nous voulons qu’un bloc location{} conserve les en-têtes définis dans ses contextes parents en plus de ceux définis localement, nous devons redéfinir les en-têtes parents dans le bloc location{}. C’est ce que nous avons fait dans le bloc location /correct :

% curl -i localhost:8081/correct
HTTP/1.1 200 OK
Server: nginx/1.21.5
Date: Mon, 21 Feb 2022 10:12:30 GMT
Content-Type: text/plain
Content-Length: 2
Connection: keep-alive
X-HTTP-LEVEL-HEADER: 1 X-ANOTHER-HTTP-LEVEL-HEADER: 1 X-SERVER-LEVEL-HEADER: 1 X-LOCATION-LEVEL-HEADER: 1
OK

Erreur n° 5 : la directive « proxy_buffering off »

La mise en mémoire tampon du proxy est activée par défaut dans NGINX (la directive « proxy_buffering » est définie sur « on »). Elle signifie que NGINX stocke la réponse d’un serveur dans des tampons internes au fur et à mesure de sa réception et ne commence à envoyer les données au client qu’une fois la réponse entièrement mise en mémoire tampon. Cette mise en mémoire tampon permet d’optimiser les performances avec les clients lents : puisque NGINX conserve la réponse en mémoire tampon aussi longtemps que nécessaire pour que le client la récupère intégralement, le serveur mandataire peut renvoyer sa réponse aussi rapidement que possible, puis redevenir disponible pour traiter d’autres requêtes.

Lorsque la mise en mémoire tampon du proxy est désactivée, NGINX ne met en mémoire tampon que la première partie de la réponse d’un serveur avant de commencer à l’envoyer au client, dans un tampon dont la taille par défaut correspond à une page mémoire (4 Ko ou 8 Ko selon le système d’exploitation). Cet espace est généralement tout juste suffisant pour l’en-tête de la réponse. NGINX envoie ensuite la réponse au client de manière synchrone à mesure qu’il la reçoit, ce qui contraint le serveur à rester inactif en attendant que NGINX puisse accepter le segment de réponse suivant.

Nous sommes donc surpris de voir si souvent proxy_buffering off dans les configurations. L’objectif est peut-être de réduire la latence ressentie par les clients, mais l’effet est négligeable, tandis que les effets indésirables sont nombreux : lorsque la mise en mémoire tampon du proxy est désactivée, la limitation du débit et la mise en cache ne fonctionnent pas, même si elles sont configurées, les performances se dégradent, etc.

Il n’existe qu’un petit nombre de cas d’utilisation dans lesquels la désactivation de la mise en mémoire tampon du proxy peut se justifier (comme les requêtes HTTP longues). Nous vous déconseillons donc vivement de modifier le paramètre par défaut. Pour plus d’informations, consultez le NGINX Plus Admin Guide.

Erreur n° 6 : utilisation incorrecte de la directive « if »

La directive « if » est délicate à utiliser, en particulier dans les blocs location{}. Souvent, elle ne produit pas le résultat attendu et peut même provoquer des erreurs de segmentation. Elle est d’ailleurs si délicate qu’un article intitulé « If is Evil » lui est consacré dans le wiki de NGINX. Nous vous invitons à le consulter pour une analyse détaillée des problèmes et des moyens de les éviter.

En règle générale, les seules directives que vous pouvez toujours utiliser en toute sécurité dans un bloc if{} sont return et rewrite. L’exemple suivant utilise if pour détecter les requêtes qui incluent l’en-tête X‑Test (mais il peut s’agir de n’importe quelle condition que vous souhaitez tester). NGINX renvoie l’erreur 430 (Request Header Fields Too Large), l’intercepte à l’emplacement nommé @error_430 et transmet la requête au groupe en amont nommé b.

location / {    
    error_page 430 = @error_430;
    if ($http_x_test) {
        return 430; 
    }

    proxy_pass http://a;
}

location @error_430 {
    proxy_pass b;
}

Pour cette utilisation et bien d’autres de la directive « if », il est souvent possible de s’en passer complètement. Dans l’exemple suivant, lorsque la requête contient l’en-tête X‑Test, le bloc map{} définit la variable $upstream_name sur b, et la requête est transmise par proxy au groupe de serveurs en amont portant ce nom.

map $http_x_test $upstream_name {    
    default "b";
    ""      "a";
}

# ...

location / {
    proxy_pass http://$upstream_name;
}

Erreur n° 7 : contrôles d’intégrité excessifs

Il est assez courant de configurer plusieurs serveurs virtuels pour transmettre les requêtes au même groupe de serveurs en amont (autrement dit, d’inclure la même directive proxy_pass dans plusieurs blocs server{}). Dans ce cas, l’erreur consiste à inclure une directive health_check dans chaque bloc server{}. Cela ne fait qu’augmenter la charge sur les serveurs en amont sans fournir d’informations supplémentaires.

Au risque d'énoncer une évidence, la solution consiste à ne définir qu'une seule vérification d'état par bloc upstream{}. Ici, nous définissons la vérification d'état du groupe en amont nommé b dans un emplacement nommé spécialement prévu à cet effet, avec les délais d'expiration et les paramètres d'en-tête appropriés.

location / {    
    proxy_set_header Host $host;
    proxy_set_header "Connection" "";
    proxy_http_version 1.1;
    proxy_pass http://b;
}

location @health_check {
    health_check;
    proxy_connect_timeout 2s;
    proxy_read_timeout 3s;
    proxy_set_header Host example.com;
    proxy_pass http://b;
}

Dans les configurations complexes, il est possible de simplifier davantage la gestion en regroupant tous les emplacements de contrôle d’intégrité dans un seul serveur virtuel, avec l’API et le tableau de bord NGINX Plus, comme dans cet exemple.

server {	
        listen 8080;
 
	location / {
	    # …
 	}
 
	location @health_check_b {
	    health_check;
	    proxy_connect_timeout 2s;
	    proxy_read_timeout 3s;
	    proxy_set_header Host example.com;
	    proxy_pass http://b;
	}
 
	location @health_check_c {
	    health_check;
	    proxy_connect_timeout 2s;
	    proxy_read_timeout 3s;
	    proxy_set_header Host api.example.com;
	    proxy_pass http://c;
	}
 
	location /api {
	    api write=on;
	    # directives limiting access to the API (see 'Mistake 8' below)
	}
 
	location = /dashboard.html {
	    root   /usr/share/nginx/html;
	}
}

Pour plus d’informations sur les contrôles d’intégrité des serveurs HTTP, TCP, UDP et gRPC, consultez le NGINX Plus Admin Guide.

Erreur n° 8 : accès non sécurisé aux métriques

Des métriques de base sur le fonctionnement de NGINX sont disponibles grâce au module « Stub Status ». Avec NGINX Plus, vous pouvez également recueillir un ensemble de métriques beaucoup plus complet à l’aide de l’API NGINX Plus. Pour activer la collecte des métriques, ajoutez respectivement la directive stub_status ou api dans un bloc server{} ou location{}. Ce bloc devient alors l’URL à laquelle vous accédez pour consulter les métriques. (Pour l’API NGINX Plus, vous devez également configurer des zones de mémoire partagée pour les entités NGINX — serveurs virtuels, groupes en amont, caches, etc. — dont vous souhaitez collecter les métriques ; consultez les instructions du Guide d’administration de NGINX Plus.)

Certaines métriques contiennent des informations sensibles qui peuvent être utilisées pour attaquer votre site Web ou les applications relayées par NGINX. Or, nous constatons parfois dans les configurations des utilisateurs que l’accès à l’URL correspondante n’est pas restreint. Nous allons examiner ici quelques méthodes permettant de sécuriser les métriques. Nous utiliserons stub_status dans les premiers exemples.

Avec la configuration suivante, n’importe qui sur Internet peut accéder aux métriques à l’adresse http://example.com/basic_status.

server {    
    listen 80;
    server_name example.com;

    location = /basic_status {
        stub_status;
    }
}

Protéger les métriques avec l’authentification HTTP Basic

Pour protéger les métriques par mot de passe avec l’authentification HTTP Basic, incluez les directives auth_basic et auth_basic_user_file. Le fichier (ici, .htpasswd) répertorie les noms d’utilisateur et les mots de passe des clients autorisés à se connecter pour consulter les métriques :

server {    
    listen 80;
    server_name example.com;

    location = /basic_status {
        auth_basic “closed site”;
        auth_basic_user_file conf.d/.htpasswd;
        stub_status;
    }
}

Protéger les métriques avec les directives « allow » et « deny »

Si vous ne souhaitez pas obliger les utilisateurs autorisés à se connecter et que vous connaissez les adresses IP depuis lesquelles ils accéderont aux métriques, vous pouvez également utiliser la directive allow. Vous pouvez spécifier des adresses IPv4 et IPv6 individuelles ainsi que des plages CIDR. La directive deny all empêche l’accès depuis toute autre adresse.

server {    
    listen 80;
    server_name example.com;

    location = /basic_status {
        allow 192.168.1.0/24;
        allow 10.1.1.0/16;
        allow 2001:0db8::/32;
        allow 96.1.2.23/32;
        deny  all;
        stub_status;
    }
}

Combiner les deux méthodes

Et si nous voulions combiner les deux méthodes ? Nous pouvons autoriser les clients provenant d’adresses spécifiques à accéder aux métriques sans mot de passe, tout en exigeant une authentification pour ceux provenant d’autres adresses. Pour cela, nous utilisons la directive satisfy any. Elle indique à NGINX d’autoriser l’accès aux clients qui s’authentifient avec des identifiants HTTP Basic ou qui utilisent une adresse IP préapprouvée. Pour renforcer la sécurité, vous pouvez définir satisfy sur all afin d’exiger que même les personnes provenant d’adresses spécifiques s’authentifient.

server {    
    listen 80;
    server_name monitor.example.com;

    location = /basic_status {
        satisfy any;

        auth_basic “closed site”;
        auth_basic_user_file conf.d/.htpasswd;
        allow 192.168.1.0/24;
        allow 10.1.1.0/16;
        allow 2001:0db8::/32;
        allow 96.1.2.23/32;
        deny  all;
        stub_status;
    }
}

Avec NGINX Plus, vous utilisez les mêmes techniques pour limiter l’accès au point de terminaison de l’API NGINX Plus (http://monitor.example.com:8080/api/ dans l’exemple suivant), ainsi qu’au tableau de bord de surveillance de l’activité en temps réel à l’adresse http://monitor.example.com/dashboard.html.

Cette configuration autorise l’accès sans mot de passe uniquement aux clients provenant du réseau 96.1.2.23/32 ou de l’hôte local. Comme les directives sont définies au niveau server{}, les mêmes restrictions s’appliquent à l’API et au tableau de bord. À noter que le paramètre write=on de la directive api permet également à ces clients d’utiliser l’API pour modifier la configuration.

Pour plus d’informations sur la configuration de l’API et du tableau de bord, consultez le Guide d’administration de NGINX Plus.

server {    
    listen 8080;
    server_name monitor.example.com;
 
    satisfy any;
    auth_basic “closed site”;
    auth_basic_user_file conf.d/.htpasswd;
    allow 127.0.0.1/32;
    allow 96.1.2.23/32;
    deny  all;

    location = /api/ {    
        api write=on;
    }

    location = /dashboard.html {
        root /usr/share/nginx/html;
    }
}

Erreur n° 9 : utiliser ip_hash lorsque tout le trafic provient du même bloc CIDR /24

L’algorithme ip_hash répartit la charge du trafic entre les serveurs d’un bloc upstream{} en fonction d’un hachage de l’adresse IP du client. La clé de hachage correspond aux trois premiers octets d’une adresse IPv4 ou à l’intégralité d’une adresse IPv6. Cette méthode assure la persistance de session, ce qui signifie que les requêtes d’un client sont toujours transmises au même serveur, sauf lorsque celui-ci est indisponible.

Supposons que nous ayons déployé NGINX en tant que proxy inverse dans un réseau privé virtuel configuré pour assurer une haute disponibilité. Nous avons placé différents pare-feu, routeurs, répartiteurs de charge de couche 4 et passerelles devant NGINX afin d’accepter le trafic provenant de différentes sources (réseau interne, réseaux partenaires, Internet, etc.) et de le transmettre à NGINX, qui le relaie ensuite vers les serveurs en amont. Voici la configuration NGINX initiale :

http {
    upstream {
        ip_hash;
        server 10.10.20.105:8080;
        server 10.10.20.106:8080;
        server 10.10.20.108:8080;
    }
 
    server {# …}
}

Mais il s’avère qu’il y a un problème : tous les appareils d’« interception » se trouvent sur le même réseau 10.10.0.0/24. Pour NGINX, tout le trafic semble donc provenir d’adresses appartenant à cette plage CIDR. Rappelons que l’algorithme ip_hash calcule le hachage à partir des trois premiers octets d’une adresse IPv4. Dans notre déploiement, ces trois premiers octets sont identiques — 10.10.0 — pour chaque client. Le hachage est donc le même pour tous, ce qui ne permet pas de répartir le trafic entre différents serveurs.

La solution consiste à utiliser à la place l’algorithme hash avec la variable $binary_remote_addr comme clé de hachage. Cette variable récupère l’adresse complète du client et la convertit en une représentation binaire de 4 octets pour une adresse IPv4 et de 16 octets pour une adresse IPv6. Le hachage est désormais différent pour chaque dispositif d’interception, et l’équilibrage de charge fonctionne comme prévu.

Nous incluons également le paramètre « consistent » afin d’utiliser la méthode de hachage ketama plutôt que celle par défaut. Cela réduit considérablement le nombre de clés réaffectées à un autre serveur en amont lorsque l’ensemble de serveurs change, ce qui permet d’obtenir un meilleur taux de succès du cache pour les serveurs de cache.

http {    
        upstream {
        hash $binary_remote_addr consistent;
        server 10.10.20.105:8080;
        server 10.10.20.106:8080;
        server 10.10.20.108:8080;
    }

    server {# …}
}

Erreur n° 10 : ne pas tirer parti des groupes upstream

Supposons que vous utilisiez NGINX dans l’un des cas d’usage les plus simples, comme proxy inverse pour une seule application backend basée sur Node.js et écoutant sur le port 3000. Une configuration courante pourrait ressembler à ceci :

http {
    server {
        listen 80;
        server_name example.com;

        location / {
            proxy_set_header Host $host;
            proxy_pass http://localhost:3000/;
        }
    }
}

Simple, n’est-ce pas ? La directive « proxy_pass » indique à NGINX où envoyer les requêtes des clients. NGINX doit simplement résoudre le nom d’hôte en une adresse IPv4 ou IPv6. Une fois la connexion établie, NGINX transmet les requêtes à ce serveur.

L’erreur consiste ici à supposer que, puisqu’il n’y a qu’un seul serveur — et donc aucune raison de configurer la répartition de charge —, il est inutile de créer un bloc upstream{}. En réalité, un bloc upstream{} donne accès à plusieurs fonctionnalités qui améliorent les performances, comme l’illustre cette configuration :

http {
    upstream node_backend {
        zone upstreams 64K;
        server 127.0.0.1:3000 max_fails=1 fail_timeout=2s;
        keepalive 2;
    }

    server {
        listen 80;
        server_name example.com;

        location / {
            proxy_set_header Host $host;
            proxy_pass http://node_backend/;
            proxy_next_upstream error timeout http_500;

        }
    }
}

La directive « zone » établit une zone de mémoire partagée dans laquelle tous les processus de travail NGINX de l’hôte peuvent accéder aux informations de configuration et d’état des serveurs en amont. Plusieurs groupes de serveurs en amont peuvent partager cette zone. Avec NGINX Plus, la zone vous permet également d’utiliser l’API NGINX Plus pour modifier les serveurs d’un groupe en amont ainsi que les paramètres de chaque serveur sans redémarrer NGINX. Pour plus de détails, consultez le Guide d’administration de NGINX Plus.

La directive « server » comporte plusieurs paramètres permettant d’ajuster le comportement du serveur. Dans cet exemple, nous avons modifié les conditions utilisées par NGINX pour déterminer qu’un serveur est défaillant et ne peut donc plus accepter de requêtes. Ici, un serveur est considéré comme défaillant dès qu’une tentative de communication échoue au cours de chaque période de 2 secondes (au lieu de la valeur par défaut d’un échec sur une période de 10 secondes).

Nous combinons ce paramètre avec la directive proxy_next_upstream afin de définir ce que NGINX considère comme une tentative de communication ayant échoué, auquel cas il transmet les requêtes au serveur suivant du groupe en amont. Aux conditions d’erreur et d’expiration du délai par défaut, nous ajoutons http_500 afin que NGINX considère qu’un code HTTP 500 (Erreur interne du serveur) renvoyé par un serveur en amont correspond à une tentative ayant échoué.

La directive keepalive définit le nombre de connexions keepalive inactives aux serveurs en amont qui sont conservées dans le cache de chaque processus worker. Nous en avons déjà présenté les avantages dans la section « Erreur nº 3 : ne pas activer les connexions keepalive aux serveurs en amont ».

Avec NGINX Plus, vous pouvez configurer des fonctionnalités supplémentaires avec les groupes de serveurs en amont :

  • Nous avons indiqué précédemment que NGINX Open Source ne résout les noms d’hôte des serveurs en adresses IP qu’une seule fois, au démarrage. Le paramètre resolve de la directive server permet à NGINX Plus de surveiller les changements d’adresses IP correspondant au nom de domaine d’un serveur en amont et de modifier automatiquement la configuration en amont sans qu’un redémarrage soit nécessaire. Le paramètre service permet également à NGINX Plus d’utiliser les enregistrements DNS SRV, qui contiennent des informations sur les numéros de port, les pondérations et les priorités. Cette fonctionnalité est essentielle dans les environnements de microservices, où les numéros de port des services sont souvent attribués dynamiquement. Pour en savoir plus sur la résolution des adresses de serveur, consultez l’article Utilisation du DNS pour la découverte de services avec NGINX et NGINX Plus sur notre blog.
  • Le paramètre slow_start de la directive server permet à NGINX Plus d’augmenter progressivement le volume de requêtes qu’il envoie à un serveur récemment considéré comme opérationnel et disponible pour accepter des requêtes. Cela évite un afflux soudain de requêtes susceptible de surcharger le serveur et de provoquer une nouvelle défaillance.
  • La directive queue permet à NGINX Plus de placer les requêtes dans une file d’attente lorsqu’il n’est pas possible de sélectionner un serveur en amont pour les traiter, au lieu de renvoyer immédiatement une erreur au client.

Ressources

Pour essayer NGINX Plus, commencez votre essai gratuit de 30 jours dès aujourd’hui ou contactez-nous pour discuter de vos cas d’utilisation.


Partager
Mots-clés : F5 NGINX, 2021, Tech

À propos de l’auteur

Timo Stark
Timo StarkIngénieur en gestion de produits

Autres articles de Timo Stark

Articles de blog associés

Tendances actuelles des technologies cloud natives, de l’ingénierie de plateforme et de l’IA
NGINX | 13/10/2025

Tendances actuelles des technologies cloud natives, de l’ingénierie de plateforme et de l’IA

Découvrez les principaux enseignements tirés des réponses à l’enquête annuelle F5 NGINX sur les dernières tendances en matière de technologies cloud natives, de sécurité, d’ingénierie de plateforme et d’IA.

Quels contrôleurs Ingress NGINX sont affectés par les vulnérabilités CVE-2022-4886, CVE-2023-5043 et CVE-2023-5044 ?
NGINX | 03/11/2023

Quels contrôleurs Ingress NGINX sont affectés par les vulnérabilités CVE-2022-4886, CVE-2023-5043 et CVE-2023-5044 ?

Lorsque de nouvelles CVE sont signalées, il est facile de ne plus savoir si elles concernent un outil de contrôleur Ingress NGINX particulier, car il existe plusieurs projets. Cet article présente les CVE et explique comment distinguer les différents projets de contrôleurs Ingress afin de dissiper toute confusion.

Votre API est-elle en temps réel ? Testez sa latence avec l’outil rtapi de NGINX
NGINX | 11/03/2020

Votre API est-elle en temps réel ? Testez sa latence avec l’outil rtapi de NGINX

Votre API répond-elle aux requêtes en moins de 30 ms ? Si ce n’est pas le cas, il ne s’agit pas d’une API en temps réel offrant la faible latence exigée par la grande majorité des organisations. Mesurez la latence de votre API avec l’outil rtapi de NGINX et découvrez comment NGINX peut l’aider à offrir une réactivité en temps réel.

Un testeur d’expressions régulières pour NGINX et NGINX Plus
NGINX | 11/06/2019

Un testeur d’expressions régulières pour NGINX et NGINX Plus

Le testeur d’expressions régulières (regex) pour NGINX et NGINX Plus vous évite de tâtonner en vous indiquant si une expression régulière utilisée dans un bloc location ou map correspond aux valeurs comme prévu. Il génère la configuration appropriée et affiche les valeurs des groupes de capture.

Caches partagés avec les clusters de cache NGINX Plus, partie 1
NGINX | 24/01/2017

Caches partagés avec les clusters de cache NGINX Plus, partie 1

Optimisez la capacité de votre cache web en le répartissant entre plusieurs serveurs de cache web NGINX ou NGINX Plus. Découvrez comment procéder dans cet article.

Introduction aux microservices
NGINX | 19/05/2015

Introduction aux microservices

Les microservices suscitent actuellement beaucoup d’intérêt. Cet article de blog est le premier d’une série de sept consacrée à la conception, au développement et au déploiement de microservices.