Когда сайт начинает отдавать крупные файлы — видео, аудио, образы, большие архивы — запросы с заголовком Range появляются немедленно. Их присылают медиаплееры для перемотки, скачивальщики для докачки и браузеры при нестабильных сетях. Ответ на такой запрос — 206 Partial Content — кажется простым, но на практике часто бьётся о кэш, компрессию, ETag и прокси. Ниже — практический минимум по Nginx и Apache, чтобы частичная отдача работала стабильно и предсказуемо.
Что такое HTTP Range и когда он нужен
Range: bytes=... просит сервер выдать не весь ресурс, а часть. Клиент указывает диапазон байт: от‑до или от‑конца. Сервер отвечает 206 Partial Content с заголовком Content-Range и куском тела. Зачем это нужно:
- Перемотка в медиаплеере без скачивания всего файла.
- Докачка после обрыва — клиент запрашивает недостающий хвост.
- Оптимизация доставки через кэш: можно отдать только востребованный фрагмент.
Сервер, который умеет корректно Range, обычно добавляет Accept-Ranges: bytes в ответы 200/206, тем самым сигнализируя клиентам поддержку частичной отдачи.
Ключевые заголовки и их смысл
Range: bytes=0-1023— запрос первых 1024 байт. Можно просить «с конца»:bytes=-1024.Content-Range: bytes 0-1023/1234567— что именно отдали: диапазон и полный размер.Accept-Ranges: bytes— сервер поддерживает отдачу по байтам.If-Range— условная частичная отдача: «если контент не изменился (по ETag или Last-Modified), верни 206; иначе отдай целиком 200».ETagиLast-Modified— валидаторы. ДляIf-Rangeкорректно работает только сильный ETag (без префиксаW/).
Правило безопасности: если запрошенный диапазон некорректен (выходит за размер файла), возвращайте
416 Range Not SatisfiableсContent-Range: bytes */size. Это помогает клиентам пересчитать офсеты.
Частичная отдача и кэш: почему возникают проблемы
Кэширование 206 — тонкая тема. Базовые прокси и браузеры не всегда кэшируют частичные ответы, а некоторые серверы по умолчанию не хотят класть 206 в кэш. Отсюда типовые симптомы:
- Множественные запросы к бэкенду при перемотке — каждый «скачок» лезет на origin.
- Несовместимость с компрессией: диапазон относится к байтам сжатого тела, а не исходного контента, что ломает воспроизведение.
- Конфликт валидаторов между нодами: разные
ETagвызывают 200 вместо 206, сбивая клиентов.
Есть две рабочие стратегии:
- Игнорировать Range на уровне бэкенда и кэшировать полный 200, а частичные ответы отдавать уже из кэша. Так умеет Apache mod_cache (директива
CacheIgnoreRange), и так можно организовать в Nginx через модульslice. - Кэшировать фрагменты как отдельные сущности («шардировать» ресурс на куски фиксированного размера) и собирать ответ из этих кусочков. Это классический подход Nginx с
sliceдля медиаконтента.
Если вы выбираете стратегию кэширования и политику валидаторов, пригодится материал Cache-Control и ETag для статики.

Nginx: статическая отдача с Range без сюрпризов
Nginx из коробки корректно поддерживает Range для статических файлов. Основная задача — сделать отдачу неблокирующей и ограничить злоупотребления многодиапазонными запросами.
# /etc/nginx/nginx.conf (фрагмент)
http {
# Включаем сильные ETag для статического контента
etag on;
# Ограничение многодиапазонных запросов (защита от лишней нагрузки)
max_ranges 1;
}
# server/location для больших файлов
location /media/ {
# Неблокирующая отдача больших файлов
sendfile on;
tcp_nopush on;
aio threads;
directio 8m;
# Явно показываем клиентам поддержку Range
add_header Accept-Ranges bytes;
# Контроль кэшируемости (пример, подбирайте значения под проект)
add_header Cache-Control public, max-age=31536000, immutable;
}
Зачем directio и aio threads? Для больших объектов это снижает блокировки на диске: чтение идёт в обход page cache (с выбранного порога) и выполняется пулом потоков, а не рабочими процессами Nginx. max_ranges 1 закрывает старый класс атак и странные кейсы мультидиапазонов, которые большинству медиаплееров не нужны. Если вы только выбираете стек обслуживания статики, посмотрите короткое сравнение Nginx vs Apache.
Проверяем статическую отдачу
# Полный ответ (ожидаем 200 и Accept-Ranges)
curl -I https://example.org/media/big.mp4
# Первый килобайт (ожидаем 206 и Content-Range)
curl -i -H "Range: bytes=0-1023" https://example.org/media/big.mp4
# Хвост с произвольной позиции
curl -i -H "Range: bytes=1048576-" https://example.org/media/big.mp4
Проверьте, что Content-Length в 200‑ответе равен полному размеру, а Content-Range в 206 корректно указывает диапазон и общий размер.
Nginx как прокси: кэш и Range через slice
Если файлы лежат за бэкендом (S3‑совместимое хранилище, приложение, другой сервер) и вы хотите эффективно кэшировать частичные запросы, удобен модуль slice. Он «разрезает» ресурс на фиксированные куски и может кэшировать каждый слайс отдельно.
# Общие настройки кэша
proxy_cache_path /var/cache/nginx/media levels=1:2 keys_zone=media:20m max_size=50g inactive=7d;
server {
location /video/ {
# Размер шардирования (подбирайте под типичный bitrate и RTT)
slice 1m;
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
# Пробрасываем диапазон слайса к origin
proxy_set_header Range $slice_range;
# Ключ кэша должен включать диапазон
proxy_cache media;
proxy_cache_key $scheme$proxy_host$request_uri$slice_range;
proxy_cache_valid 200 206 30m;
# Если origin не умеет Range и отдаёт 200, Nginx сам сформирует 206
proxy_force_ranges on;
# Защита и предсказуемость
max_ranges 1;
add_header Accept-Ranges bytes;
# Рекомендованная кэшируемость для медиа
add_header Cache-Control public, max-age=604800;
}
}
Как это работает: клиент просит bytes=10m-, Nginx разбивает на сегменты по 1 МБ, делает несколько подзапросов с Range к origin, складывает слайсы в кэш с разными ключами и собирает итоговый 206 для клиента. Повторные перемотки для соседних диапазонов чаще попадут в кэш.
Важно учитывать компрессию: если gzip включён для типа контента, диапазон будет относиться к сжатому телу. Для видео/аудио сжатие обычно не нужно — исключите медиа из gzip_types, иначе Range станет бесполезным для плееров.
Для высокой нагрузки и контроля кеша выделяйте отдельный узел — на собственном VDS можно гибко настроить Nginx, диски и лимиты под профиль медиатрафика.
Типичные ловушки с Nginx и Range
- Разные ETag на нодах. Для статических файлов оставляйте
etag on, но следите, чтобы при выкладке сохранялись время модификации и размер — тогда ETag совпадёт. Синхронизируйте контент так, чтобы сохранялсяmtime. - Многодиапазонные запросы. Ограничьте
max_ranges 1. Этого достаточно для большинства клиентов и экономит CPU/IO. - Кэш и 206. Не все прослойки охотно кэшируют 206. С
sliceскладывайте куски как 200/206 и собирайте ответ. Без шардирования кэшировать произвольные диапазоны не стоит — получится фрагментация. - Несовместимость с gzip для медиа. Уберите медиа из
gzip_types, чтобы диапазоны соответствовали реальным байтам файла.

Apache: статическая выдача Range и ограничение мультидиапазонов
Apache поддерживает частичную отдачу из коробки. Для больших файлов включаем эффективную выдачу и ограничиваем мультидиапазоны.
# httpd.conf или vhost (фрагмент)
# Сильные ETag без inode (стабильно на кластере)
FileETag MTime Size
# Лимитируем количество диапазонов в запросе
MaxRanges 1
# Рекомендации для статической отдачи
EnableSendfile On
EnableMMAP Off
# Пример контекстных настроек для каталога с медиа
<Directory "/var/www/media">
# Явно объявляем поддержку Range
Header set Accept-Ranges bytes
# Делаем контент публично кэшируемым (подберите сроки хранения)
Header set Cache-Control "public, max-age=604800"
</Directory>
FileETag MTime Size — важный момент для кластеров: исключаем инод, чтобы ETag был одинаковым на разных серверах при равных размере и времени модификации.
Apache mod_cache: игнорировать Range на backend, отдавать из кэша
Если хотите, чтобы backend всегда получал запросы без Range, а частичные ответы обслуживались фронтендом из кэша, используйте mod_cache с директивой CacheIgnoreRange:
# Диск-кэш для /media, игнорируем Range при запросе к origin
CacheQuickHandler On
CacheLock On
CacheLockAge 5
CacheLockMaxAge 10
CacheEnable disk "/media"
CacheIgnoreRange On
CacheHeader on
Схема такая: первый запрос с Range приведёт к получению полного объекта 200 от origin и его кэшированию; затем Apache сам нарежет 206 для клиентов из локального кэша. Этот подход хорош, если объект имеет разумный размер и часто переиспользуется.
Валидаторы, If-Range и консистентность
If-Range — полезный механизм: клиент запрашивает часть файла только если он не изменился. Если валидатор не совпал, сервер вернёт полный 200. Чтобы всё работало предсказуемо:
- Обеспечьте сильный
ETag(безW/) и стабильный между нодами. В Nginx для статических файлов это достигается сохранениемmtimeпри выкладке. В Apache используйтеFileETag MTime Size. - Не меняйте ETag по пустякам. Например, не пересобирайте файл «на месте» без изменения содержимого — ETag должен отражать контент.
- Для длинного хранения в кэше настраивайте
Cache-Controlи, при необходимости,Vary(например, поAccept-Encoding), чтобы избежать путаницы между сжатыми и несжатыми вариантами.
Важно: диапазоны относятся к конкретному представлению ресурса. Если выдаёте gzipped-версию, диапазон указывает байты сжатого потока. Для медиа обычно избегайте компрессии.
Проверка сценариев с curl
Ниже минимальный набор проверок, который я делаю после настройки:
# 1) Базовая проверка 206
curl -i -H "Range: bytes=0-1023" https://example.org/media/big.mp4
# 2) Диапазон с открытым концом
curl -i -H "Range: bytes=1048576-" https://example.org/media/big.mp4
# 3) Некорректный диапазон (должен быть 416)
curl -i -H "Range: bytes=9999999999-" https://example.org/media/big.mp4
# 4) If-Range с ETag (подставьте реальный ETag)
curl -i -H "If-Range: \"123456789abcdef\"" -H "Range: bytes=0-1023" https://example.org/media/big.mp4
# 5) Повторный запрос — проверяем кэш-хиты (по server headers)
curl -i -H "Range: bytes=0-1048575" https://example.org/video/film.mp4
Проверьте, что первый запрос к проксируемой локации собрался из слайсов и последующие приходят из кэша (по диагностическим заголовкам, которые вы добавляете сами).
Чеклист для продакшена
- Nginx статик:
etag on,sendfile on,aio threads, порогdirectio,max_ranges 1, корректныеCache-Control. - Nginx прокси:
slice,proxy_cache_keyс$slice_range,proxy_set_header Range $slice_range,proxy_force_ranges on, ограничениеmax_ranges. - Apache статик:
EnableSendfile On,EnableMMAP Off,MaxRanges 1,FileETag MTime Size, заголовокAccept-Ranges. - Apache mod_cache:
CacheIgnoreRange Onдля стратегии «кэшируем полный объект». - Компрессия: не применять gzip к аудио/видео; аккуратно с Range и сжатием.
- Валидаторы: сильные ETag и консистентность между нодами; сохранение
mtimeпри выкладке. - Тесты: 200/206/416,
If-Range, кэш‑хиты, поведение при перемотке.
Практические советы по выбору размеров и таймаутов
- Размер слайса: начните с 1–4 МБ. Меньше — больше накладных расходов на запросы к origin, больше — хуже «попадание» при перемотке.
- Кэш: под медиакаталог выделите отдельную
keys_zoneи лимит по размеру; следите заinactive, чтобы «редкие» куски не вываливались слишком быстро. - Таймауты: для origin увеличьте
proxy_read_timeoutиsend_timeout, если сеть до хранилища неидеальна. Отсечка по соединениям клиента — по профилю нагрузки. - Грейс: если используете прокси‑кэш с выдачей «устаревшего при ошибке», добавляйте настроенный «stale» для устойчивости к всплескам ошибок origin.
Что сломается, если сделать «не так»
- Медиаплеер зависает при перемотке — вероятно, получаете сжатое тело и диапазон относится к gz‑потоку, а не к байтам файла.
- Постоянные 200 вместо 206 при
If-Range— валидаторы различаются между нодами, ETag слабый или меняется без изменения содержимого. - Бурст запросов на origin при пиковом онлайне — частичные ответы не кэшируются; внедряйте
sliceили стратегию полного кэша с игнором Range на backend. - Неожиданные 416 — клиент просит диапазоны вне размера. Проверьте корректность
Content-Lengthна 200 иContent-Rangeна 206.
Итоги
Частичная отдача файлов — обязаловка для любого медиапроекта и просто крупных загрузок. Чтобы HTTP Range и 206 Partial Content работали без сюрпризов, настройте сервер и кэш согласованно: включите предсказуемые валидаторы, ограничьте мультидиапазоны, продумайте стратегию кэширования (полные объекты либо шардирование с slice), аккуратно обойдитесь с компрессией. После базовой доводки вы получите экономию трафика к origin, стабильную перемотку и отсутствие «битых» докачек — ровно то, что нужно медиа‑сайтам и серверам скачиваний.


