Nginx почти всегда оказывается в центре, когда мы подключаем сайт к CDN. Чаще всего он выступает в роли origin-сервера: CDN ходит к нему за контентом, кэширует и раздает трафик пользователям. На теории это звучит просто, но на практике всплывают вопросы: что оставить на CDN, что отдать напрямую, как сделать cookie-free статику, какие заголовки Cache-Control выставлять и как не убить кэш очередной правкой конфига.
В этой статье разберем практическую схему «Nginx + бесплатный CDN как origin» с уклоном в производительность и предсказуемость кэша. Ориентируюсь на типовой стек: Nginx (иногда с PHP-FPM), один или несколько доменов, доступ в DNS, возможен как виртуальный хостинг, так и VDS.
Базовая схема: где Nginx, где CDN и что такое origin
В связке «Nginx + CDN» обычно есть три уровня:
- Браузер клиента — ходит в CDN по DNS-имени, которое вы задали (например,
cdn.example.comили самwww.example.com). - CDN — принимает запрос, решает, есть ли свежий кэш, и если нет — идет к origin.
- Nginx как origin — это ваш сервер или виртуальный хостинг, где крутится «реальный» сайт.
Важно понимать: origin — это не «какой-то специальный» сервер. Это тот же Nginx, который вы уже настраивали, только к нему теперь обращается не пользователь напрямую, а CDN-провайдер.
Практический критерий: если вы можете открыть сайт по IP вашего сервера или по внутреннему домену, на этот же адрес будет ходить CDN.
Дальше нужно решить два ключевых вопроса:
- Какой домен будет обслуживать CDN, а какой — ходить на сервер напрямую.
- Как вы делите статику и динамику: cookie-free поддомены, отдельные
server-блоки, разные правилаCache-Control.
Варианты схем: всё через CDN или только статика
Технически CDN можно поставить перед всем сайтом целиком или только перед статическими ресурсами.
Схема 1: CDN перед всем сайтом
В этом варианте:
- Пользователь обращается к
www.example.com. - DNS для
www.example.comуказывает на CDN. - CDN ходит к origin — вашему Nginx — по IP или отдельному техническому домену, например
origin.example.com.
Плюсы:
- Минимум доменов для пользователя: всё на одном имени.
- Можно кэшировать часть HTML (или хотя бы страницы для неавторизованных).
- CDN защищает весь трафик от DDoS и всплесков.
Минусы:
- Сложнее управлять кэшем HTML: надо аккуратно работать с
Vary, авторизацией и cookie. - Отладка чуть сложнее: все запросы идут через CDN, нужно уметь смотреть и CDN-логи, и Nginx-логи.
Схема 2: CDN только для статики (классический cookie-free CDN origin)
В этом варианте:
- HTML обрабатывается и отдается Nginx напрямую по
www.example.com. - Статика (CSS, JS, картинки, шрифты) отдается с поддомена
static.example.comилиcdn.example.com. - Этот поддомен проксируется через CDN, а originом для него выступает Nginx.
Такую схему чаще всего имеют в виду, когда говорят про cookie-free CDN origin: для статики создается поддомен, на который не вешаются сессионные cookie и который можно смело кэшировать «по максимуму».
Плюсы:
- Чёткое разделение динамики и статики.
- Проще и безопаснее агрессивно кэшировать статику.
- Структура URL прозрачно показывает, что именно отдается с CDN.
Минусы:
- Нужно настраивать поддомен и править ссылки на ресурсы в приложении (или через рерайты/подстановки).
- При ошибках в конфиге Nginx или Cache-Control легко случайно сломать кэширование.
Дальше будем фокусироваться именно на второй схеме: cookie-free поддомен со статикой, обслуживаемый CDN, где Nginx — origin.
DNS и поддомен для CDN-origin
Исходим из того, что уже есть домен example.com и сайт открывается по www.example.com, фронтировочный Nginx установлен и работает (на боевом хостинге или локальном сервере).
Нам нужно:
- Создать поддомен статики, например
static.example.comилиcdn.example.com. - Сначала направить его на Nginx, чтобы отладить конфиг без CDN.
- Потом сменить DNS этого поддомена, чтобы он указывал на CDN, а для CDN задать origin в виде нашего сервера.
Типовой порядок действий:
- В панели DNS добавляете запись типа
A(илиAAAAпод IPv6) дляstatic.example.comна IP вашего сервера. - Создаете
server-блок в Nginx под этот домен (ниже покажу пример). - Настраиваете отдачу статики и заголовки
Cache-ControlиExpires. - Когда всё работает — в панели CDN создаете ресурс для
static.example.com, указывая origin (IP сервера или технический домен, напримерorigin.example.com). - Меняете DNS
static.example.comтак, как требует CDN (обычно этоCNAMEна хост CDN или NS-делегирование).
До того как вы включите CDN, сайт должен корректно работать с поддомена статики напрямую: браузер обращается к static.example.com, а Nginx без участия CDN отдает нужные файлы и заголовки. Это сильно упрощает отладку.
Nginx-конфиг для cookie-free статики под CDN
Ниже — минимальный server-блок Nginx для поддомена статики, который удобно использовать как CDN origin.
server {
listen 80;
server_name static.example.com;
root /var/www/example.com/current/public;
# Отдаем только статику, без PHP
location / {
# Защита от обхода index: если файл есть — отдаем, иначе 404
try_files $uri =404;
}
# Общие заголовки для статики
location ~* \.(?:css|js|png|jpe?g|gif|ico|svg|webp|avif|woff2?|ttf|eot)$ {
try_files $uri =404;
# cookie-free: не ставим свои cookie и не прокидываем сессии
proxy_set_header Cookie "";
# Кэш в браузере и CDN
add_header Cache-Control "public, max-age=31536000, immutable";
expires 1y;
# Дополнительно можно отключить логи для мелкой статики
access_log off;
}
}
Несколько важных моментов:
- Путь
rootдолжен указывать на ту же папку, где лежат ваши статические файлы, к которым потом будет обращаться приложение. Часто удобно иметь отдельный каталог под статику, например/var/www/example.com/static. try_filesс=404гарантирует, что вы не отдаете лишнего, и одновременно позволяет CDN корректно кэшировать 404-ответы (это важно, чтобы CDN не забивал origin бессмысленными запросами).Cache-ControlиExpiresвыставляются на уровне Nginx. Большинство CDN по умолчанию уважают эти заголовки и используют их для построения своей политики кэширования.
Здесь мы сразу использовали Cache-Control: public, max-age=31536000, immutable. Такая комбинация подходит для файлов с версионированием в имени (например, app.abc123.js): если вы меняете содержимое — меняете имя файла. Тогда можно безопасно кэшировать его на год.
Если вы хотите глубже оптимизировать цепочку «хостинг + CDN», посмотрите также материал про ускорение WordPress на общем хостинге с CDN и статью о версионировании статики для кэша CDN.

Cookie-free: что это на самом деле и чем здесь помогает Nginx
Чаще всего cookie попадают к статике случайно: корневой домен example.com устанавливает cookie с параметром Domain=.example.com, и в результате браузер шлет их и на static.example.com. Это лишний трафик туда-обратно (иногда десятки килобайт на запрос), плюс некоторые CDN используют наличие или значение cookie при формировании ключа кэша.
У нас есть три линии обороны:
- Не ставить cookie на поддомен статики из кода приложения (или ставить только на
www.example.comс правильнымDomain). - Выделить поддомен
static.example.com, который вообще не обслуживает динамику и не знает о сессиях. - На уровне Nginx очистить заголовок
Cookieпри необходимости.
В простейшем случае достаточно первых двух шагов: поддомен со статикой не обрабатывает PHP, а только отдает файлы, и приложение никак не может туда «прикрутить» cookie.
Но если вы всё же используете проксирование (например, статика генерируется другим сервисом), можно добавить очистку Cookie:
location ~* \.(?:css|js|png|jpe?g|gif|ico|svg|webp|avif|woff2?|ttf|eot)$ {
proxy_pass http://127.0.0.1:9000;
# не отправлять cookie на backend
proxy_set_header Cookie "";
add_header Cache-Control "public, max-age=31536000, immutable";
expires 1y;
}
При этом важно, чтобы proxy_pass вел не на ту же логику, что обслуживает авторизацию или личный кабинет, иначе можно «располовинить» кэш по неожиданным признакам.
Cache-Control и уровни кэша: браузер, CDN, Nginx
Когда вы строите схему «Nginx + CDN origin», нужно учитывать как минимум три уровня кэширования:
- Браузер — смотрит на
Cache-ControlиExpires, иногда наETagилиLast-Modified. - CDN — может использовать те же заголовки, но у него часто есть свои настройки, переопределяющие логику на отдельных путях или хостах.
- Nginx — может кэшировать как фронтовой прокси (
proxy_cache,fastcgi_cache), но это кэш на стороне origin, а не CDN.
Часть ошибок как раз происходит из-за смешения уровней. Если Nginx кэширует сам, а еще и CDN на это сверху накатывается, то при отладке трудно понять, на каком этапе «зависла» старая версия. Поэтому для начала стоит ответить себе на вопросы:
- Нужен ли вам
proxy_cacheилиfastcgi_cacheна стороне Nginx или пусть всё кэширует только CDN. - Кто главный «владелец» заголовков
Cache-Control— Nginx или приложение.
Для большинства проектов схема такая:
- Статика: заголовки кэша задает Nginx в
location-ах по расширениям файлов. - HTML и API: заголовки управляются приложением, а Nginx их не переписывает.
- CDN: по умолчанию читает
Cache-Control, но некоторые пути (например,/api) дополнительно выключены из кэша на стороне CDN.
Жесткий кэш для статических ассетов
Если вы внедряете версионирование ресурсов (например, Webpack, Vite и т.п.), для ассетов лучше всего использовать максимально агрессивный кэш:
location ~* \.(?:css|js|png|jpe?g|gif|ico|svg|webp|avif|woff2?|ttf|eot)$ {
try_files $uri =404;
add_header Cache-Control "public, max-age=31536000, immutable";
expires 1y;
}
Параметр immutable подсказывает браузеру, что ресурс не меняется, пока не изменился URL. CDN тоже может использовать это как подсказку, хотя поведение зависит от конкретного провайдера.
Более мягкий кэш для картинок контента
Чтобы не зависеть от версионирования URL для контентных картинок (загруженных, например, из CMS), имеет смысл поставить умеренный max-age или даже использовать ETag.
location ~* \.(?:jpe?g|png|gif|webp|avif)$ {
try_files $uri =404;
# Более мягкий кэш: 1 неделя
add_header Cache-Control "public, max-age=604800";
expires 7d;
}
Если ваша CMS всегда отдает файлы под новыми именами при изменении, можно снова поднимать срок до месяцев или года.
Контроль динамики, чтобы CDN не кэшировал HTML
Если вы не готовы кэшировать HTML на стороне CDN (авторизация, персонализация, корзина и т.п.), явно укажите это в ответах — либо в приложении, либо через Nginx для определенных локаций:
location / {
# HTML страницы: не кэшировать в браузере
add_header Cache-Control "private, no-store, no-cache, must-revalidate";
}
location /api/ {
add_header Cache-Control "private, no-store, no-cache, must-revalidate";
}
Большинство CDN по умолчанию не кэшируют ответы с Cache-Control: private или no-store, но это обязательно надо сверить с документацией провайдера.
HTTPS, HSTS и тонкости TLS между CDN и Nginx
В бесплатных планах CDN обычно есть варианты:
- CDN завершает HTTPS на своей стороне, а до origin ходит по HTTP или HTTPS.
- HTTPS до origin обязателен, и CDN проверяет сертификат.
Для схемы с Nginx как origin нужно решить:
- Будет ли CDN ходить к вам по HTTPS (желательно — да).
- Какой сертификат вы используете на стороне origin: публичный от ACME-клиента или self-signed (если CDN это позволяет).
На стороне Nginx вы делаете обычный HTTPS-виртуалхост под домен, который используете как origin (например, origin.example.com или тот же static.example.com, если CDN ходит к нему по HTTPS). Покупать или выпускать сертификат логичнее под боевой домен статики; при необходимости можно использовать отдельные SSL-сертификаты под разные поддомены.
server {
listen 443 ssl http2;
server_name static.example.com;
ssl_certificate /etc/ssl/example.com/fullchain.pem;
ssl_certificate_key /etc/ssl/example.com/privkey.pem;
root /var/www/example.com/current/public;
location / {
try_files $uri =404;
}
location ~* \.(?:css|js|png|jpe?g|gif|ico|svg|webp|avif|woff2?|ttf|eot)$ {
try_files $uri =404;
add_header Cache-Control "public, max-age=31536000, immutable";
expires 1y;
}
}
С HSTS нужно быть осторожным: если вы используете его на уровне основного сайта (www.example.com), убедитесь, что не создали схему, в которой часть поддоменов (через CDN и без него) ведут себя по-разному относительно HTTPS. Вместе с CDN обычно достаточно включать HSTS на уровне фронтового домена, не затрагивая отдельный технический origin.

Поведение кэша при изменении конфигов: как не выбивать CDN из строя
Когда Nginx выступает origin-сервером, любые нестабильности на нем отражаются на кэше CDN. Несколько практических советов.
Релоады без потери соединений
Используйте nginx -s reload (или systemctl reload nginx), чтобы перезапуск был плавным. Для CDN важно, чтобы origin отвечал быстро и без существенных пауз — иначе CDN начинает считать его «плохим» и может временно перестать тянуть с него кэш.
Перед боевым изменением конфигов имеет смысл проверить их валидность:
nginx -t
Если есть стендовый сервер, лучше прогонять изменения на нем с аналогичной схемой «Nginx + бесплатный CDN origin», но под тестовым доменом.
Не ломайте Cache-Control без крайней нужды
Почти любой CDN умеет делать принудительную инвалидацию кэша по путям или по маскам. Если вам нужно поменять политику кэширования статики, лучше:
- Сначала внести изменения в
location(например, снизитьmax-age). - Потом сделать частичную инвалидацию в панели CDN по измененным путям.
Массовый перевод всего на no-cache часто бьет и по производительности, и по стоимости (ударов в origin становится в разы больше).
Диагностика: где искать правду — в CDN или в Nginx
Чтобы понять, почему конкретный ресурс не кэшируется, нужно смотреть на цепочку ответа: CDN-статус и origin-ответ.
Полезные шаги при отладке:
- Включить детализированные логи access в Nginx для домена-origin, как минимум на время отладки.
- Посмотреть в панели CDN, пришел ли запрос к origin (у многих провайдеров есть per-request дебаг или минимальные логи).
- В браузере (DevTools) смотреть заголовки ответа, включая X-заголовки, которые ставит CDN (обычно там есть
HitилиMissпо кэшу).
Пример log_format, который удобно использовать для домена статики, чтобы видеть кэширование на уровне браузера и CDN:
log_format cdn_origin '$remote_addr - $host "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_cache_control"';
server {
listen 443 ssl http2;
server_name static.example.com;
access_log /var/log/nginx/static.access.log cdn_origin;
# далее конфиг...
}
Здесь мы логируем заголовок Cache-Control из запроса клиента ($http_cache_control), чтобы понимать, не мешают ли нам режимы вроде «Disable cache» в браузере разработчика.
FAQ: частые вопросы по Nginx + бесплатный CDN origin
Можно ли сделать CDN только для части статики?
Да, можно. Например, поднимать отдельный поддомен img.example.com только под картинки, а CSS и JS продолжать отдавать напрямую. В этом случае в Nginx вы просто делаете отдельный server на img.example.com и настраиваете там свой набор location и Cache-Control. CDN будет видеть только то, что приходит на этот поддомен.
Нужно ли включать proxy_cache в Nginx, если уже есть CDN?
Для большинства сценариев с бесплатным CDN достаточно кэша самого CDN. proxy_cache на origin имеет смысл:
- если origin — это не Nginx, а медленный бэкенд (например, другой внешний API);
- или если у вас несколько CDN или потребителей, и вы хотите централизованный кэш перед ними.
Но для обычного веб-сайта (один CDN, один origin) проще оставить кэш только на CDN и на браузере, а Nginx держать максимально предсказуемым.
Как убедиться, что статика действительно cookie-free?
Откройте DevTools в браузере, вкладку Network, и посмотрите запрос к static.example.com. В разделе Headers:
- В запросе не должно быть заголовка
Cookie. - В ответе не должно быть
Set-Cookie.
Если какой-то cookie всё-таки просачивается, проверяйте:
- параметр
DomainвSet-Cookie(скорее всего, он слишком общий); - наличие динамики (PHP или другой бэкенд) на поддомене статики.
Итоги
Компоновка «Nginx + бесплатный CDN origin» позволяет сильно разгрузить сервер, но требует аккуратной настройки статики и кэша. Ключевые практические моменты:
- Выделяйте cookie-free поддомен под статику и обслуживайте его отдельным
server-блоком Nginx. - Явно задавайте
Cache-ControlиExpiresдля статики, особенно если используете CDN. - Не кэшируйте HTML через CDN, пока не проработаны сценарии авторизации и персонализации.
- Следите за тем, чтобы на статическом поддомене не было
Set-Cookieи чтобы запросы были действительно cookie-free. - При изменениях конфигов используйте плавный reload и по возможности не ломайте политику кэша без необходимости — лучше делать точечную инвалидацию на стороне CDN.
С правильно настроенным Nginx как origin вы сможете безопасно выжимать максимум даже из бесплатного CDN: быстрый отклик, минимальную нагрузку на сервер и предсказуемое поведение кэша.


