MAATRIX / Блог / Что такое CDN изнутри: где на самом деле лежит ваша картинка

Что такое CDN изнутри: где на самом деле лежит ваша картинка

MAATRIX

Вы загрузили новую картинку на сайт, обновили её в админке, а часть посетителей всё равно видит старую версию — иногда сутками. Это не баг деплоя и не кривые руки разработчика: между браузером пользователя и вашим сервером стоит CDN, и картинка в этот момент физически лежит не у вас, а на десятках чужих серверов по всему миру, каждый из которых узнаёт об обновлении в своё время. Разберём, что такое CDN изнутри — куда реально попадает файл при первом запросе, кто и как решает, сколько его хранить, и что сделать, чтобы обновление долетело до всех сразу, а не по одному региону в сутки.

Что происходит, когда вы вводите адрес сайта с картинкой

Когда браузер запрашивает https://example.com/images/banner.jpg, первое, что решает судьбу запроса, — это DNS. У сайта, подключённого к CDN, домен (или его CDN-поддомен) резолвится не в один IP вашего сервера, а в адрес ближайшей к пользователю точки присутствия провайдера CDN — так называемого edge-сервера, или PoP (point of presence). У крупных провайдеров (Cloudflare, Fastly, Amazon CloudFront, BunnyCDN, KeyCDN и других) таких точек — десятки и сотни по всему миру, и обычно применяется anycast-маршрутизация: один и тот же IP-адрес объявлен из множества дата-центров, а сетевая маршрутизация сама подводит запрос к географически ближайшему.

Дальше происходит развилка:

  • Edge-сервер уже хранит эту картинку в кеше (её недавно запрашивал кто-то из того же региона, и срок жизни кеша ещё не истёк) — файл отдаётся сразу с edge, до вашего сервера запрос не доходит вообще.
  • Edge-сервер картинку не хранит (кеш пуст, файл новый, или его срок жизни истёк) — edge идёт за файлом на origin, то есть на ваш настоящий сервер, получает ответ, сохраняет его у себя и только потом отдаёт браузеру.

Ключевая мысль: origin — это ваш сервер, тот самый VPS или выделенный сервер, где физически лежит файл и откуда он раздаётся изначально. CDN — это не хранилище вместо вашего сервера, а прослойка кеширования перед ним. Если завтра отключить весь CDN, сайт продолжит работать напрямую с origin — просто медленнее для дальних пользователей и тяжелее для самого сервера.

Origin и edge: у кого какая роль

Разница между origin и edge — не в том, что один «главный», а другой «второстепенный», а в том, что они решают разные задачи:

Origin (ваш сервер)Edge (сервер CDN)
Что хранитОригинал файла, единственный источник правдыВременную копию — ровно то, что успел закешировать
Где физическиОдин дата-центр (или несколько, если вы сами настроили георепликацию)Множество точек по всему миру у провайдера CDN
Кто управляетВыПровайдер CDN (вы влияете только заголовками и панелью управления)
Что бывает при сбоеOrigin недоступен — edge может отдавать закешированное (stale), но не обновлятьОдин edge упал — anycast перенаправит запрос на соседний
Динамический контентОбрабатывает всегдаОбычно не кеширует (проксирует к origin)

У части CDN есть ещё один слой — origin shield (или «промежуточный кеш»): один выделенный edge-узел, который сам ходит на origin и раздаёт ответ остальным точкам присутствия. Смысл в том, чтобы при холодном кеше на origin пришёл не один запрос с каждого из сотен edge-узлов, а один-единственный, а остальные точки получили файл уже от shield-узла. Если вы замечали, что при массовом наплыве трафика на новый контент origin почти не чувствует нагрузки — это, скорее всего, работа shield-слоя, а не магия.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Кто решает, кешировать ли ответ и на сколько

Edge-сервер не решает произвольно, сколько хранить файл — он читает это из заголовков ответа, которые ваш origin-сервер (точнее, ваш веб-сервер или бэкенд) отправил вместе с файлом. Ключевой заголовок — Cache-Control:

Cache-Control: public, max-age=86400

Здесь public разрешает кешировать ответ не только браузеру, но и промежуточным прокси (в том числе CDN), а max-age=86400 задаёт срок жизни в секундах — в данном примере сутки. Отдельно от max-age существует s-maxage — та же идея, но именно для «разделяемых» кешей вроде CDN, и если оба значения заданы, CDN обычно ориентируется на s-maxage.

