Раздача статики: root, alias, try_files, sendfile

Рейтинг: 49% · 10 голосов
Самый подробный курс по Nginx - от первого конфига до production API-gateway. Архитектура и event loop, контексты и location, виртуальные хосты, статика, reverse proxy и upstream-балансировка, кэширование, HTTPS и TLS (post-quantum), HTTP/2 и HTTP/3 QUIC, безопасность и rate limiting, realip, JSON-логи и сквозная трассировка, метрики, тюнинг и траблшутинг. Плюс OpenResty (Lua, фазы, JWT, динамика) и Angie (нативные Prometheus, ACME, dynamic upstream). На основе эталонного gateway и лучших практик. Для новичков и SRE/DevOps. Актуально на 2026 (nginx 1.30/1.31, Angie, ingress-nginx EOL -> Gateway API).
Ответить
Аватара пользователя
Igor_NgINX
Сообщения: 44
Зарегистрирован: 11 май 2026, 05:31

Раздача статики: root, alias, try_files, sendfile

Сообщение Igor_NgINX »

Оглавление курса (44)
  1. Что такое Nginx и почему он захватил веб
  2. Установка Nginx, Angie и OpenResty: пакеты, версии, Docker
  3. Первый конфиг с нуля: минимальный сервер за 5 минут
  4. Архитектура процессов: master, worker и event loop
  5. Анатомия конфигурации: контексты, наследование и модульность
  6. Виртуальные хосты: server и server_name
  7. Директива location: матчинг и приоритеты по шагам
  8. Раздача статики: root, alias, try_files, sendfile (вы здесь)
  9. Переменные, map и блок if
  10. rewrite, return и канонизация URL
  11. Работа с заголовками: add_header и proxy_set_header
  12. Модель доверия прокси-заголовков: realip и защита от спуфинга
  13. Сжатие: gzip, brotli и zstd
  14. Reverse proxy: proxy_pass и проброс запроса
  15. upstream и балансировка нагрузки
  16. Таймауты, повторы и устойчивость прокси
  17. FastCGI и PHP: nginx + PHP-FPM (и uwsgi/scgi)
  18. WebSocket, gRPC и стриминг через Nginx
  19. Кэширование ответов: proxy_cache и микрокэш
  20. HTTPS и TLS: сертификаты, протоколы, шифры (2026)
  21. Let's Encrypt и ACME: certbot, acme.sh, нативный ACME Angie
  22. HTTP/2 и HTTP/3 (QUIC) в 2026
  23. Безопасность: заголовки, скрытие версии, ограничения
  24. Rate limiting и защита от перегрузки
  25. Контроль доступа: allow/deny, auth_basic, auth_request и mTLS
  26. Логирование: access_log, форматы и структурированный JSON
  27. Сквозная трассировка запросов и OpenTelemetry
  28. Метрики и мониторинг Nginx
  29. Производительность и тюнинг под нагрузку
  30. Траблшутинг: 502, 504, 403, 404 и debug-лог
  31. OpenResty: Nginx как платформа на LuaJIT
  32. Фазы обработки запроса и Lua-хуки
  33. Lua API: ngx.*, shared dict, cosocket и lua-resty-core
  34. Практика OpenResty: авторизация, JWT, кэш, Redis
  35. Angie: современный форк Nginx 2026
  36. Динамическая конфигурация и service discovery
  37. Nginx как API-gateway и Kubernetes Gateway API
  38. Динамические модули, njs и WAF
  39. Stream-модуль: проксирование TCP и UDP
  40. Деплой, перезагрузка и конфиг как код
  41. Сценарий: статика, SPA и кэширование за CDN
  42. Сценарий: микросервисный gateway по образцу ngx-trace-gateway
  43. Капстоун: собираем production-gateway с нуля
  44. Чек-лист безопасности, CVE 2026 и аудит конфигурации
Любой веб-проект на 80 процентов состоит из статики: HTML, картинки, шрифты, собранные бандлы JS и CSS. И именно тут nginx раскрывается в полную силу - отдавать файлы с диска он умеет так, как не умеет почти никто. Но ровно здесь же новички спотыкаются чаще всего: то alias со слешем превращает корректный путь в 404, то SPA отдаёт белый экран при перезагрузке страницы, то сервер дико грузит диск, потому что каждый запрос открывает файл заново. Этот урок - про nginx статику без боли: где жить файлам (root против alias), как искать их (try_files), как отдавать быстро (sendfile, tcp_nopush, tcp_nodelay, open_file_cache) и как не сломать одностраничное приложение.

