Cache HTTP

ETag, Last-Modified, Cache-Control et Vary : éviter de renvoyer des données inchangées via les headers HTTP.

Créé le 11 juin 2026·Mis à jour le 11 juin 2026

Introduction


Le cache HTTP est un mécanisme qui permet d'éviter de renvoyer des données inchangées entre le serveur et le client. Plutôt que de régénérer et transmettre une réponse identique à chaque requête, le serveur peut indiquer au client que sa version locale est toujours valide.

Il existe deux grandes approches :

  • Cache de validation : le client possède une copie et demande au serveur si elle est encore à jour (ETag, Last-Modified).
  • Cache d'expiration : le serveur indique au client combien de temps la réponse peut être réutilisée sans même le contacter (Cache-Control).

ETag


Un ETag est un identifiant de version attribué à une ressource. Lors de la première réponse, le serveur l'inclut dans le header ETag. Le client le conserve en cache avec la réponse.

HTTP/1.1 200 OK
Content-Type: application/json
ETag: "d4e8f2a1"

{ "id": 5, "nom": "Produit A", "prix": 29.90 }

Lors d'une requête suivante, le client transmet l'ETag via le header If-None-Match :

GET /produits/5 HTTP/1.1
If-None-Match: "d4e8f2a1"

Le serveur régénère l'ETag courant de la ressource et le compare. S'ils sont identiques, la ressource n'a pas changé :

HTTP/1.1 304 Not Modified
ETag: "d4e8f2a1"

Le serveur répond 304 Not Modified sans corps : le client utilise sa copie en cache. Le gain est double : moins de bande passante consommée, et moins de travail côté serveur (pas de sérialisation, pas de corps à transmettre).

Générer un ETag en PHP


<?php

function generateEtag(array $data): string
{
    return '"' . md5(json_encode($data)) . '"';
}

$produit = ['id' => 5, 'nom' => 'Produit A', 'prix' => 29.90];
$etag = generateEtag($produit);

header('ETag: ' . $etag);

if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] === $etag) {
    http_response_code(304);
    exit;
}

header('Content-Type: application/json');
echo json_encode($produit);

L'ETag peut aussi être construit à partir d'un numéro de version en base ou d'une date de dernière modification, à condition qu'il change à chaque modification de la ressource.

Last-Modified


Last-Modified est l'alternative basée sur la date. Le serveur inclut la date de dernière modification de la ressource dans sa réponse :

HTTP/1.1 200 OK
Last-Modified: Wed, 11 Jun 2026 10:00:00 GMT

Le client transmet cette date dans les requêtes suivantes via If-Modified-Since :

GET /produits/5 HTTP/1.1
If-Modified-Since: Wed, 11 Jun 2026 10:00:00 GMT

Si la ressource n'a pas été modifiée depuis cette date, le serveur répond 304 Not Modified.

Le fonctionnement est identique à l'ETag, mais la précision est limitée à la seconde. Si une ressource peut être modifiée plusieurs fois par seconde, les ETag sont préférables.

Cache-Control


Cache-Control est le mécanisme d'expiration : il indique au client combien de temps il peut réutiliser la réponse sans contacter le serveur.

Cache-Control: public, max-age=3600

Les directives principales :

DirectiveDescription
publicLa réponse peut être mise en cache par n'importe quel intermédiaire
privateUniquement le client final (pas de proxy, pas de CDN)
no-cacheMet en cache mais revalide à chaque accès
no-storeInterdit toute mise en cache
max-ageDurée de validité en secondes
s-maxageDurée spécifique aux caches partagés (proxies, CDN)
must-revalidateInterdit de servir une réponse expirée
Cache-Control: private, no-cache
Cache-Control: public, max-age=86400, s-maxage=604800
Cache-Control: no-store

no-cache ne signifie pas "ne pas mettre en cache" mais "mettre en cache et revalider à chaque accès". Pour interdire totalement la mise en cache, utiliser no-store.

En PHP


<?php

// Ressource publique, valide 1 heure
header('Cache-Control: public, max-age=3600');

// Données personnalisées, jamais en cache partagé
header('Cache-Control: private, no-store');

// Ressource statique, longue durée
header('Cache-Control: public, max-age=2592000, immutable');

Vary


Vary indique aux caches que la réponse peut différer selon certains headers de la requête. Sans lui, un cache partagé pourrait servir la mauvaise version à un client.

Exemple : si votre API retourne les données dans la langue du client, vous devez signaler que le cache dépend de Accept-Language :

HTTP/1.1 200 OK
Cache-Control: public, max-age=3600
Vary: Accept-Language

Un cache intermédiaire stockera une version par langue et servira la bonne selon le header de la requête entrante.

Combiner validation et expiration


ETag et Cache-Control sont complémentaires et s'utilisent ensemble :

HTTP/1.1 200 OK
Cache-Control: public, max-age=300
ETag: "d4e8f2a1"

Le client réutilise la réponse pendant 300 secondes sans contacter le serveur. Passé ce délai, il envoie l'ETag avec If-None-Match. Si la ressource n'a pas changé, le serveur répond 304 sans corps. C'est l'approche la plus efficace.

Résumé


MécanismeTypeEn-tête clientEn-tête serveurCode si valide
ETagValidationIf-None-MatchETag304
Last-ModifiedValidationIf-Modified-SinceLast-Modified304
Cache-ControlExpiration-Cache-Control-
VarySegmentation-Vary-

Le cache HTTP est sans doute la couche de cache la plus rentable à mettre en place : elle agit avant même que PHP soit sollicité, et peut être combinée avec un reverse proxy pour éliminer entièrement le traitement côté application.