Оптимизация NGINX

Оптимизация веб сервера nginx позволит вам ускорить ваш сайт, сократив время ответа сервера.

· 8 минуты на чтение
Оптимизация NGINX

Nginx это отличный веб-сервер, который уже давно зарекомендовал себя, как надежное решение даже для больших проектов. Несмотря на то, что nginx отлично работает из коробки, рекомендую проделать некоторые настройки по оптимизации, которые уменьшат время ответа сервера.

Не забывайте, что после внесения изменений можно проверять их корректность командой:

nginx -t

А чтобы применить настройки, необходимо перезапустить nginx:

nginx -s reload
Спонсор поста

История изменений

18.07.2022: Добавил информацию о кэшировании на стороне сервера. Провел общий рефакторинг статьи.

Оптимизация работы соединений

Начнем оптимизацию с основных параметров, которые отвечают за количество обрабатываемых соединений и сроки их поддержания.

worker_processes  auto;
worker_priority     -2;
...
events {
    worker_connections  2048;
    multi_accept on;
}
...
http {
    ...
    keepalive_timeout          45;
    reset_timedout_connection  on;
    client_body_timeout        35;
    client_header_timeout      12;
    send_timeout               30;
    ...
    client_body_buffer_size    10K;
    client_header_buffer_size   1k;
    client_max_body_size        8m;
    large_client_header_buffers 4 4k;
    ...
}

worker_processes - Определяет количество рабочих процессов. Обычно, выставляют равному числу ядер, но в новых версиях его лучше устанавливать в auto. По умолчанию 1.

worker_connections - Устанавливает максимальное количество соединений одного рабочего процесса, то есть nginx будет обрабатывать worker_processes * worker_connections, остальные запросы ставить в очередь. Следует выбирать значения от 1024 до 4096. По умолчанию 512.

multi_accept - Позволяет принимать максимально возможное количество соединений. Иначе, процесс nginx за один раз будет принимать только одно новое соединение. По умолчанию off.

keepalive_timeout - Отвечает за максимальное время поддержания keepalive-соединения, в случае, если пользователь по нему ничего не запрашивает. Для современных систем, стоит выставить от 30 до 50. В нашем случае 45. По умолчанию 75.

reset_timedout_connection - Если клиент перестал читать страницу, Nginx будет сбрасывать соединение с ним. По умолчанию off.

client_body_timeout - Ждет выставленное количество секунд тело запроса от клиента, после чего сбрасывает соединение. Таймаут задается только для периода между двумя последовательными операциями чтения, а не для передачи всего тела запроса. Если клиент ничего не передает в течение этого времени, запрос завершается с ошибкой 408 (Request Time-out). По умолчанию 60.

client_header_timeout - Определяет тайм-аут для чтения заголовка запроса клиента. Если клиент не передает весь заголовок в течение этого времени, запрос завершается с ошибкой 408 (Request Time-out).

send_timeout - Если клиент прекратит чтение ответа, Nginx подождет выставленное количество секунд и сбросит соединение. По умолчанию 60.

client_body_buffer_size - Задает размер буфера для чтения тела запроса клиента. Если тело запроса больше буфера, то все тело или только его часть записывается во временный файл.

client_header_buffer_size - Относится к размеру буфера относительно заголовка клиентского запроса.

client_max_body_size - Устанавливает максимально допустимый размер тела запроса клиента, указанный в поле заголовка запроса "Content-Length". Если размер в запросе превышает настроенное значение, клиенту возвращается ошибка 413 (Request Entity Too Large).

large_client_header_buffers - Максимальное количество и размер буферов для больших клиентских заголовков. Строка запроса не может превышать размер одного буфера, иначе клиенту будет возвращена ошибка 414 (Request-URI Too Large).

Сжатие GZIP

Один из самых эффективных методов ускорить ответ от вашего веб-сервера nginx - это включить gzip сжатие. gzip обеспечивает сжатие без потерь, иными словами, исходные данные можно полностью восстановить при распаковке. Он основан на алгоритме DEFLATE, который использует комбинацию алгоритма LZ77 и алгоритма Хаффмана.

Большинство клиентов и серверов поддерживают gzip. Когда gzip-совместимый клиент/веб-браузер запрашивает ресурс у сервера с поддержкой Gzip, сервер сжимает ответ перед отправкой обратно клиенту.

Общая конфигурация Gzip-сжатия может выглядеть следующим образом. Откройте основной файл конфигурации /etc/nginx/nginx.conf и добавьте следующие директивы в блок server.

http {
  ...

    gzip                on;
    gzip_min_length     500;
    gzip_vary           on;
    gzip_proxied        expired no-cache no-store private auth;
    gzip_types          text/plain text/css text/javascript application/javascript application/x-javascript text/xml application/xml application/xml+rss application/json;
    gzip_disable        "msie6";
    gzip_comp_level 6;
    gzip_buffers 16 8k;

  ...
}

gzip включает сжатие.

gzip_min_length устанавливает минимальную длину ответов, для которых будет применяться сжатие. По умолчанию 20, но имеет смысл поставить больше, так как постоянное сжатие приводит к повышению нагрузки на процессоры сервера и клиента.

gzip_vary - позволяет кэшировать как сжатые, так и обычные версии ресурса.

gzip_proxied Разрешает или запрещает сжатие ответа методом gzip для проксированных запросов в зависимости от запроса и ответа. То, что запрос проксированный, определяется на основании наличия поля “Via” в заголовке запроса. В директиве можно указать одновременно несколько параметров.

Параметры gzip_proxied

off запрещает сжатие для всех проксированных запросов, игнорируя остальные параметры;

expired разрешает сжатие, если в заголовке ответа есть поле “Expires” со значением, запрещающим кэширование;

no-cache разрешает сжатие, если в заголовке ответа есть поле “Cache-Control” с параметром “no-cache”;

no-store разрешает сжатие, если в заголовке ответа есть поле “Cache-Control” с параметром “no-store”;

private разрешает сжатие, если в заголовке ответа есть поле “Cache-Control” с параметром “private”;

no_last_modified разрешает сжатие, если в заголовке ответа нет поля “Last-Modified”;

no_etag разрешает сжатие, если в заголовке ответа нет поля “ETag”;

auth разрешает сжатие, если в заголовке запроса есть поле “Authorization”;

any разрешает сжатие для всех проксированных запросов.

gzip_types по умолчанию включено сжатие для ответов типа текст. В данном параметре можно перечислить все необходимые типы ответов.

gzip_disable запрещает для перечисленных параметров заголовка User-Agent сжатие. В данном примере для Internet Explorer 6 сжатие применяться не будет, так как данный браузер не умеет принимать сжатые ответы.

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

Статический контент - это содержимое сайта, которое остается неизменным продолжительное время на всех страницах. Например, это такие файлы, как картинки, CSS и JS файлы.

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

В главный конфигурационный файл Nginx можно добавить следующие директивы, чтобы указать серверу кэшировать статические файлы веб-страницы для более быстрого доступа к ним.

server {
  ...

    # Media
    location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|mp4|ogg|ogv|webm|htc)$ {
        expires 30d;
    }

    # CSS and Js
    location ~* \.(css|js|woff2)$ {
        expires 365d;
    }

  ...
}

В данном примере мы кэшируем медиа-файлы, такие как jpeg, gif, png на 30 дней. А такие файлы как css, js на 365 дней.

Fingerprint
Однако, если ваш дизайн сайта обновится, то пользователи продолжат использовать неактуальную версию css файлов, что может привести к проблемам отображения вашего сайта у клиента.

Самый простой способ побороть эту проблему, это использовать fingerprint файла. То есть, когда файл меняется, вы меняете его название. Делается это обычно добавлением какого-нибудь префикса/суфикса.

Например у нас есть файл стилей style.css, мы можем посчитать для него MD5 хэш и добавить его в название. Тогда у нас получится следуюшее название: style.e626dd36e0085927f334adbe3eb38e7a.css.

При любом изменении файла MD5 хэш должен пересчитываться. Таким образом при изменении файла у него будет другое название, и браузер будет вынужден скачать его в любом случае.

Кэширование на стороне сервера

Если какие-то запросы не часто меняются, то можно закэшировать их на стороне сервера. Тогда Nginx один раз получит результат запроса от вашего приложения, а дальше будет отдавать их другим клиентам.

Перед настройками, нам нужно создать директорию, в которой будут хранится данные кэша:

sudo mkdir -p /var/nginx/cache

Чтобы включить кеширование, нужно прописать несколько директив в основной конфигурации nginx.conf.

http {
    proxy_cache_path /var/nginx/cache levels=1:2 keys_zone=nginxcash:60m max_size=256m inactive=24h;
    proxy_cache_key "$scheme$request_method$host$request_uri";
    proxy_cache_methods GET HEAD;
    proxy_cache_min_uses 2;
}

proxy_cache_path указывает путь в файловой системе. Также этот параметр позволяет задать

Не кэшируйте HTTP-ответы при первом обращении. Используйте proxy_cache_min_uses 2, чтобы кэшировать только те элементы, к которым обращались более одного раза. Таким образом, вы уменьшите нагрузку прокси-кэша на запись и предотвратите заполнение кэша содержимым, к которому редко обращаются.

Ключ кэширования Nginx по умолчанию не очень хорошо работает с сайтами с несколькими поддоменами. Вы можете настроить ключ кэширования, задав proxy_cache_key. В своей конфигурации я использую вот такой ключ:

proxy_cache_key $scheme$host$uri$is_args$args;

Переносим кэш Nginx в RAM

Можно значительно ускорить кэш, если смонтировать его не в файловую систему а в RAM.

Для этого также создаем папку для кэша, можно использовать ту же, но ее нужно очистить от папок. Далее монтируем созданный каталог в RAM с помощью команды tmpfs, выделяя 256 мегабайт под кэш:

sudo mount -t tmpfs -o size=256M tmpfs /var/nginx/cache

Если вам понадобиться отключить RAM-кеш, просто выполните команду:

sudo umount /var/nginx/cache

Чтобы автоматически пересоздать каталог кеша в RAM после перезагрузки, нам нужно обновить файл /etc/fstab. Добавьте в него следующую строку:

tmpfs /var/nginx/cache tmpfs defaults,size=256M 0 0

Оптимизация работы с файлами

http {
  ...
    sendfile      on;
    aio           on;
    tcp_nopush    on;

    open_file_cache max=100000 inactive=20s;
    open_file_cache_valid 45s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;
    ...
}

sendfile позволяет использовать более совершенный системный вызов, который обеспечивает прямую передачу файла, то есть без системных вызовов read + write.

aio включает использование асинхронного обращения к файлам, что избавит от очередей запросов.

tcp_nopush позволит передавать заголовок ответа и начало файла в одном пакете.

open_file_cache по умолчанию выключена. Задает настройку для кэширования информации о файлах, с которыми работает nginx. По умолчанию выключено.

open_file_cache_valid задает время, через которое веб-сервер будет проверять актуальность данных. По умолчанию 60 секунд.

open_file_cache_min_uses задает минимальное число обращений к файлу, чтобы дескриптор файла оставался открытым в кэше.

open_file_cache_errors включает или выключает кэширование ошибок.

Прочие оптимизации

Оптимизация журнала доступа

Ведение журнала очень важно при устранении неполадок и для аудита. Однако, включение ведения журнала снижает производительность сервера, поскольку он регистрирует каждый запрос Nginx, поступающий на сервер.

Есть два варианта решения этой проблемы:

  • полностью отключить ведение журнала доступа
  • использовать буфер перед записью на диск

Отключение ведения журнала выполняется с помощью директивы access_log:

http {
   access_log off;
}

Если необходимо вести журнал доступа, включите буферизацию журнала доступа. Это позволит Nginx буферизировать серию записей журнала и записывать их в файл журнала одновременно, вместо того чтобы выполнять различные операции записи для каждого запроса.

Есть два тригера для записи в файл, которые можно использовать одновременно:

  • если следующая строка лога не помещается в буфер
  • если данные в буфере старше, чем указано в параметре flush.

Чтобы настроить буфер, нам просто нужно добавить параметры buffer и flush с соответствующим значением в директиве access_log:

nginx / {
    ...
    access_log /var/log/nginx/fast_api.log combined buffer=256k flush=10s;
    error_log /var/log/nginx/fast_api.err.log;
}

Таким образом, согласно приведенной выше конфигурации, изначально журналы доступа будут записываться в буфер и сохраняться на диск только тогда, когда размер буфера достигнет 256 КБ или буферизованные данные станут старше 10 секунд.

Используйте HTTP 2

HTTP/2 является преемником сетевого протокола HTTP 1.x. Основным направлением HTTP/2 является сокращение общего времени загрузки веб-страниц, что позволяет оптимизировать производительность. Он также фокусируется на использовании ресурсов сети и сервера, а также на повышении безопасности, поскольку в HTTP/2 шифрование SSL/TLS является обязательным.

Ваш блок HTTPS-сервера должен выглядеть следующим образом:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    ...
}
Рандомный блок

Безопасность

Помимо уменьшения времени отклика веб-сервера, необходимо позаботиться о безопасности. Разберем основные http заголовки, которые могут представлять угрозу.

X-XSS-Protection

Заголовок X-XSS-Protection может предотвратить некоторые XSS-атаки.

Вы можете реализовать защиту XSS, используя три варианта в зависимости от конкретной потребности.

  • X-XSS-Protection: 0; Это полностью отключит фильтр
  • X-XSS-Protection: 1; Это включает фильтр, но очищает только потенциально вредоносные скрипты
  • X-XSS-Protection: 1; mode = block; Это включает фильтр и полностью блокирует страницу.
server {
  ...

  add_header X-XSS-Protection "1; mode=block";

  ...
}

X-Frame-Options

Заголовок X-Frame-Options позволяет снизить уязвимость вашего сайта для clickjacking-атак. Этот заголовок служит инструкцией для браузера не загружать вашу страницу в frame/iframe. Не все браузеры поддерживают этот вариант.

Настроить X-Frame-Options можно тремя способами:

  • DENY: это полностью отключит функции iframe.
  • SAMEORIGIN: iframe может использоваться только кем-то из того же источника.
  • ALLOW-FROM: Это позволит размещать страницы в окнах iframe только с определенных URL-адресов.
server {
  ...

  add_header X-Frame-Options "DENY";

  ...
}

X-Permitted-Cross-Domain-Policies

Аналогично механизму браузеров блокировки стороннего контента Adobe Flash имеет свой. Он регулируется файлами crossdomain.xml сайта, начиная с корневого каталога. Проблема с механизмом в том, что на любом уровне вложенности корневой регулирующий файл (политика безопасности) может быть переопределен. Чтобы избежать таких ситуаций, необходимо задать этот HTTP-заголовок.

Доступно несколько вариантов настройки:

  • none - никакая политика не допускается
  • master-only - разрешить только главную политику
  • all - все позволено
  • by-content-only - Разрешить только определенный тип контента. Пример - XML
  • by-ftp-only - применимо только для FTP-сервера
server {
  ...

  add_header X-Permitted-Cross-Domain-Policies master-only;

  ...
}

Strict-Transport-Security

Заголовок Strict-Transport-Security запрещает использование незащищенного HTTP соединения на сайте, если есть защищенное HTTPS.

server {
  ...

  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

  ...
}

X-Content-Type-Options

Рейтинг наиболее опасных к использованию возможностей браузера возглавляет возможность Internet Explorer «угадывать» тип файла, игнорируя его MIME-тип.

При передаче от сервера к браузеру все файлы имеют тот или иной тип, который прямо указывает на суть содержимого файла. Однако, Internet Explorer имеет встроенный механизм, который позволяет по-содержимому файла переопределить его тип.

Таким образом, обычные текстовые файлы могут быть интерпретированы как JavaScript со всеми вытекающими последствиями. Например, если у вас на сайте запрещена загрузка текстовых файлов с расширениями .js пользователями, то они могут загрузить в виде картинок текстовый файл, содержащий JavaScript-код, который может быть исполнен браузером.

server {
  ...

  add_header X-Content-Type-Options nosniff;

  ...
}

Резюмирую

Вот и все, потратив немного своего времени, вы ускорили загрузку вашего сайта для клиентов, а также немного обезопасили его от некоторого вида атак.

Struchkov Mark
Struchkov Mark
Задавайте вопросы, если что-то осталось не понятным👇