Le CDN en détail : routage, cache et en-têtes
Un CDN garde des copies de vos fichiers dans des dizaines de points de présence et sert chaque visiteur depuis le plus proche, ce qui retire la majeure partie du temps d'aller-retour réseau et une bonne part de la charge de votre origine. Son comportement réel se règle avec trois choses : les en-têtes Cache-Control et s-maxage, la clé de cache, et la façon dont l'IP du visiteur est transmise à l'origine.
Ce qui change entre le clic et le premier octet
Sans CDN, chaque requête traverse la distance qui sépare le visiteur de votre serveur. La lumière dans une fibre avance à environ 200 000 km/s, soit 5 µs par kilomètre. Paris-New York fait 5 840 km à vol d'oiseau : 58 ms d'aller-retour dans le meilleur des cas, et plutôt 75 à 90 ms en vrai, parce qu'aucun câble ne suit la ligne droite et que chaque routeur intermédiaire ajoute son délai de traitement.
Ce coût se paie plusieurs fois avant même le premier octet de HTML. Une connexion TCP demande un aller-retour, TLS 1.3 un autre, TLS 1.2 deux : sur ce trajet transatlantique, vous en êtes à 174 ms de préambule avec du TLS 1.2, avant d'avoir envoyé la requête GET. Un point de présence à Paris ramène ce même préambule sous les 20 ms, et HTTP/3 sur QUIC (RFC 9000 et RFC 9114) fusionne le handshake transport et le handshake TLS pour économiser encore un aller-retour.
Le CDN ne rend pas votre application plus rapide. Il déplace une copie plus près, et il évite à l'origine de répondre deux mille fois à la même demande de logo.
Anycast ou routage DNS : deux façons de choisir le PoP
Deux mécanismes coexistent. En anycast, la même adresse IP est annoncée en BGP depuis tous les points de présence à la fois, et le réseau de votre opérateur choisit l'annonce la plus proche au sens topologique, pas géographique. Un dig sur un domaine derrière Cloudflare renvoie deux adresses en 104.x ou 172.67.x, et ces mêmes adresses répondent depuis Francfort, Sydney ou Sao Paulo selon l'endroit d'où vous les interrogez.
L'autre approche fait le travail dans le DNS : le résolveur pose la question, le serveur autoritaire du CDN renvoie une adresse différente selon la position de ce résolveur. C'est la logique historique d'Akamai, et celle de Route 53 devant CloudFront. Elle a un angle mort. Si votre résolveur se trouve à 2 000 km de vous et que l'extension EDNS Client Subnet (RFC 7871) n'est pas transmise, vous êtes envoyé vers le PoP proche du résolveur, pas vers celui qui est proche de vous.
Identifier le CDN devant un site revient donc à identifier le propriétaire de l'adresse. Un WHOIS sur l'IP renvoyée par dig +short donne l'AS qui l'annonce.
| Fournisseur | AS principal | Routage dominant |
|---|---|---|
| Cloudflare | AS13335 | Anycast BGP |
| Fastly | AS54113 | Anycast BGP |
| Amazon CloudFront | AS16509 | DNS, latence Route 53 |
| Akamai | AS20940 | DNS, mapping propriétaire |
| Google Cloud CDN | AS15169 | Anycast BGP |
Les en-têtes qui décident vraiment de la durée de cache
La règle de base tient en une phrase : le CDN obéit à Cache-Control, et s-maxage l'emporte sur max-age pour tous les caches partagés. Vous pouvez donc demander au navigateur de revalider toutes les 60 secondes et au CDN de garder la réponse une heure, avec Cache-Control: public, max-age=60, s-maxage=3600. La référence est le RFC 9111, publié en juin 2022, qui remplace le RFC 7234.
Sans en-tête, chaque CDN applique sa valeur par défaut, et elles ne se ressemblent pas. CloudFront retient 86 400 secondes quand l'origine ne dit rien. Cloudflare décide d'abord à partir de l'extension du fichier et ne met pas le HTML en cache tant que vous n'avez pas écrit une règle explicite. Deux sites identiques derrière deux fournisseurs différents n'ont donc pas le même comportement, et c'est presque toujours là que commence le débogage.
La panne classique : Cache-Control: public, max-age=31536000 posé par erreur sur une réponse HTML. Vous purgez le CDN, la page se corrige pour les nouveaux visiteurs, et les autres restent bloqués sur l'ancienne version jusqu'à un an, parce qu'une purge côté CDN ne touche pas le cache du navigateur. Le seul remède est de changer d'URL. C'est aussi la raison pour laquelle on met une empreinte dans le nom du fichier : app.7f3c1d.css peut vivre un an en cache, app.css non.
Autre piège, plus discret : Vary: User-Agent. Chaque chaîne d'agent utilisateur crée sa propre entrée, et il en circule des dizaines de milliers de variantes. Le taux de HIT s'effondre sans qu'aucune page ne paraisse cassée. Limitez Vary à ce qui change vraiment la réponse, en général Accept-Encoding et parfois Accept-Language.
| Directive ou en-tête | Effet | Exemple |
|---|---|---|
| max-age=N | Fraîcheur pour tous les caches, navigateur compris | max-age=31536000 |
| s-maxage=N | Fraîcheur pour les caches partagés seuls, prioritaire | s-maxage=3600 |
| private | Interdit le stockage par le CDN, autorise le navigateur | private, max-age=0 |
| no-store | Aucun stockage, nulle part | no-store |
| stale-while-revalidate=N | Sert la copie périmée pendant la revalidation de fond (RFC 5861) | stale-while-revalidate=30 |
| stale-if-error=N | Sert la copie périmée si l'origine répond 5xx (RFC 5861) | stale-if-error=86400 |
| immutable | Le navigateur ne revalide pas, même au rechargement (RFC 8246) | max-age=31536000, immutable |
| Vary | Ajoute des en-têtes à la clé de cache | Vary: Accept-Encoding |
| Age | Secondes passées dans le cache, ajouté par le CDN (RFC 9111) | Age: 412 |
| Surrogate-Control | Consignes lues puis retirées par le CDN avant le client | Surrogate-Control: max-age=600 |
Lire l’état du cache dans une réponse
Deux curl d'affilée suffisent à savoir ce qui se passe. La commande curl -sSI https://exemple.fr/assets/app.css renvoie les en-têtes seuls : la première réponse porte souvent cf-cache-status: MISS et age: 0, la seconde cf-cache-status: HIT avec un age qui grandit à chaque seconde.
Pour séparer ce qui coûte du temps, utilisez les variables de sortie de curl : curl -o /dev/null -s -w 'dns:%{time_namelookup} tcp:%{time_connect} tls:%{time_appconnect} ttfb:%{time_starttransfer}' https://exemple.fr/. Si time_appconnect chute après la mise en place du CDN mais que time_starttransfer bouge à peine, le temps part dans la génération de la page côté origine, et aucun PoP n'y changera quoi que ce soit tant que la réponse n'est pas cachée.
- cf-cache-status (Cloudflare) : HIT, MISS, EXPIRED, STALE, REVALIDATED, UPDATING, BYPASS, DYNAMIC. La valeur DYNAMIC signifie que la ressource n'a même pas été considérée comme cachable.
- x-cache (CloudFront) : Hit from cloudfront, Miss from cloudfront, RefreshHit from cloudfront, accompagné de x-amz-cf-pop dont les trois premières lettres sont le code IATA de l'aéroport le plus proche du PoP, par exemple CDG50-P1 pour Paris.
- x-served-by et x-cache-hits (Fastly) : la chaîne liste les nœuds traversés et le nombre de HIT sur chacun, ce qui rend visible le tiered caching.
- age : présent sur toute réponse sortie d'un cache partagé, il donne le temps écoulé depuis la mise en cache et permet de vérifier qu'un TTL est réellement appliqué.
- 304 Not Modified : la revalidation a fonctionné, l'origine ou le PoP a comparé votre ETag ou votre Last-Modified et n'a pas renvoyé le corps.
L’IP du visiteur ne remonte plus, et ce que ça casse
Dès qu'un CDN est en place, votre origine ne voit plus que les adresses du CDN. Les logs se remplissent de 104.16.0.0/13 ou de 151.101.0.0/16, la géolocalisation croit que tout le monde vient du même endroit, et une limitation de débit par IP bloque tous vos visiteurs d'un coup au lieu du seul abuseur.
L'adresse réelle voyage dans un en-tête. X-Forwarded-For est l'usage de fait, Forwarded en est la version normalisée (RFC 7239), et chaque fournisseur ajoute la sienne : CF-Connecting-IP chez Cloudflare, True-Client-IP chez Akamai. Côté serveur, la remise en place se fait en deux lignes.
Ne faites jamais confiance à cet en-tête sans liste blanche. La commande curl -H 'X-Forwarded-For: 8.8.8.8' https://votre-origine.fr suffit à se choisir une adresse si l'origine accepte l'en-tête de n'importe quel émetteur : bannissement contourné, quota contourné, journal faussé. Quand plusieurs proxies s'enchaînent, X-Forwarded-For contient une liste séparée par des virgules, et la valeur fiable est la dernière ajoutée par un proxy de confiance, jamais la première de la chaîne, qui vient du client.
Corollaire : filtrez l'origine. Si son IP publique reste joignable en direct, le cache, le WAF et la limitation de débit se contournent en une requête. Cette adresse fuit souvent par l'historique DNS du domaine, par un enregistrement MX ou SPF resté sur le même serveur, ou par un certificat visible dans les journaux de Certificate Transparency. Le RFC 8586 définit par ailleurs l'en-tête CDN-Loop, utile pour détecter les boucles quand deux CDN se retrouvent chaînés par accident.
- nginx, module ngx_http_realip_module : une ligne set_real_ip_from par plage publiée du CDN, puis real_ip_header CF-Connecting-IP;
- Apache, mod_remoteip : RemoteIPHeader CF-Connecting-IP, complété par RemoteIPTrustedProxy pour chacune des plages.
- Les plages sont publiées et changent : récupérez-les depuis le fournisseur (cloudflare.com/ips-v4 et ips-v6 pour Cloudflare) plutôt que de les recopier une fois pour toutes.
Confusions courantes
Un CDN est un proxy inverse distribué. Un Varnish ou un nginx posé devant votre application fait le même travail de cache, mais depuis un seul endroit : vous gagnez de la charge, pas de la latence. À l'inverse, un CDN branché sur une origine mal réglée ne vous donne qu'un cache vide qui remonte tout.
Il n'est pas un hébergeur, sauf si vous publiez un site entièrement statique sur son stockage. Il n'est pas non plus un pare-feu applicatif : CDN, WAF et anti-DDoS sont vendus ensemble par les mêmes acteurs, ce qui entretient la confusion, alors que ce sont trois produits distincts avec trois configurations distinctes. Et le résolveur public 1.1.1.1 de Cloudflare n'a aucun rapport avec son CDN, malgré le nom commun.
Pour du contenu personnalisé, la bonne question n'est pas « cachable ou pas » mais « quelle partie ». Une page de compte client ne se met pas en cache, ses images et sa feuille de style oui. Reste alors le gain de la poignée de main TLS terminée au PoP et celui des connexions déjà ouvertes vers l'origine, mesurable avec time_appconnect, mais bien plus modeste qu'un HIT franc.
Dernier point d'honnêteté : le nombre de PoP annoncé par chaque fournisseur bouge tous les trimestres, et les chiffres marketing comptent parfois les villes, parfois les datacenters, parfois les deux. Prenez-les sur la page du fournisseur au moment où vous en avez besoin, pas dans un article, y compris celui-ci.
Testez vos connaissances
Quelle directive fixe une durée de cache pour le CDN différente de celle du navigateur ?
Score : 0 sur 3
Questions fréquentes
Un CDN sert-il à quelque chose si tous mes visiteurs sont en France ?
Le gain de latence pure devient faible : Roubaix vers Marseille, c'est une dizaine de millisecondes. Ce qui reste : l'absorption des pics de trafic, la terminaison TLS répartie, la protection de l'origine et la bande passante sortante qui n'est plus facturée par votre hébergeur. Sur un site chargé d'images ou de vidéos, ces points pèsent plus que les millisecondes.
Pourquoi mes statistiques ne montrent plus qu’une poignée d’adresses IP ?
Parce que votre serveur enregistre l'IP du point de présence, pas celle du visiteur. Configurez le module realip de nginx (set_real_ip_from puis real_ip_header) ou mod_remoteip sur Apache, avec les plages publiées par votre CDN. Sans ça, vos logs, vos bannissements et votre géolocalisation reposent tous sur la même mauvaise adresse.
Comment vérifier qu’une page est bien servie depuis le cache ?
curl -sSI sur l'URL, deux fois de suite. Regardez cf-cache-status, x-cache ou x-served-by selon le fournisseur, et l'en-tête age. Un age qui reste à 0 sur toutes les requêtes indique que rien n'est mis en cache, le plus souvent à cause d'un Set-Cookie dans la réponse ou d'un Cache-Control: private.
Faut-il purger le CDN à chaque déploiement ?
Pas si vos fichiers statiques portent une empreinte dans leur nom : les nouvelles URL sont inconnues du cache donc servies neuves, et les anciennes expirent seules. La purge reste utile pour le HTML et les fichiers à nom fixe comme /sitemap.xml. Comptez de quelques secondes à quelques minutes de propagation selon le fournisseur, et gardez en tête qu'elle ne vide aucun cache navigateur.
Un CDN protège-t-il d’une attaque par déni de service ?
Il absorbe le volume, ce qui traite le cas des attaques volumétriques sur des ressources cachées. Il ne protège de rien si l'IP d'origine reste joignable en direct : l'attaquant vise le serveur et ignore le PoP. Restreindre l'origine aux seules plages du CDN fait partie de l'installation, pas des options.
À lire aussi
cdn · anycast · cache · codes statut http