Несколько читателей блога webo.in просили меня выложить конфигурацию связки nginx + Apache, на которой работает сервер. Хотя это и не относится напрямую к теме клиентской оптимизации. Однако, большинству специалистов, занимающихся клиентской оптимизацией, будет интересно узнать о настройке нескольких хостов для выдачи статики и пара других трюков, связанных с балансировкой запросов.
Также я подробно комментирую все настройки конкретно Apache, которые так или иначе относятся к самой оптимизации времени загрузки страниц.
Итак, Apache установлен на localhost на порт 8080, далее я буду приводить части его конфигурационного файла с комментариями, что они означают, и как их можно поменять.
С самого начала включаем gzip для текстовых файлов и favicon:
AddEncoding gzip .gz AddOutputFilterByType DEFLATE text/html AddOutputFilterByType DEFLATE text/xml AddOutputFilterByType DEFLATE image/x-icon
Далее я устанавливаю максимальную степень сжатия (9) для таких файлов и максимальный размер окна (15). Если сервер не такой мощный, то уровень сжатия можно выставить в 1, размер файлов при этом увеличивается примерно на 20%.
DeflateCompressionLevel 9 DeflateWindowSize 15
Мы отключаем сжатие для тех браузеров, у которых проблемы с его распознаванием (такие случаи уже достаточно хорошо описаны и задокументированы):
BrowserMatch ^Mozilla/4 gzip-only-text/html BrowserMatch ^Mozilla/4\.0[678] no-gzip BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
Теперь мы заботимся о сжатии для CSS/JS-файлов (эта техника хорошо описана в соответствующей статье). Для всех файлов (.css|.js|.xml
)
RewriteEngine ON RewriteCond %{HTTP:Accept-encoding} !gzip [OR] RewriteCond %{HTTP_USER_AGENT} Safari [OR] RewriteCond %{HTTP_USER_AGENT} Konqueror RewriteRule ^(.*)\.(css|js|xml)\.gz$ $1.$2 [QSA,L]
Здесь мы выставляем соответствующий MIME-тип для сжатых стилей и скриптов:
<FilesMatch .*\.js\.gz$> ForceType text/javascript Header set Content-Encoding: gzip </FilesMatch> <FilesMatch .*\.css\.gz$> ForceType text/css Header set Content-Encoding: gzip </FilesMatch>
Если серверных ресурсов не так жалко, то можно архивировать CSS/JS файлы и стандартным для Apache образом примерно так:
<FilesMatch .*\.(js|css)$> SetOutputFilter DEFLATE BrowserMatch ^Safari no-gzip BrowserMatch ^Konqueror no-gzip </FilesMatch>
Далее пара небольших фиксов для улучшения работы с различными локальными прокси-серверами: мы указываем на необходимость передачи заголовка User-Agent
(потому что по нему принимается решение о сжатии) и заголовка Cache-Control
(чтобы кеширование не осуществлялось на прокси-сервере, а файл передавался полностью пользователю). Цитата с w3.org:
[Этот заголовок] указывает на то, что все ответное сообщение полностью или его часть предназначена для отдельного пользователя и не должна быть сохранена в каком-то промежуточном кеше
Ну и сам сами правила, собственно:
Header append Vary User-Agent Header append Cache-Control private
Включаем кеширование для всех файлов сроком на 10 лет:
ExpiresActive On ExpiresDefault "access plus 10 years"
Если вы хотите конкретно для HTML-файлов его выключить, то можно поступить следующим образом:
<FilesMatch .*\.(html|php)$> ExpiresActive Off </FilesMatch>
Для корневой директории выставляем ETag и корректируем кеширование в Apache для работы с SSI (через XBitHack
):
<Directory "RootDir/"> FileETag MTime Size XBitHack full </Directory>
Для Личного кабинета кеширование у меня отключено:
<Directory "RootDir/my/"> ExpiresActive Off XBitHack off </Directory>
В принципе, это все, что нам нужно от Apache, но у меня была еще одна небольшая задача/проблема на сервере.
Суть проблемы в том, что на webo.in есть уникальная страница для каждого проверенного сайта. Находится она по адресу https://webo.in/urls/{адрес_сайта}/
. Однако, в некоторых случаях сама страница может физически не существовать (фактически, это статичный HTML). Поэтому нужно при отсутствии страницы перенаправлять запросы на динамический адрес, чтобы он выбрал данные из базы и создал сам HTML-файл.
Более того, именно такие адреса прописаны в sitemap.xml
, поэтому поисковый трафик идет именно на них. В связи с чем нам жизненно необходимо, чтобы они работали (и работали максимально быстро). Для этого в конфигурации Apache проверяется, существует ли физический файл, если нет и он запрашивается из нужной нам директории, то такой запрос нужно адресовать динамическому серверному скрипту:
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} ^/urls/(.*)/ RewriteCond %{REQUEST_FILENAME} !index.s?html RewriteRule (.*) https://webo.in/check/?url=%1 [QSA,L]
Таким образом, каждая страница, которой физически нет на сервере, но она заявлена в каких-то внешних источниках, появляется при первом запросе к ней.
Поскольку реальный IP пользователя у нас будет в другой переменной окружения (из-за nginx), нам нужно изменить формат логов. У меня это сделано следующим образом:
LogFormat "%{X-Real-IP}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" combined
Откуда берется переменная X-Real-IP
речь пойдет чуть ниже.
Для начала пробросим все запросы к nginx на Apache:
server { listen webo.in; server_name webo.in www.webo.in; location / { proxy_pass http://127.0.0.1:8080/; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
Apache у нас поднят на порту 8080, туда мы направляем все запросы к хосту webo.in
, которые принял nginx. Дополнительно мы выставляем переменные X-Real-IP
(для нормального ведения логов Apache, иначе у нас будут одни записи типа 127.0.0.1 вместо IP пользователей) и Host
(так как Apache занимается не одним хостом, то знать, на какой хост пришли запросы ему тоже нужно).
теперь более интересная часть. Нам нужно сделать несколько (у меня это 1) хостов для выдачи статики (картинок и др.) прямо с nginx (чтобы лишний раз не пробрасывать ее на Apache).
server { listen i.webo.in; # здесь можно вписать любое число виртуальных хостов, которые # мы хотим подключить для выдачи физически одинаковой статики server_name i.webo.in; # далее добавляются кеширующие заголовки для всех картинок, # лежащих прямо в корне i.webo.in location ~* /[^/]*\.(jpg|jpeg|gif|png)$ { access_log off; expires 10y; add_header Last-Modified: $date_gmt; } # здесь делаем то же самое и для папки urls, которая # обслуживает запросы типа i.webo.in/urls/webo.in.png # Естественный вопрос: почему нельзя сразу для всех файлов # прописать такие правила? Ответ: для ограничения возможных # адресов, которые обслуживает nginx (т.е. в такой # конфигурации он отвечает только за корень и urls) location ~* /urls/[^/]*\.(jpg|jpeg|gif|png)$ { root DocRoot/i/urls; access_log off; expires 10y; add_header Last-Modified: $date_gmt; } # далее раздел, занимающийся опять-таки "мягким" серверным # кешированием: если картинки для сайта нет, то запрос # направляется серверному скрипту if (!-f $request_filename) { rewrite ^/urls/(.*)\.png$ https://webo.in/i/image.php?url=$1 break; } # ну и дальше пара строк для обработки 404/403-ошибок error_page 404 https://webo.in/404.shtml; error_page 403 https://webo.in/404.shtml; }
Комментарий от
Опыт работы с nginx подсказывает
expires 10yзаменить на
expires maxИногда (но все же бывает) сервер может спросить установку даты и времени и тогда отдача контента с заголовком
Expires
на 10 лет не поможет, на помощь приходит параметрmax
, который задаёт время 31 декабря 2037 23:55:55 GMT для строкиExpires
и 10 лет для строкиCache-Control
.
В общем, это все. На выходе мы получаем 2 хоста: один чисто под nginx, второй на связке Apache + nginx. Дополнительно осуществляем набор действий по клиентской оптимизации и некоторому серверному кешированию.