Другие важные варианты:

Cache-Control: no-store          # не кешировать нигде и никогда
Cache-Control: private           # можно кешировать в браузере, но не в CDN
Cache-Control: no-cache          # можно кешировать, но каждый раз проверять актуальность
Cache-Control: public, max-age=31536000, immutable   # кешировать год и не перепроверять вовсе

immutable — полезная деталь: она говорит CDN и браузеру, что содержимое по этому URL никогда не изменится, поэтому даже проверку на актуальность делать не нужно. Это осмысленно только вместе с версионированием имени файла (об этом — ниже), иначе вы буквально пообещаете, что файл не изменится, а потом измените его.

Если ваш сервер вообще не прислал Cache-Control (так бывает, если статику отдаёт бэкенд-фреймворк без явной настройки заголовков), у многих CDN включается собственная логика по умолчанию — обычно ориентируясь на тип файла и заголовок Expires, если он есть. Точные значения TTL по умолчанию отличаются у разных провайдеров и со временем меняются в их документации, поэтому полагаться на «default» не стоит — надёжнее явно задать Cache-Control самостоятельно.

Проверить, что реально прислал ваш сервер, можно одной командой:

curl -I https://example.com/images/banner.jpg

В ответе ищите строки cache-control, etag, last-modified — это и есть инструкции, по которым живёт edge-кеш.

Почему одна и та же картинка выглядит по-разному в разных регионах

Это самая частая практическая жалоба: «обновил картинку, а у части пользователей всё ещё старая». Причина почти всегда одна — каждый edge-сервер хранит свою независимую копию кеша. Это не единое распределённое хранилище с мгновенной синхронизацией, а сотни (у крупных провайдеров) слабо связанных между собой кешей. Пользователь из Франкфурта и пользователь из Сингапура обращаются к разным edge-узлам, и у каждого — свой момент, когда файл был закеширован, и свой оставшийся TTL.

Развитие событий обычно такое:

  1. Вы загружаете новую версию banner.jpg на origin под тем же именем файла.
  2. Edge-узел, у которого TTL старой версии ещё не истёк, продолжает отдавать её из кеша — он ничего не знает об обновлении на origin, пока сам не решит перепроверить.
  3. Edge-узел, где кеш к этому моменту уже устарел (или его вовсе не было), при следующем запросе идёт на origin заново и получает новую версию.
  4. В результате разные регионы показывают разные версии картинки одновременно — иногда часами, иногда сутками, в зависимости от того, какой max-age вы задали.

Дополнительный нюанс — заголовки условных запросов ETag и Last-Modified. Если у ответа истёк max-age, но остались ETag/Last-Modified, edge не обязательно скачивает файл заново целиком — он может отправить на origin условный запрос с If-None-Match или If-Modified-Since, и если origin отвечает 304 Not Modified, edge просто продлевает жизнь уже имеющейся у себя копии. Это экономит трафик, но означает, что если вы поменяли файл, а ETag/Last-Modified на origin не обновились (например, файл перезалит с сохранением старой метки времени), edge вполне законно продолжит отдавать старое содержимое.

Как заставить CDN отдать новую версию сразу

Есть два принципиально разных подхода, и на практике лучше комбинировать оба.

Первый — versioning имени файла (cache busting). Вместо того чтобы перезаписывать banner.jpg тем же именем, меняйте имя или добавляйте параметр при каждом изменении:

/images/banner.a1b2c3d4.jpg
/images/banner.jpg?v=2026090501

Раз URL изменился — для CDN и браузера это совершенно новый ресурс, у которого нет никакого устаревшего кеша, который нужно сбрасывать. Это самый надёжный способ, потому что не зависит от скорости распространения команды purge по сети CDN. Большинство сборщиков фронтенда (Webpack, Vite и подобные) уже добавляют хеш в имя файла автоматически — если вы это видите в собранных файлах, инвалидация там уже решена архитектурно.

Второй — явный purge (инвалидация) конкретного URL через API или панель CDN. Если менять имя файла неудобно (например, картинку в CMS ожидаемо перезаписывают по тому же пути), нужно явно попросить CDN сбросить кеш именно этого URL на всех edge-узлах. У Cloudflare это делается, например, так:

curl -X POST "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/purge_cache" \
  -H "Authorization: Bearer <API_TOKEN>" \
  -H "Content-Type: application/json" \
  --data '{"files":["https://example.com/images/banner.jpg"]}'

Важный нюанс: распространение purge-команды по всем точкам присутствия занимает не ноль времени — обычно счёт идёт на секунды-десятки секунд у большинства провайдеров, но конкретную цифру для вашего плана и региона нужно смотреть в документации своего CDN, а не считать её универсальной константой. Кроме того, у части провайдеров purge конкретного URL — платная или ограниченная по частоте функция на бесплатных тарифах, тогда как «сбросить всё» доступно проще, но бьёт по нагрузке на origin: все edge-узлы разом придут за свежими копиями.

Заголовки кеширования, которые стоит настраивать на origin осознанно

Практическое правило — разная статика заслуживает разной политики кеширования, и настраивается это на вашем сервере (в конфиге Nginx, например), а CDN просто выполняет то, что вы прописали:

# Статика с версионированным именем — кешировать максимально долго
location ~* \.(?:[a-f0-9]{8,}\.)?(?:jpg|jpeg|png|gif|webp|css|js|woff2)$ {
    add_header Cache-Control "public, max-age=31536000, immutable";
}

# HTML-страницы — не давать CDN кешировать надолго,
# чтобы контент обновлялся быстро
location ~* \.html$ {
    add_header Cache-Control "public, max-age=60, must-revalidate";
}

# Приватные страницы с авторизацией — вообще не кешировать
location /account/ {
    add_header Cache-Control "private, no-store";
}

Логика простая: файлы, у которых при изменении меняется имя (хешированные бандлы, версионированные картинки), можно кешировать хоть на год — это безопасно ровно потому, что старое имя больше никто не запросит. А вот файлы, которые вы обновляете под тем же именем (логотип, favicon, статичные картинки в CMS без версионирования), должны жить в кеше недолго — минуты, максимум часы, — иначе после каждого обновления вы будете зависеть от ручного purge на всех edge-узлах.

Отдельно стоит Vary — если ваш сервер отдаёт разный контент в зависимости от заголовка запроса (например, Accept-Encoding для сжатия или Accept для формата картинки типа AVIF/WebP), это нужно явно объявить через Vary: Accept-Encoding, иначе CDN рискует закешировать одну версию и отдавать её всем подряд независимо от возможностей браузера. Это же касается связки с security headers для сайта — часть из них (Cache-Control в контексте Set-Cookie, например) напрямую влияет на то, что CDN вообще имеет право кешировать.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Если у меня уже настроен Nginx-кеш на сервере, зачем ещё и CDN?

Это разные уровни. Кеш Nginx (proxy_cache/fastcgi_cache) экономит вычисления вашего собственного сервера — рендер страницы, запрос к БД — но ответ всё равно физически идёт с одного дата-центра до пользователя через весь путь в сети. CDN добавляет географическую близость: копия лежит рядом с пользователем, а не только рядом с бэкендом. Подробнее о настройке серверного кеша — в статье про кеширование Nginx на VPS.

CDN обязательно нужен небольшому сайту?

Нет. Если аудитория сосредоточена в одном регионе и сервер стоит рядом с ней, выигрыш от CDN для HTML минимален — основная польза для маленького проекта обычно в разгрузке отдачи статики и в защите от простых DDoS на уровне HTTP, а не в скорости.

Как узнать, отдал ли конкретный ответ edge-кеш или он ушёл на origin?

Смотрите служебные заголовки в ответе — у большинства CDN есть свой индикатор, например CF-Cache-Status: HIT/MISS/EXPIRED у Cloudflare или X-Cache: Hit from cloudfront у CloudFront. Команда та же: curl -I <url>.

Можно ли вообще отключить кеш на CDN для конкретного пути?

Да, у всех провайдеров есть page rules / cache rules, позволяющие исключить конкретный путь (например, /api/* или /account/*) из кеширования полностью — это стандартная и часто обязательная настройка для динамических и приватных разделов сайта.

Что будет, если origin временно недоступен?

Часть CDN умеет отдавать «протухший», но ещё хранящийся в кеше ответ вместо ошибки — эта функция обычно называется stale-if-error или настраивается отдельной опцией в панели. Работает не по умолчанию и не у всех тарифов — стоит проверить в документации конкретного провайдера, если для вас это критично.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →