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

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

· 10 минуты на чтение
Оптимизация 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 — определяет количество рабочих процессов. Обычно задается равным числу ядер процессора, но в последних версиях Nginx рекомендуется использовать значение auto, что позволяет серверу автоматически выбрать оптимальное количество процессов. По умолчанию — 1.

worker_connections — задает максимальное количество соединений, которое может обслуживать один рабочий процесс. Общая способность сервера обрабатывать запросы — это произведение worker_processes * worker_connections. Остальные запросы будут поставлены в очередь. Рекомендуемые значения — от 1024 до 4096. По умолчанию — 512.

multi_accept — позволяет рабочему процессу принимать несколько новых соединений одновременно. По умолчанию — off.

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

reset_timedout_connection — если клиент перестал читать страницу, Nginx автоматически завершит соединение. По умолчанию — off.

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

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

send_timeout — если клиент прекратит чтение ответа, сервер дождется установленное время и затем закроет соединение. По умолчанию — 60 секунд.

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

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

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

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

Сжатие GZIP

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

Большинство современных клиентов и серверов поддерживают GZIP. Когда браузер или другой клиент, совместимый с GZIP, запрашивает ресурс у сервера с поддержкой GZIP, сервер сжимает ответ перед отправкой. Это позволяет значительно уменьшить объем передаваемых данных и ускорить загрузку страниц.

Пример конфигурации для включения GZIP-сжатия в Nginx. Откройте основной файл конфигурации /etc/nginx/nginx.conf и добавьте следующие директивы в блок http:

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 на сервере.

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

gzip_vary — добавляет заголовок Vary: Accept-Encoding в ответ сервера. Это позволяет кэшировать как сжатые, так и несжатые версии одного ресурса в зависимости от того, поддерживает ли клиент GZIP-сжатие.

gzip_proxied — определяет, нужно ли сжимать ответы для проксированных запросов в зависимости от их содержимого. Запрос считается проксированным, если в его заголовке присутствует поле 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 — задает список типов MIME, для которых будет применяться сжатие. По умолчанию GZIP включен для ответов типа text/*. В данном примере сжатие включено для текстовых файлов, CSS, JavaScript и JSON.

gzip_disable — отключает сжатие для определенных клиентов. В данном примере сжатие отключено для браузера Internet Explorer 6 (определяется по значению User-Agent), так как этот браузер не поддерживает работу с GZIP-ответами.

gzip_comp_level — устанавливает уровень сжатия. Рекомендуемое значение — 6, так как оно обеспечивает баланс между скоростью сжатия и эффективностью (по умолчанию уровень сжатия равен 1, а максимальный — 9).

gzip_buffers — задает количество и размер буферов для хранения сжатых данных перед отправкой клиенту. В данном случае установлено 16 буферов по 8К.

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

Статический контент — это элементы веб-сайта, которые остаются неизменными на протяжении долгого времени на всех страницах. К такому контенту относятся файлы изображений, CSS и JavaScript. Поскольку эти файлы редко изменяются, их можно сохранять в кэше браузера пользователя. Это позволяет браузеру загружать локальные копии файлов, а не запрашивать их каждый раз с сервера, что ускоряет работу сайта.

Чтобы настроить кэширование статического контента, можно добавить следующие директивы в основной конфигурационный файл 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;
    }

  ...
}

В этом примере медиа-файлы, такие как изображения и видео, будут кэшироваться на 30 дней, а файлы CSS, JS и шрифты — на 365 дней.

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

Fingerprint — это метод, при котором при каждом изменении файла его название изменяется. Обычно это делается путем добавления префикса или суффикса, сгенерированного на основе хэша файла. Например, для файла стилей style.css можно вычислить его MD5-хэш и добавить его к имени файла. В итоге получится файл с именем style.e626dd36e0085927f334adbe3eb38e7a.css.

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

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

Для повышения производительности можно кэшировать ответы на запросы на стороне 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 — указывает путь к директории, где будут храниться кэшированные данные.

  • Параметр levels задает структуру папок для хранения кэша, keys_zone определяет размер зоны для хранения метаданных (в данном случае 60 мегабайт).
  • max_size=256m задает максимальный размер кэша.
  • inactive=24h указывает, что кэшированные данные, к которым не было обращений в течение 24 часов, будут автоматически удалены.

proxy_cache_key — определяет ключ кэширования. В данном случае используется комбинация схемы запроса, метода, хоста и URI. Этот ключ позволяет разделить кэш для разных поддоменов и запросов.

proxy_cache_methods — указывает, какие HTTP-методы будут кэшироваться (в данном примере — только методы GET и HEAD).

proxy_cache_min_uses — задает минимальное количество обращений к ресурсу перед его кэшированием. Установка значения 2 означает, что кэширование начнется только после второго обращения, что предотвращает кэширование редко используемых данных и снижает нагрузку на запись.

Переносим кэш 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

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

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

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 — включает использование системного вызова sendfile(), который обеспечивает более эффективную передачу файлов. Вместо использования отдельных вызовов для чтения и записи (read и write), sendfile() позволяет напрямую передавать данные из файловой системы в сетевое соединение. Это значительно ускоряет передачу больших файлов, таких как изображения или видео.

aio — активирует асинхронный ввод-вывод (AIO), который позволяет серверу обрабатывать файловые запросы асинхронно. Это сокращает время ожидания при обработке запросов, устраняя блокировки потоков и очереди операций чтения/записи.

tcp_nopush — позволяет передавать заголовок HTTP-ответа и начало файла в одном сетевом пакете, что снижает количество отправляемых пакетов и уменьшает накладные расходы на установку соединения.

open_file_cache — включает кэширование информации о файлах, с которыми работает Nginx. По умолчанию эта опция отключена. Настройка позволяет кэшировать данные о состоянии файлов (например, наличие, размер, права доступа), что снижает нагрузку на файловую систему при частых обращениях к одним и тем же файлам.

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

  • max=100000 — определяет максимальное количество файловых дескрипторов, которые могут быть кэшированы одновременно.
  • inactive=20s — задает время, в течение которого файл будет оставаться в кэше, если к нему не было обращений. После этого периода файл удаляется из кэша, если не был запрошен заново.

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

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

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

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

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

Существует два подхода для решения этой проблемы:

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

Для отключения журнала достаточно добавить следующую директиву в конфигурацию:

http {
   access_log off;
}

Буферизация может быть настроена с использованием двух триггеров:

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

Пример настройки буферизации:

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

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

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

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

Чтобы включить HTTP/2, нужно изменить конфигурацию блока HTTPS-сервера следующим образом:

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

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

Помимо оптимизации времени отклика веб-сервера, необходимо уделить внимание вопросам безопасности. Рассмотрим ключевые HTTP-заголовки, которые помогут защитить ваш сайт от распространенных угроз.

X-XSS-Protection

Заголовок X-XSS-Protection помогает предотвратить XSS-атаки (межсайтовый скриптинг). Он настраивает поведение браузера при обнаружении подозрительных скриптов на странице. В зависимости от вашей потребности можно настроить защиту одним из следующих способов:

  • X-XSS-Protection: 0; — полностью отключает фильтр XSS.
  • 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. Этот заголовок говорит браузеру, как следует обрабатывать страницы, встраиваемые в iframe, и снижает риск того, что злоумышленники могут использовать ваш сайт в таких атаках.

Способы настройки:

  • DENY — полностью запрещает использование страницы в iframe.
  • SAMEORIGIN: — разрешает использование iframe только для страниц с того же домена.
  • ALLOW-FROM — разрешает использование iframe только с определенных URL-адресов.

Пример настройки для запрета использования iframe:

server {
  ...

  add_header X-Frame-Options "DENY";

  ...
}

X-Permitted-Cross-Domain-Policies

Этот заголовок регулирует доступ к файлам через кросс-доменные политики, предотвращая использование небезопасных файлов и ресурсов, таких как Flash. Это важно для сайтов, которые могут работать с Flash или XML-контентом, поскольку механизмы контроля могут быть уязвимы для злоумышленников.

Доступные параметры:

  • 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 (HSTS) обеспечивает использование только защищенных HTTPS-соединений на сайте. Он запрещает использование HTTP-протокола, перенаправляя все запросы на HTTPS, и защищает сайт от атак типа “downgrade” (понижение протокола).

Настройка заголовка HSTS может выглядеть так:

server {
  ...

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

  ...
}

Параметр max-age=31536000 указывает, что браузер должен запоминать настройку в течение года, а includeSubDomains применяет политику ко всем поддоменам.

X-Content-Type-Options

Заголовок X-Content-Type-Options предотвращает браузерам (особенно Internet Explorer) “угадывать” тип содержимого файла на основе его содержимого, а не заголовка MIME. Это важно для защиты от атак, при которых текстовые файлы могут быть интерпретированы как скрипты.

Пример настройки:

server {
  ...

  add_header X-Content-Type-Options nosniff;

  ...
}

Заголовок nosniff запрещает браузеру изменять тип содержимого, что помогает предотвратить возможные XSS-атаки.

Резюмирую

Оптимизация производительности и безопасности сайта — важный шаг для улучшения пользовательского опыта и защиты от потенциальных угроз. Внесенные изменения позволили вам:

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

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

Повысить безопасность с помощью важных HTTP-заголовков, таких как X-XSS-Protection, X-Frame-Options, Strict-Transport-Security, и X-Content-Type-Options, которые помогают защитить сайт от XSS-атак, clickjacking и других угроз.

Кроме того, вы внедрили поддержку HTTP/2, что значительно улучшит производительность за счет параллельной загрузки ресурсов, улучшенной безопасности и оптимизированной работы с сетевыми ресурсами.

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

Теперь ваш сайт готов к работе с большим количеством пользователей, а его безопасность значительно улучшена.

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