HTTP : guide pratique pour administrer un serveur web
Comprenez HTTP côté serveur : méthodes, codes 200 et 404, en-têtes utiles, journaux nginx, et la place de TLS et HTTP/3 dans vos diagnostics.
Qu’est-ce que HTTP ?
HTTP (Hypertext Transfer Protocol) est l’ensemble des règles qu’un client et un serveur web utilisent pour demander une ressource et la renvoyer. Le client envoie une requête : une méthode telle que GET, un chemin tel que /pricing, une version du protocole, une liste d’en-têtes et parfois un corps. Le serveur répond avec un code d’état tel que 200, suivi de ses propres en-têtes et généralement d’un corps. Chaque affichage de page et chaque appel d’API (application programming interface) sur votre serveur correspond à cet échange, répété.
HTTP ne conserve aucun état par lui-même. Le serveur ne se souvient pas de ce que vous avez demandé une seconde plus tôt. Tout ce qui se comporte comme une mémoire, par exemple une session de connexion, est donc transporté dans un en-tête à chaque requête. Cette propriété explique une grande partie de ce qui suit : la mise en cache dépend entièrement des en-têtes, et un load balancer peut envoyer votre requête suivante vers un autre backend sans rien interrompre.
Tout ce qui suit montre à quoi ressemble ce modèle côté serveur, dans votre access log et dans votre configuration nginx.
Une requête et une réponse brutes, annotées
Voici une requête HTTP/1.1 complète. Une ligne vide termine les en-têtes. Tout ce qui suit cette ligne constitue le corps. Un GET n’a normalement pas de corps.
GET /pricing HTTP/1.1
Host: example.com
User-Agent: curl/8.5.0
Accept: */*
Accept-Encoding: gzipGETest la méthode. Elle indique l’opération demandée.GETlit,POSTenvoie des données,PUTremplace,DELETEsupprime etHEADdemande les en-têtes d’unGETsans son corps./pricingest le chemin. Le nom d’hôte ne fait pas partie de la ligne de requête, d’où la présence de l’en-tête suivant.HTTP/1.1est la version du protocole utilisée par le client.Host: example.comindique le site demandé par le client. HTTP/1.1 l’exige. nginx répond donc par400 Bad Requestà une requête qui en est dépourvue.- Les autres champs indiquent des préférences.
Accept-Encoding: gzipsignifie que le client peut décompresser le contenu. Le serveur peut donc compresser le corps.
La réponse a la même structure, avec une ligne d’état au début.
HTTP/1.1 200 OK
Date: Thu, 06 Aug 2026 09:12:44 GMT
Server: nginx
Content-Type: text/html; charset=utf-8
Content-Length: 5310
Cache-Control: public, max-age=300
<!doctype html>...200 OKest le code d’état accompagné de sa phrase explicative. Le code est l’information importante. La phrase est décorative et les clients l’ignorent.Content-Typeindique au client comment traiter les octets qui suivent.Content-Lengthest la taille du corps en octets. Le client sait ainsi où le corps se termine. Lorsque sa taille n’est pas connue à l’avance, le serveur envoieTransfer-Encoding: chunkedet indique la fin avec un chunk de longueur nulle.Cache-Controlindique au navigateur et aux caches intermédiaires pendant combien de temps ils peuvent considérer cette réponse comme fraîche. Lorsqu’elle dépassemax-agesecondes, elle est périmée. Cela ne signifie pas qu’elle est supprimée : un cache peut conserver une copie périmée et la revalider auprès de votre serveur au lieu de télécharger à nouveau tout le corps.- La ligne vide qui suit les en-têtes les sépare du corps, dans les deux sens.
Les noms d’en-têtes ne tiennent pas compte de la casse. Chaque ligne se termine par un retour chariot suivi d’un saut de ligne, et non par un simple saut de ligne. Vous ne les saisirez pas à la main, mais vous les verrez dans une capture de paquets.
Pour observer un échange réel, exécutez cette commande vers un site dont vous êtes propriétaire :
curl -sS -o /dev/null -D - https://example.com/-D - écrit les en-têtes de la réponse dans votre terminal et -o /dev/null ignore le corps. Préférez cette option à curl -I, car -I envoie une requête HEAD. Un serveur d’application qui traite HEAD différemment de GET, comme c’est souvent le cas, vous affichera alors des en-têtes qu’aucun navigateur ne reçoit. curl -v affiche les deux côtés, avec les lignes de requête marquées > et les lignes de réponse marquées <.
À quoi ressemble la ligne de requête dans votre journal d’accès nginx
nginx fournit un format de journalisation combined. Voici sa définition :
log_format combined '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';Voici une ligne produite par ce format :
203.0.113.45 - - [06/Aug/2026:09:12:44 +0000] "GET /pricing HTTP/1.1" 200 5310 "https://example.com/" "Mozilla/5.0 (X11; Linux x86_64) Chrome/127.0.0.0 Safari/537.36"203.0.113.45est$remote_addr, l’adresse qui a ouvert la connexion TCP (protocole de contrôle de transmission). Derrière un proxy, il s’agit de l’adresse du proxy, pas de celle du visiteur, sauf si vous avez configuré le module realip décrit plus bas pour la remplacer.- Le premier
-est un emplacement réservé fixe. Le second est$remote_user. Il n’est renseigné que si l’authentification HTTP basic est utilisée. "GET /pricing HTTP/1.1"est$request, c’est-à-dire la ligne de requête complète d’origine. Avec l’échappement par défaut des journaux nginx, un guillemet double, une barre oblique inverse et tout octet inférieur à 32 ou supérieur à 126 sont écrits sous la forme\xXX. La ligne reste donc fidèle, mais pas toujours identique octet par octet.200est le code d’état renvoyé par votre serveur, et non celui perçu par le visiteur.5310est$body_bytes_sent, le corps seul de la réponse. Les en-têtes de réponse ne sont pas comptabilisés. Cette valeur est donc toujours inférieure au nombre d’octets réellement envoyés.- Les deux derniers champs entre guillemets sont
RefereretUser-Agent. Ils proviennent tous les deux du client et peuvent donc contenir n’importe quelle valeur.
Les données incohérentes sont également journalisées, sous forme échappée. Un client qui parle TLS (sécurité de la couche transport) à votre port 80 en clair laisse une ligne 400 dont le champ de requête commence par des octets échappés tels que "\x16\x03\x01\x02\x00\x01". \x16 est le type d’enregistrement de la négociation TLS. Ces octets sont donc le début d’un ClientHello, et non une ligne de requête. Votre serveur se comporte correctement. Un élément tente d’utiliser HTTPS sur un port HTTP.
Ajoutez également $server_protocol à votre format de journalisation. Il affiche HTTP/1.1, HTTP/2.0 ou HTTP/3.0. C’est le moyen le plus rapide de vérifier qu’un changement de protocole a bien été pris en compte.
Signification des codes d’état courants renvoyés par votre propre site
Le premier chiffre indique la classe. C’est la première information à examiner.
2xx signifie que la requête a abouti. 200 OK correspond à une lecture normale. 201 Created est renvoyé après un POST qui a créé une ressource. 204 No Content indique que la requête a réussi, mais qu’il n’y a rien à renvoyer. C’est la réponse habituelle à un DELETE.
3xx signifie qu’il faut consulter une autre adresse. 301 indique une redirection permanente. Les navigateurs la mettent fortement en cache, parfois jusqu’à ce que l’utilisateur efface son profil. Un 301 qui pointe vers le mauvais nom d’hôte est donc difficile à annuler. Utilisez 302 pendant les tests d’une redirection. 304 Not Modified indique une réussite, pas une erreur : le client a envoyé une requête conditionnelle, soit If-None-Match avec un ETag (entity tag) que vous reconnaissez encore, soit If-Modified-Since avec une date depuis laquelle la ressource n’a pas changé. Vous avez donc répondu avec les en-têtes, sans corps de réponse. Un journal rempli de 304s indique que la mise en cache fonctionne.
4xx signifie que la requête était incorrecte. 400 Bad Request indique une entrée malformée. 401 Unauthorized signifie réellement que le client n’est pas authentifié et doit contenir un en-tête WWW-Authenticate indiquant le schéma utilisé. 403 Forbidden signifie que la requête a été comprise, mais refusée malgré tout. 404 Not Found indique un chemin qui n’existe pas, ou dont le serveur préfère ne pas admettre l’existence. Certaines applications renvoient 404 au lieu de 403 pour dissimuler une ressource aux clients qui n’y ont pas accès. 405 Method Not Allowed indique que le chemin est correct, mais que la méthode ne l’est pas. C’est ce que renvoie un POST vers un emplacement de fichiers statiques. 413 indique que le corps de la requête dépasse client_max_body_size, la valeur par défaut de nginx étant de 1 mégaoctet. Le journal d’erreurs le confirme avec client intended to send too large body.
Un 403 sur un fichier statique concerne presque toujours le système de fichiers, et non une règle HTTP. Consultez /var/log/nginx/error.log avant de modifier la configuration. open() "/srv/site/index.html" failed (13: Permission denied) signifie que l’utilisateur du worker nginx ne peut pas lire le fichier. La cause la plus fréquente est l’absence du droit d’exécution pour les autres utilisateurs sur un répertoire parent. directory index of "/srv/site/" is forbidden signifie que le chemin correspond à un répertoire sans fichier index, alors que autoindex est désactivé.
5xx signifie que le problème vient de votre côté. 500 est l’erreur serveur générique. Sur votre propre site, elle indique généralement une erreur non gérée dans votre application. 502 Bad Gateway signifie que nginx n’a pas pu obtenir de réponse exploitable de l’upstream. Le journal d’erreurs en précise la cause : connect() failed (111: Connection refused) while connecting to upstream signifie que rien n’écoute sur l’adresse indiquée dans proxy_pass. 504 Gateway Timeout signifie que nginx a cessé d’attendre l’upstream. Le cas le plus fréquent est un upstream qui a accepté la connexion, puis n’a rien envoyé pendant proxy_read_timeout, soit 60 secondes par défaut. Le journal l’enregistre alors comme upstream timed out (110: Connection timed out) while reading response header from upstream. Une tentative de connexion qui ne se termine pas dans proxy_connect_timeout, également fixé à 60 secondes par défaut, se termine aussi par 504, et la ligne du journal se termine alors par while connecting to upstream. 503 Service Unavailable signifie que le serveur ne peut temporairement pas traiter la requête, généralement parce qu’il est surchargé ou arrêté pour une maintenance planifiée. Notez que le limiteur de débit intégré à nginx renvoie 503, car limit_req_status vaut 503 par défaut. Si vous recherchez 429 Too Many Requests dans le journal, mais trouvez 503 à la place, c’est la raison. Définissez limit_req_status 429; pour obtenir le code correct.
Les en-têtes importants lors de l’exécution du serveur
Host sélectionne le site. Une seule adresse IP peut servir des centaines de noms d’hôte, et nginx compare Host à server_name pour déterminer quel bloc server répond. Si aucun nom ne correspond, nginx utilise le serveur par défaut. Il s’agit du premier bloc à l’écoute sur cette adresse et ce port, sauf si un autre bloc est marqué default_server. Si un nouvel hôte virtuel renvoie le mauvais site, c’est presque toujours pour cette raison : le nom ne correspondait pas et la requête est passée au serveur par défaut. Testez-le sans modifier le DNS :
curl -sS -o /dev/null -D - -H 'Host: app.example.com' http://127.0.0.1/User-Agent est une description du client, fournie en texte libre. Utilisez-la comme indication lors de la lecture des journaux. Ne l’utilisez jamais comme mécanisme de contrôle : un client qui veut mentir à ce sujet le fera. Bloquer un scraper selon User-Agent filtre donc uniquement les clients qui restent courtois.
Content-Type indique comment interpréter les octets : application/json pour une requête d’API, text/html; charset=utf-8 pour une page. nginx associe les extensions de fichiers aux types avec /etc/nginx/mime.types, et le fichier nginx.conf fourni par le paquet définit default_type application/octet-stream;. Un fichier dont l’extension n’est pas connue de nginx est donc proposé au téléchargement au lieu d’être affiché. Le symptôme visible est une page qui se charge sans mise en forme, tandis que la console du navigateur affiche Refused to apply style from ... because its MIME type ('text/plain') is not a supported stylesheet MIME type. MIME signifie ici multipurpose internet mail extensions. Il s’agit du système de nommage à l’origine de ces chaînes de type.
Cache-Control permet de contrôler chaque cache situé entre votre serveur et le lecteur. public, max-age=31536000, immutable convient aux ressources dont le nom contient un hash du contenu, car le nom change lorsque le contenu change. Utilisez no-store pour tout contenu propre à un utilisateur : un cache partagé qui conserve une page après connexion la transmettrait à la personne suivante qui demande la même URL. private est le réglage intermédiaire : le navigateur peut conserver la réponse, mais pas un cache partagé.
X-Forwarded-For est nécessaire parce qu’un proxy masque l’adresse du visiteur. Après le passage d’une requête par un reverse proxy, $remote_addr contient l’adresse du proxy. Vos journaux, votre géolocalisation et votre limitation de débit voient donc un seul client jusqu’à ce que vous restauriez l’adresse d’origine. Le proxy doit transmettre cette adresse :
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;Il faut ensuite indiquer au serveur destinataire qu’il doit faire confiance à cette adresse, et préciser exactement à qui faire confiance :
set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;Ces deux directives appartiennent au module realip de nginx. Il remplace $remote_addr par l’adresse transmise et conserve l’adresse de la connexion dans $realip_remote_addr. Si vous compilez nginx vous-même, ajoutez --with-http_realip_module, car ce module n’est pas compilé par défaut.
Indiquez uniquement les plages que vous contrôlez. X-Forwarded-For est du texte brut qu’un client peut envoyer librement. set_real_ip_from 0.0.0.0/0; permet donc à un visiteur de choisir l’adresse que vous journalisez et celle que votre limiteur de débit comptabilise.
X-Forwarded-Proto évite un problème précis et très fréquent. Votre proxy termine TLS, puis transmet la requête à l’application en HTTP simple. L’application reçoit une requête non chiffrée, estime que le visiteur doit utiliser HTTPS et répond 301 https://example.com/. Le navigateur suit cette redirection, le proxy termine de nouveau TLS et transmet encore la requête en HTTP simple. La boucle se répète jusqu’à ce que le navigateur abandonne avec ERR_TOO_MANY_REDIRECTS. Envoyer X-Forwarded-Proto: https indique à l’application que le visiteur utilise déjà HTTPS. Elle cesse alors de rediriger.
HTTP/1.1 vs HTTP/2 vs HTTP/3 : ce qui change pour vous
HTTP/1.1 est un protocole textuel et traite en pratique une requête à la fois par connexion. Connection: keep-alive permet à la requête suivante de réutiliser la même connexion TCP, ce qui évite le coût de l’établissement d’une nouvelle connexion, mais les réponses reviennent toujours dans l’ordre des requêtes. Le protocole autorise aussi le pipelining : le client peut envoyer plusieurs requêtes sans attendre les réponses. La règle d’ordre reste toutefois applicable et aucun navigateur moderne n’active le pipelining par défaut. Une réponse lente bloque toutes celles qui sont en attente derrière elle. C’est le head-of-line blocking. Les navigateurs contournent ce problème en ouvrant simultanément plusieurs connexions vers le même hostname.
HTTP/2 conserve les mêmes méthodes et les mêmes codes d’état, mais utilise un framing binaire. De nombreuses requêtes partagent une seule connexion sous la forme de streams indépendants. Les portions répétées des en-têtes sont compressées, ce qui est important, car une requête moderne en contient beaucoup. La connexion utilise toujours TCP. La perte d’un paquet bloque donc tous les streams de cette connexion jusqu’à l’arrivée de sa retransmission. Le head-of-line blocking n’a pas disparu. Il est descendu de HTTP vers la couche de transport. Le server push faisait partie de HTTP/2, mais a disparu en pratique, car Chrome a supprimé sa prise en charge en 2022.
HTTP/3 conserve à nouveau les mêmes sémantiques et remplace TCP par QUIC, un transport basé sur UDP (user datagram protocol). Les streams QUIC sont indépendants jusqu’à la couche de transport. La perte d’un paquet ne bloque donc que le stream auquel il appartenait. TLS 1.3 est intégré au handshake QUIC au lieu d’être superposé, ce qui réduit le nombre d’allers-retours nécessaires à l’établissement d’une nouvelle connexion. Deux conséquences pratiques en découlent : le port UDP utilisé par votre serveur pour HTTP/3 doit être ouvert sur chaque firewall traversé, et tout réseau qui limite ou bloque UDP fera revenir les clients vers HTTP/2. Ce port est généralement 443, mais le protocole permet au serveur d’utiliser n’importe quel port UDP, car l’annonce qui indique aux navigateurs d’utiliser HTTP/3 précise toujours explicitement le port.
Concrètement, ce qui change pour vous. Les navigateurs ne commencent généralement pas par HTTP/3. Ils se connectent en HTTP/2 ou HTTP/1.1, trouvent un en-tête Alt-Svc: h3=":443"; ma=86400 dans la réponse, puis utilisent HTTP/3 pour les connexions suivantes vers cet hôte. L’exception est un enregistrement HTTPS dans le DNS dont la valeur alpn liste h3. Le navigateur, par exemple Firefox, peut alors ouvrir HTTP/3 dès la première connexion. Cet enregistrement fournit un indice supplémentaire et ne remplace pas l’en-tête. Celui-ci reste donc indispensable. Il s’agit du principal mécanisme de découverte. Dans nginx, HTTP/2 est devenu une directive distincte à partir de la version 1.25.1 (http2 on; dans le bloc server, en remplacement de l’ancien paramètre listen ... http2), et QUIC est arrivé dans la version mainline 1.25.0. Un site HTTP/3 nécessite alors listen 443 quic reuseport; en plus de listen 443 ssl;. Le module HTTP/3 n’est pas compilé par défaut (--with-http_v3_module), et la documentation de nginx qualifie toujours sa prise en charge d’expérimentale.
Les proxies n’ont pas tous le même niveau de maturité dans ce domaine. Vérifiez donc le comportement correspondant à la version que vous utilisez réellement. En novembre 2026, Caddy fournit HTTP/3 par défaut, sans configuration supplémentaire. nginx nécessite le listener quic explicite ainsi que l’en-tête Alt-Svc décrit ci-dessus. Traefik l’active par entry point au moyen d’une option http3 explicite. Si vous terminez TLS avec Traefik devant plusieurs applications Docker, la version du protocole reçue par vos visiteurs est déterminée à cet endroit. La connexion entre le proxy et votre conteneur reste généralement en HTTP/1.1, quelle que soit la version négociée par le navigateur.
Vérifiez au lieu de supposer. curl --http3 -sS -o /dev/null -D - https://example.com/ ne fonctionne que si curl -V liste HTTP3 parmi ses fonctionnalités, et la plupart des builds des distributions ne l’incluent pas. Le contrôle le plus fiable consiste à utiliser vos propres journaux : ajoutez $server_protocol au format, puis observez ce que négocient les navigateurs réels. Avant cela, vérifiez que le port UDP utilisé par votre listener quic, 443 dans la configuration ci-dessus, est réellement ouvert. Un firewall qui autorise uniquement TCP 443 laissera HTTP/3 échouer discrètement, tandis que le site continuera de fonctionner en HTTP/2. Savoir quels ports sont ouverts et en écoute sur votre serveur Linux est le premier point à vérifier.
HTTPS : HTTP est le protocole, TLS est l’enveloppe
HTTPS n’est pas un protocole distinct. Il s’agit des mêmes requêtes et des mêmes codes d’état, transportés dans une session TLS. Le port 80 les transporte en clair, tandis que le port 443 les transporte de manière chiffrée. La négociation TLS se termine d’abord, puis la requête HTTP circule dans le canal chiffré. C’est pourquoi un problème de certificat n’est jamais associé à un code d’état : l’échec se produit avant l’envoi du moindre octet HTTP, donc aucune réponse ne peut recevoir de code.
Un détail dans l’ordre des opérations est important sur un serveur qui héberge plusieurs sites. Le certificat est sélectionné à l’aide de SNI (server name indication), un champ de la négociation TLS qui contient le nom d’hôte en clair avant même l’existence d’un en-tête HTTP. Le serveur sélectionne donc d’abord un certificat à partir de SNI, puis sélectionne un virtual host à partir de l’en-tête Host. Il s’agit de deux recherches distinctes, qui correspondent normalement. Lorsqu’elles ne correspondent pas, le navigateur affiche une erreur de nom, telle que NET::ERR_CERT_COMMON_NAME_INVALID, et n’envoie aucune requête, car le certificat de votre serveur par défaut a été présenté pour un nom qu’il ne couvre pas.
Pour un site public, obtenez un certificat valide et laissez-le se renouveler automatiquement. Certbot avec Let's Encrypt sur nginx écrit les chemins des certificats dans votre server block et installe le timer de renouvellement à votre place. Pour un nom d’hôte qu’aucune autorité publique ne peut valider, par exemple un nom interne ou une adresse IP privée sur votre propre réseau, un certificat autosigné sur Ubuntu est l’option la plus appropriée, à condition d’accepter que chaque client doive être configuré pour lui faire confiance.
Une fois TLS opérationnel, redirigez tout le trafic du port 80 vers le port 443 :
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}Ajoutez Strict-Transport-Security uniquement lorsque vous en êtes certain. L’en-tête add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; indique aux navigateurs de refuser le HTTP en clair pour ce nom d’hôte pendant deux ans. Ils appliquent cette règle depuis leur propre cache. Retirer l’en-tête ultérieurement ne l’annule donc pas. Commencez avec un max-age de quelques heures. Vérifiez que chaque sous-domaine utilise bien HTTPS, puis augmentez cette valeur.
FAQ
Quelle est la différence entre HTTP et HTTPS ?
HTTPS est du HTTP transmis dans une session TLS (transport layer security). Les méthodes et les codes d’état sont identiques. Ce qui change, c’est que les octets sont chiffrés entre le client et le composant qui termine TLS, et que le port par défaut passe de 80 à 443. Comme la négociation TLS se termine avant l’envoi du premier octet HTTP, un échec de certificat ne produit jamais de code d’état. C’est pourquoi l’avertissement de certificat d’un navigateur affiche un nom d’erreur tel que NET::ERR_CERT_COMMON_NAME_INVALID au lieu d’un nombre comme 403.
Pourquoi mon site renvoie-t-il 502 Bad Gateway ?
Un 502 de nginx signifie que nginx n’a pas pu obtenir de réponse exploitable de l’upstream vers lequel il relaie les requêtes. La requête du visiteur était donc correcte, mais un composant situé derrière nginx ne l’était pas. Consultez /var/log/nginx/error.log. connect() failed (111: Connection refused) while connecting to upstream signifie que rien n’écoute à l’adresse et sur le port indiqués dans proxy_pass. Vérifiez donc que l’application fonctionne et écoute à l’endroit prévu. no live upstreams while connecting to upstream signifie que tous les serveurs du bloc upstream ont été marqués comme indisponibles après des échecs répétés. Comparez avec 504 Gateway Timeout, qui signifie que nginx a dépassé le délai d’attente de l’upstream, soit lors de l’établissement de la connexion dans le délai proxy_connect_timeout, soit lors de l’attente d’une réponse dans le délai proxy_read_timeout.
Pourquoi mon access log affiche-t-il la même adresse IP pour tous les visiteurs ?
Parce que $remote_addr enregistre l’adresse qui a ouvert la connexion TCP. Derrière un reverse proxy ou un content delivery network, cette adresse est celle du proxy. L’adresse du visiteur arrive plutôt dans l’en-tête X-Forwarded-For. Configurez proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; sur le proxy, puis configurez set_real_ip_from sur nginx côté réception avec la plage d’adresses du proxy, ainsi que real_ip_header X-Forwarded-For;. Indiquez uniquement les plages que vous contrôlez. Cet en-tête est du texte que n’importe quel client peut envoyer. Si vous lui faites confiance depuis l’ensemble d’Internet, un visiteur peut donc choisir l’adresse que vous journalisez et celle à laquelle vous appliquez la limitation de débit.
Dois-je activer HTTP/2 ou HTTP/3 ?
HTTP/2 vaut la peine d’être activé. Il s’agit d’une directive sur un site qui utilise déjà TLS, et il supprime la limite de requêtes par connexion qui ralentit les pages contenant de nombreux petits fichiers. HTTP/3 apporte un gain plus limité et moins certain. Il nécessite aussi un port UDP ouvert, généralement 443, ainsi qu’une version du proxy compilée avec la prise en charge de QUIC. N’oubliez pas que les navigateurs passent normalement à HTTP/3 uniquement après avoir reçu un en-tête Alt-Svc dans une réponse précédente, ou un enregistrement DNS HTTPS qui liste h3. Sans l’un de ces éléments, rien ne change, quelle que soit la valeur indiquée par votre directive listen. Ajoutez $server_protocol à votre format de log et mesurez le protocole effectivement négocié par les visiteurs avant d’y consacrer du temps.
Que signifie 403 Forbidden lorsque le fichier existe ?
Sur un site statique, un 403 indique généralement un problème de permissions du système de fichiers plutôt qu’une règle HTTP. open() ... failed (13: Permission denied) dans /var/log/nginx/error.log signifie que l’utilisateur du worker nginx ne peut pas lire le fichier. La cause la plus fréquente est l’absence de permission d’exécution pour les autres utilisateurs sur un répertoire parent, et non un problème de mode du fichier lui-même. directory index of ... is forbidden signifie que la requête correspond à un répertoire sans fichier d’index alors que autoindex est désactivé. Une règle deny explicite dans le bloc location correspondant renvoie également 403. Consultez donc ce bloc lorsque l’error log ne fournit aucune information.