Разберём механику, а не рецепты, чтобы ты понимал, ПОЧЕМУ конфиг работает именно так, и мог чинить чужие конфиги по симптому.

Где лежат файлы: nginx root и alias

Вся nginx раздача файлов начинается с того, как директива маппит URI в путь на диске. Есть ровно два способа: root и alias. Они выглядят похоже, но склеивают путь по-разному, и в этом весь подвох.

root - это база, к которой URI приклеивается целиком. Полный путь к файлу = root плюс весь $uri.

Код: Выделить всё

server {
    root /var/www/site;

    location /static/ {
        # запрос /static/css/app.css
        # ищется файл /var/www/site/static/css/app.css
        # root + ПОЛНЫЙ uri (включая /static/)
    }
}
alias - это замена части пути, совпавшей с префиксом location. Совпавший кусок URI отбрасывается, вместо него подставляется alias.

Код: Выделить всё

location /static/ {
    alias /var/www/assets/;
    # запрос /static/css/app.css
    # /static/ заменяется на /var/www/assets/
    # ищется файл /var/www/assets/css/app.css
}
То есть root говорит "добавь URI к этому каталогу", а alias говорит "подмени префикс location вот этим каталогом". root хорош, когда структура на диске повторяет URL. alias нужен, когда URL и каталог называются по-разному (URL /static/, а на диске /assets/).

Изображение

Классическая грабля nginx root alias: слеш в конце

Это самая частая ошибка во всём nginx, и она стоит часов отладки. Правило простое, но контринтуитивное:
Если в location есть завершающий слеш, то и в alias он ОБЯЗАН быть. Несоответствие слешей ломает склейку пути.
Смотри, как ломается. Location со слешем, alias без слеша:

Код: Выделить всё

# СЛОМАНО
location /static/ {
    alias /var/www/assets;   # нет слеша на конце!
}
# запрос /static/app.css
# nginx отбрасывает "/static/" и подставляет "/var/www/assets"
# к остатку "app.css" -> получается "/var/www/assetsapp.css"
# файла нет -> 404, хотя руками всё на месте
Видишь склейку assets + app.css = assetsapp.css? Слеш потерялся при стыковке. Правильно - слеши с обеих сторон:

Код: Выделить всё

# ПРАВИЛЬНО
location /static/ {
    alias /var/www/assets/;
}
# /static/app.css -> /var/www/assets/app.css
С root такой проблемы нет в принципе: он приклеивает URI как есть, лишних склеек не происходит. Поэтому общий совет: по умолчанию используй root, держи его на уровне server, чтобы все location резолвили файлы от одной предсказуемой базы (как в эталонном http_server.conf: root в server, а location только уточняют). alias бери осознанно, когда без него никак, и тогда трижды проверь слеши.

Вторая мина alias - его нельзя смешивать с регэксп-location без захвата группы, и про неё ниже, в разделе про try_files.

index и try_files: как nginx ищет файл

index срабатывает, когда запрос приходит на каталог (URI заканчивается слешем). Директива перебирает имена по списку и отдаёт первый существующий файл:

Код: Выделить всё

index index.html index.htm;
# запрос на /docs/ -> пробуем /docs/index.html, потом /docs/index.htm
Но настоящая рабочая лошадь - это try_files. Она берёт список путей, проверяет их по очереди на диске и отдаёт ПЕРВЫЙ существующий. Последний аргумент - это фолбэк: либо внутренний редирект на URI, либо именованный @location, либо HTTP-код.

Код: Выделить всё

location / {
    try_files $uri $uri/ =404;
}
# 1. есть файл по $uri? отдаём его
# 2. есть каталог $uri/ ? обрабатываем как каталог (сработает index)
# 3. ничего нет -> отдаём 404
Три варианта последнего аргумента, и разница принципиальна:
  • =404 (или любой код) - честно вернуть ошибку. Для каталогов со статикой это правильно: если файла нет, его и нет.
  • /index.html - внутренний редирект на конкретный URI. Это и есть паттерн SPA.
  • @fallback - передать в именованную локацию, где обычно стоит proxy_pass на бэкенд. Так строят гибрид "статика с диска, иначе в приложение".

Код: Выделить всё

location / {
    try_files $uri $uri/ @backend;
}
location @backend {
    proxy_pass http://app_upstream;
}
Важно про производительность: каждый аргумент try_files - это обращение к файловой системе (stat). Без кэша дескрипторов это лишний I/O на каждый запрос, поэтому try_files и open_file_cache - неразлучная пара (об open_file_cache - ниже).

