Cache-Control
Utilisation
Les serveurs d’origine, les intermédiaires et les clients s’appuient tous sur Cache-Control pour s’accorder sur le moment où une réponse stockée reste utilisable et sur celui où une copie fraîche est nécessaire. Une même réponse combine souvent plusieurs directives afin d’exprimer une politique de cache complète : combien de temps stocker, qui est autorisé à stocker et que faire une fois la fraîcheur expirée.
Le tableau ci-dessous indique quelles directives s’appliquent aux requêtes, aux réponses ou aux deux.
Plusieurs directives sont séparées par des virgules dans une seule valeur d’en-tête, ou réparties sur plusieurs en-têtes Cache-Control. Lorsque des directives contradictoires apparaissent, c’est la combinaison la plus restrictive qui s’applique.
Remarque
Les directives non reconnues sont ignorées par les caches. Cela permet d’introduire de nouvelles directives sans casser les implémentations plus anciennes.
Remarque
En l’absence d’en-tête Cache-Control, les caches appliquent une fraîcheur heuristique. Une heuristique courante consiste à prendre 10 % du temps écoulé depuis Last-Modified. Cela signifie que les réponses dépourvues de Cache-Control sont mises en cache par les navigateurs et les CDN. Définir explicitement Cache-Control évite tout comportement de mise en cache inattendu.
Remarque
Cache-Control régit le moment de la revalidation (durée de fraîcheur). ETag régit la manière de revalider (identité du contenu). Les deux fonctionnent de concert : Cache-Control détermine si une revalidation est nécessaire, ETag détermine si le contenu a changé. L’en-tête Pragma est l’équivalent HTTP/1.0 de Cache-Control: no-cache et est de fait obsolète.
max-age
La directive max-age indique aux caches de ne renvoyer une réponse stockée que si celle-ci n’est pas plus ancienne que le nombre de secondes spécifié. Les valeurs invalides, comme les nombres négatifs, sont traitées comme 0.
Cache-Control: max-age=<seconds>
Définir max-age=0 force une revalidation de bout en bout. Ce motif trouve son origine dans les implémentations HTTP/1.0 dépourvues de prise en charge de no-cache.
max-stale
La directive max-stale signale qu’une réponse dont l’Age dépasse la durée de fraîcheur, jusqu’au nombre de secondes indiqué, est acceptable. La fenêtre de tolérance commence une fois max-age expiré.
Cache-Control: max-stale=<seconds>
Cette directive est utile lorsqu’un serveur d’origine est temporairement injoignable et qu’une réponse légèrement périmée reste acceptable.
min-fresh
La directive min-fresh demande une réponse stockée dont la durée de fraîcheur restante est d’au moins le nombre de secondes spécifié. Le cache ne renvoie la copie stockée que si la réponse restera fraîche pendant cette période supplémentaire.
Cache-Control: min-fresh=<seconds>
no-cache
Une requête portant no-cache exige que le cache valide la réponse stockée auprès du serveur d’origine avant de servir la copie. Cela force une revalidation sans écarter l’entrée stockée.
Cache-Control: no-cache
Remarque
Pour empêcher totalement un cache de stocker une réponse, utilisez plutôt no-store.
no-store
La directive no-store demande aux caches de ne pas stocker la requête ni la réponse correspondante.
Cache-Control: no-store
no-transform
La directive no-transform interdit aux intermédiaires de modifier le corps de la réponse, par exemple en recompressant des images ou en convertissant des formats de média. La restriction s’applique que l’intermédiaire soit un proxy de cache ou une passerelle de transfert.
Cache-Control: no-transform
only-if-cached
La directive only-if-cached indique au cache de renvoyer une réponse stockée sans contacter le serveur d’origine. Si aucune réponse stockée appropriée n’existe, le cache renvoie un statut 504.
Cache-Control: only-if-cached
max-age
La directive max-age déclare le nombre de secondes pendant lesquelles la réponse reste fraîche après sa génération. Le compteur démarre au moment où le serveur d’origine crée la réponse : le temps de transit et le temps passé dans les caches intermédiaires sont donc décomptés de ce budget.
Cache-Control: max-age=<seconds>
Une réponse avec max-age=3600 reste fraîche pendant une heure. Une fois l’heure écoulée, les caches considèrent la réponse comme périmée et, selon les autres directives présentes, la revalident ou récupèrent une nouvelle copie.
s-maxage
La directive s-maxage (shared max-age) remplace max-age pour les caches partagés tels que les CDN et les serveurs proxy. Les caches privés des navigateurs ignorent s-maxage et se rabattent sur max-age.
Cache-Control: s-maxage=<seconds>
no-cache
La directive no-cache en réponse exige que les caches revalident la réponse stockée auprès du serveur d’origine avant chaque réutilisation. Le cache stocke malgré tout la réponse, ce qui permet les requêtes conditionnelles avec ETag ou Last-Modified.
Cache-Control: no-cache
Lorsque no-cache inclut une liste de noms de champs, seuls ces en-têtes précis nécessitent une revalidation. Le reste de la réponse stockée est servi sans contacter le serveur d’origine.
Cache-Control: no-cache="Set-Cookie"
Remarque
Pour empêcher entièrement le stockage, utilisez no-store.
no-store
La directive no-store empêche tout cache de stocker la réponse. Chaque requête ultérieure est adressée au serveur d’origine.
Cache-Control: no-store
Remarque
no-cache autorise le stockage mais impose une revalidation avant chaque utilisation. Le cache vérifie auprès du serveur d’origine via If-None-Match ou If-Modified-Since et reçoit un 304 si le contenu est inchangé. no-store empêche entièrement le stockage : aucune copie n’est conservée et chaque requête récupère la réponse complète. Une erreur fréquente consiste à utiliser no-cache alors que no-store est attendu pour des données sensibles telles que des relevés bancaires ou des dossiers médicaux. no-cache est efficace pour du contenu fréquemment mis à jour. no-store est destiné au contenu qui ne doit jamais persister sur disque.
no-transform
La directive no-transform en réponse empêche les intermédiaires de modifier le corps de la réponse avant de la transmettre, que l’intermédiaire mette le contenu en cache ou non.
Cache-Control: no-transform
must-revalidate
La directive must-revalidate autorise les caches à servir la réponse tant qu’elle est fraîche. Une fois périmée, le cache contacte le serveur d’origine pour la revalider avant de la servir à nouveau. Si le serveur d’origine est injoignable, le cache renvoie un 504 au lieu de servir du contenu périmé.
Cache-Control: must-revalidate
proxy-revalidate
La directive proxy-revalidate fonctionne exactement comme must-revalidate, à ceci près que l’exigence ne s’applique qu’aux caches partagés. Les caches privés des navigateurs ne sont pas concernés.
Cache-Control: proxy-revalidate
must-understand
La directive must-understand demande à un cache de ne stocker la réponse que s’il reconnaît le code de statut et comprend les exigences de mise en cache associées.
Cache-Control: must-understand, no-store
Associer must-understand à no-store fournit un repli : les caches ne prenant pas en charge must-understand ignorent cette directive inconnue et respectent no-store à la place.
private
La directive private limite le stockage aux caches privés, généralement le navigateur de l’utilisateur final. Les caches partagés tels que les CDN et les serveurs proxy écartent la réponse.
Cache-Control: private
public
La directive public marque une réponse comme éligible au stockage dans les caches partagés. Les réponses portant un en-tête Authorization ne sont pas stockées par les caches partagés, sauf si la directive public est présente.
Cache-Control: public
Remarque
La directive public est inutile lorsque must-revalidate ou s-maxage est déjà présent.
immutable
La directive immutable garantit que le corps de la réponse ne changera pas pendant la durée de fraîcheur. Les caches omettent les requêtes conditionnelles pour les ressources immuables, ce qui supprime les allers-retours de revalidation pour des ressources telles que les bundles JavaScript versionnés ou les images à empreinte.
Cache-Control: public, max-age=31536000, immutable
stale-while-revalidate
La directive stale-while-revalidate prolonge la fenêtre d’utilisabilité d’une réponse périmée. Après l’expiration de la fraîcheur, les caches servent la copie périmée tout en revalidant en arrière-plan. Le paramètre définit pendant combien de secondes supplémentaires la réponse périmée reste acceptable.
Cache-Control: max-age=600, stale-while-revalidate=30
Une fois la revalidation en arrière-plan terminée, le cache remplace l’entrée périmée par la réponse fraîche. Ce motif masque la latence de revalidation aux utilisateurs finaux.
stale-if-error
La directive stale-if-error autorise les caches à servir une réponse périmée lorsque le serveur d’origine renvoie un statut d’erreur (500, 502, 503 ou 504) ou qu’il est injoignable. Le paramètre fixe le nombre de secondes, au-delà de la durée de fraîcheur, pendant lesquelles la réponse périmée reste utilisable comme repli.
Cache-Control: max-age=600, stale-if-error=86400
Exemple
Une ressource statique servie avec une longue durée de fraîcheur et le drapeau immutable. Le navigateur et tout cache partagé stockent cette réponse pendant un an sans revalidation.
Cache-Control: public, max-age=31536000, immutable
Une réponse d’API destinée uniquement au navigateur qui l’a demandée. Le cache stocke la réponse pendant cinq minutes et la revalide avant réutilisation une fois périmée.
Cache-Control: private, max-age=300, must-revalidate
Une politique ciblant le CDN qui accorde dix minutes au cache partagé tandis que le cache du navigateur obtient une minute. Les réponses périmées sont servies pendant 30 secondes au maximum pendant que le CDN revalide en arrière-plan.
Cache-Control: s-maxage=600, max-age=60, stale-while-revalidate=30
Googlebot et max-age
L’infrastructure de crawl de Google met en œuvre une mise en cache HTTP heuristique. La directive max-age aide Googlebot à déterminer la fréquence de recrawl. Une page avec un max-age long est récupérée moins souvent, tandis que no-cache signale des changements de contenu fréquents et justifie un crawl plus fréquent. Les réponses dépourvues d’en-têtes de cache sont par défaut cachables de façon heuristique pour certains codes de statut : les serveurs d’origine souhaitant empêcher totalement la mise en cache doivent donc envoyer no-store explicitement. Bingbot envoie Cache-Control: no-cache sur ses requêtes et s’appuie sur crawl-delay dans robots.txt et sur IndexNow pour cadencer le crawl.
Remarque
Pour une assistance en SEO et en mise en cache, contactez les consultants SEO ex-Google Search Brothers.
Dépannage
Les comportements de cache inattendus proviennent d’une mauvaise compréhension des directives, de remplacements par le CDN ou d’en-têtes contradictoires.
Contenu périmé servi malgré la directive no-cache. La directive no-cache n’empêche pas le stockage. Les caches stockent la réponse et la revalident à chaque réutilisation. Pour empêcher entièrement le stockage, utilisez no-store. Combinez les deux lorsque des données sensibles sont en jeu : Cache-Control: no-cache, no-store.
Le CDN ignore les directives Cache-Control. De nombreux CDN remplacent le Cache-Control de l’origine par leurs propres politiques. Cloudflare respecte par défaut les en-têtes de l’origine, mais les remplace lorsqu’une Page Rule ou une Cache Rule est active. AWS CloudFront utilise l’en-tête de l’origine, sauf si une politique de cache avec des TTL personnalisés est attachée. Vérifiez les règles de remplacement dans le tableau de bord du CDN avant de déboguer la configuration de l’origine.
Le contenu périmé persiste après avoir abaissé max-age. Les nœuds de périphérie du CDN et les caches des navigateurs conservent les réponses jusqu’à l’expiration du max-age initial. Abaisser la valeur sur l’origine ne purge pas les copies déjà mises en cache. Déclenchez une purge du CDN après avoir modifié les durées de cache. Dans Cloudflare, purgez par URL ou utilisez une purge complète de la zone. Dans CloudFront, créez une invalidation pour les chemins concernés.
Le navigateur affiche l’ancienne page lors d’une navigation précédent/suivant malgré no-store. Le cache précédent/suivant (bfcache) de Chrome, Firefox et Safari restaure l’état complet de la page depuis la mémoire, en contournant no-store. Ce comportement est voulu et ne constitue pas une violation du cache. Inspectez l’en-tête Cache-Control dans l’onglet Network des DevTools, avec « Disable cache » décoché, pour confirmer que l’en-tête parvient au navigateur. Ajouter des écouteurs d’événement unload désactive le bfcache dans certains navigateurs mais dégrade les performances.
L’en-tête Expires entre en conflit avec Cache-Control. Lorsque les deux en-têtes sont présents, Cache-Control: max-age l’emporte sur Expires. Supprimez l’en-tête Expires pour éviter toute confusion. Dans nginx :
expires off;
add_header Cache-Control "max-age=3600";
Dans Apache :
Header unset Expires
Header set Cache-Control "max-age=3600"
Diagnostiquer les en-têtes de cache avec curl et les DevTools. Exécutez curl -I https://example.re pour inspecter les en-têtes de réponse provenant de l’origine. Ajoutez -H “Cache-Control: no-cache” pour contourner les caches du CDN et atteindre directement l’origine. Dans les DevTools du navigateur, ouvrez l’onglet Network, sélectionnez la requête et consultez la section Response Headers. Repérez les en-têtes Age, X-Cache et CF-Cache-Status pour déterminer si la réponse provient d’un nœud de périphérie du CDN ou de l’origine.