Паттерн nginx SPA: try_files и /index.html

Одностраничные приложения (React, Vue, Angular, Svelte) роутятся на клиенте. Сервер физически не имеет файла /users/42/profile - этот путь существует только в JS-роутере браузера. Если пользователь обновит страницу на таком URL, nginx по умолчанию вернёт 404. Лечится это одной строкой:

Код: Выделить всё

location / {
    try_files $uri $uri/ /index.html;
}
Логика: если запрошенный путь - реальный файл (бандл /assets/main.a3f2.js) или каталог, отдаём его напрямую. Если файла нет (это "виртуальный" маршрут SPA) - отдаём /index.html, и дальше клиентский роутер сам разберётся, что показать. Так глубокие ссылки и F5 перестают ломаться - это и есть ядро nginx spa.

Но в этом паттерне есть тонкая ловушка с кэшем. Хэшированные ассеты (main.a3f2c1.js) можно и нужно кэшировать агрессивно - их имя меняется при каждой сборке. А вот сам index.html кэшировать в браузере НЕЛЬЗЯ, иначе пользователь застрянет на старой версии приложения и будет тянуть несуществующие бандлы. Поэтому ассеты и index.html разводят по разным location:

Код: Выделить всё

# хэшированная статика - кэшируем надолго (как expires 30d в эталоне)
location ~* \.(?:js|css|woff2|png|jpe?g|svg|webp|ico)$ {
    expires 30d;
    add_header Cache-Control "public, immutable";
    try_files $uri =404;     # тут НЕ фолбэк на index.html!
    access_log off;
}

# всё остальное - в SPA-фолбэк
location / {
    try_files $uri $uri/ /index.html;
}

# index.html - запретить кэш браузера
location = /index.html {
    add_header Cache-Control "no-cache";
}
Обрати внимание: в location со статикой фолбэк именно =404, а не /index.html. Иначе при опечатке в имени бандла браузер получит HTML вместо JS, в консоли будет загадочное "Unexpected token <", и ты полдня будешь искать призрак. Это прямой совет из практики: для статических ассетов missing-файл должен честно падать в 404, а не проваливаться в приложение.

autoindex: листинг каталога

Когда запрос пришёл на каталог, а ни одного index-файла нет, по умолчанию nginx вернёт 403 Forbidden. Директива autoindex on включает генерацию HTML-листинга содержимого:

Код: Выделить всё

location /downloads/ {
    autoindex on;
    autoindex_exact_size off;   # размеры в КБ/МБ, а не в байтах
    autoindex_localtime on;     # время файлов в локальной зоне
}
Удобно для внутренних файловых зеркал и репозиториев. На публичном проде autoindex почти всегда off (как в эталоне) - иначе ты отдаёшь наружу карту своих файлов, а это и утечка структуры, и помощь сканерам. Включай точечно и только там, где листинг - это фича.

Эффективная nginx раздача файлов: sendfile, tcp_nopush, tcp_nodelay

Теперь про скорость. Эти три директивы из эталонного global_tcp.conf отвечают за то, как байты файла уходят в сеть. Работают они в связке, и понимать их надо вместе.

sendfile on - ключевая оптимизация nginx sendfile. Обычная отдача файла - это цикл: ядро читает блок файла в буфер nginx (копия 1), nginx копирует его в сокет (копия 2), ядро отправляет. sendfile - это системный вызов, который передаёт данные из файла напрямую в сокет внутри ядра, минуя пользовательское пространство. Меньше копирований, меньше переключений контекста, ниже нагрузка на CPU. На статике это даёт ощутимый прирост.

tcp_nopush on (работает ТОЛЬКО при sendfile on) - включает опцию TCP_CORK. Она говорит ядру: "не отправляй пакеты, пока не наберётся полный сегмент". Без неё HTTP-заголовки уходят в одном маленьком пакете, а тело файла - следующими. С tcp_nopush заголовки и начало файла склеиваются в один полный пакет - меньше пакетов в сети, выше эффективность.

tcp_nodelay on - отключает алгоритм Нейгла (TCP_NODELAY). Нейгл придерживает мелкие пакеты, копя их в более крупный, и добавляет задержку до 200 мс. Для интерактива (API, keepalive) эта задержка - зло.

Кажется, что nopush (придержи) и nodelay (не придерживай) противоречат друг другу. Но nginx умён: он применяет CORK на основной массе файла, а на ПОСЛЕДНЕМ пакете снимает CORK и включает nodelay, чтобы хвост ушёл немедленно без 200 мс ожидания. Поэтому канонический набор - все три включены, как в эталоне:

Код: Выделить всё

# global_tcp.conf
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_time 1h;
types_hash_max_size 4096;
open_file_cache: кэш дескрипторов

При каждом запросе nginx делает open(), stat() и в конце close() для файла. Под нагрузкой на одни и те же популярные файлы это тысячи лишних системных вызовов в секунду. open_file_cache (из эталонного global_cache.conf) кэширует метаданные: открытые дескрипторы, размеры, время модификации, и даже факт отсутствия файла и ошибки доступа.

Код: Выделить всё

# global_cache.conf
open_file_cache          max=2000 inactive=20s;
open_file_cache_valid    60s;
open_file_cache_min_uses 5;
open_file_cache_errors   on;
Разбор параметров - тут важна каждая цифра:
  • max=2000 - максимум элементов в кэше; при переполнении вытесняются наименее используемые (LRU).
  • inactive=20s - если к закэшированному файлу не обращались 20 секунд, элемент выкидывается. Это НЕ срок валидности, а таймаут простоя.
  • open_file_cache_valid 60s - раз в 60 секунд nginx перепроверяет на диске, актуальна ли запись (не изменился ли, не удалён ли файл).
  • open_file_cache_min_uses 5 - файл попадает в кэш только после 5 обращений за период inactive. Защита от засорения кэша одноразовыми запросами.
  • open_file_cache_errors on - кэшировать и негативный результат: если файла нет, запоминаем это и не дёргаем диск повторно. Сильно помогает при try_files, где много заведомо несуществующих путей (тот же SPA, где почти каждый $uri - промах).
Важная оговорка: open_file_cache кэширует дескрипторы и метаданные, а не содержимое. Это не замена прокси-кэшу. И помни про valid: если ты часто обновляешь файлы на лету, слишком большой valid заставит nginx какое-то время отдавать устаревшую версию.

Заголовки кэша браузера: expires и Cache-Control

Самый дешёвый способ ускорить сайт - заставить браузер не запрашивать статику повторно. Директива expires проставляет заголовки Expires и Cache-Control с нужным TTL. Эталонный паттерн - expires 30d плюс public для статики:

Код: Выделить всё

location ~* \.(?:css|js|woff2|png|jpe?g|gif|svg|webp|ico)$ {
    expires 30d;
    add_header Cache-Control "public";
    access_log off;
    log_not_found off;
}
expires 30d говорит браузеру: "храни этот файл месяц, не спрашивай меня снова". Cache-Control public разрешает кэшировать и промежуточным прокси/CDN. access_log off на статике снижает мусор в логах и нагрузку на диск. Для хэшированных бандлов можно добавить immutable - тогда браузер не будет даже условных revalidate-запросов. А вот для часто меняющихся файлов 30 дней - перебор: завязывай TTL на стратегию версионирования (хэш в имени = можно вечно, фиксированное имя = коротко или no-cache).

Range-запросы и большие файлы

Когда отдаёшь видео, дистрибутивы, большие архивы - в дело вступают Range-запросы (HTTP 206 Partial Content). Браузер или плеер просит "дай байты с 1000000 по 2000000", чтобы перематывать видео и докачивать прерванное. nginx поддерживает Range для статики из коробки - он анонсирует Accept-Ranges: bytes и корректно отдаёт куски через sendfile. Тебе ничего настраивать не надо, главное - не сломать это самому: некоторые цепочки фильтров (например, gzip на лету) убивают Range, поэтому большие бинарники не сжимай gzip-ом (они и не сжимаются толком), оставь Range работать. Для совсем больших файлов следи за тем, чтобы отдача шла именно с диска через sendfile, а не буферизовалась в память.

Грабли try_files и alias вместе

Отдельная мина - сочетание try_files с alias. Исторически try_files внутри location с alias склеивает путь некорректно (баг тянется годами, см. nginx trac ticket 97). Симптом: фолбэк-аргумент резолвится не от alias, а от root, и ты получаешь 404 на ровном месте. Лечение - либо вообще не мешать try_files с alias, а перейти на root, либо в регэксп-location использовать alias с захватом группы:

Код: Выделить всё

# рабочий обход для regex-location + alias
location ~ ^/files/(?<rest>.*)$ {
    alias /var/www/storage/$rest;
    try_files $uri =404;
}
Практический вывод: если тебе нужен try_files с фолбэком - почти всегда проще организовать раздачу через root, и грабли исчезнут сами. alias оставь для простых "подменили префикс и отдали файл" без хитрых фолбэков.

Мини-лаба: собери раздачу статики и SPA руками

Повтори по шагам на тестовом nginx (или в Docker openresty/openresty, как в эталоне).
  • 1. Создай каталог: mkdir -p /srv/spa/assets, положи туда index.html (с любым текстом) и assets/app.js.
  • 2. Напиши server с глобальными sendfile/tcp_nopush/tcp_nodelay и open_file_cache:

Код: Выделить всё

server {
    listen 8081;
    root /srv/spa;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    open_file_cache max=1000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_errors on;

    location ~* \.(?:js|css|png|svg|woff2)$ {
        expires 30d;
        add_header Cache-Control "public";
        try_files $uri =404;
        access_log off;
    }

    location / {
        try_files $uri $uri/ /index.html;
    }
}
  • 3. Проверь конфиг: nginx -t, перезагрузи: nginx -s reload.
  • 4. curl -i http://127.0.0.1:8081/assets/app.js - убедись, что есть Expires и Cache-Control: public, Accept-Ranges: bytes.
  • 5. curl -i http://127.0.0.1:8081/users/42 - это виртуальный SPA-маршрут, файла нет. Должен вернуться index.html с кодом 200 (а не 404). Это работает фолбэк.
  • 6. curl -i http://127.0.0.1:8081/assets/missing.js - несуществующий ассет. Должен быть честный 404, а НЕ index.html (проверка, что =404 на месте).
  • 7. Сломай намеренно: добавь location /static/ { alias /srv/spa; } (без слеша), запроси /static/index.html, поймай 404 и почувствуй грабли склейки на своей шкуре. Потом добавь слеш и убедись, что починилось.
Контрольные вопросы
  • 1. В чём разница склейки пути у root и alias и почему alias без завершающего слеша ломает раздачу при location со слешем?
  • 2. Что вернёт try_files $uri $uri/ /index.html на запрос к несуществующему виртуальному маршруту SPA, и почему для location со статикой фолбэк должен быть =404, а не /index.html?
  • 3. Почему sendfile, tcp_nopush и tcp_nodelay включают вместе, хотя nopush и nodelay звучат противоположно? Что nginx делает с последним пакетом?
  • 4. Что именно кэширует open_file_cache, зачем нужен open_file_cache_errors on и почему это особенно полезно с try_files?
Итог

Раздача статики - это фундамент, и он держится на четырёх вещах. Где искать файл (root по умолчанию, alias осознанно и со слешами), как искать (try_files с правильным последним аргументом: =404 для ассетов, /index.html для SPA, @location для гибрида), как отдавать быстро (sendfile + tcp_nopush + tcp_nodelay + open_file_cache) и как разгрузить себя за счёт браузера (expires 30d, Cache-Control public для хэшированной статики). Запомни две главные грабли - слеш у alias и фолбэк на index.html там, где должен быть 404 - и ты обойдёшь 90 процентов чужих ошибок в nginx-конфигах статики.
👍2 ❤️4 🔥1 😄 🤔
Аватара пользователя
msisinni
Сообщения: 1
Зарегистрирован: 14 май 2026, 17:37

Re: Раздача статики: root, alias, try_files, sendfile

Сообщение msisinni »

Блин, та самая ошибка с alias без слеша. Полдня вчера ловил 404, оказалось ровно эта склейка assetsapp.css. Спасибо, теперь понятно почему.
👍 ❤️2 🔥1 😄 🤔
Аватара пользователя
ProxmoxChan
Сообщения: 1
Зарегистрирован: 24 май 2026, 06:53

Re: Раздача статики: root, alias, try_files, sendfile

Сообщение ProxmoxChan »

Вопрос: а если у меня SPA и при этом часть путей реально проксируется на api, как совместить try_files /index.html и фолбэк на @backend? Через два разных location по /api/ или как лучше?
👍 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
Директива location: матчинг и приоритеты по шагам
Следующая глава →
Переменные, map и блок if

Все главы курса «Nginx профессионально: от первого конфига до API-gateway на OpenResty и Angie»

Поделиться темой: ✈ Telegram VK
Похожие запросы: Ошибки nginx 403, 404, 504: диагностикаРаздача статики и SPA на nginxДиректива location в nginx: матчинг и приоритетыnginx и PHP-FPM: настройка FastCGIВиртуальные хосты nginx: server_name

Вернуться в «Nginx профессионально: от первого конфига до API-gateway на OpenResty и Angie»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость