В данной статье я расскажу о том, как я заставил свой блог на WordPress летать за счёт грамотного кэширования, сжатия и другой оптимизации серверной и клиентской сторон. На момент написания статьи характеристики VDS следующие:
CPU: 1 x 2GHz
HDD: 10Gb
RAM: 512Mb
OS: Debian 8 x64
apt-get install nginx
# Пользователь и группа, от имени которых будет запущен процесс
user www-data www-data;
# Число воркеров в новых версиях рекомендовано устанавливать в auto
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
events {
# Максимальное количество соединений одного воркера
worker_connections 1024;
# Метод выбора соединений (для FreeBSD будет kqueue)
use epoll;
# Принимать максимально возможное количество соединений
multi_accept on;
}
http {
# Указываем файл с mime-типами и указываем тип данных по-умолчанию
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Отключить вывод версии nginx в ответе
server_tokens off;
# Метод отправки данных sendfile эффективнее чем read+write
sendfile on;
# Ограничивает объём данных, который может передан за один вызов sendfile(). Нужно для исключения ситуации когда одно соединение может целиком захватить воркер
sendfile_max_chunk 128k;
# Отправлять заголовки и и начало файла в одном пакете
tcp_nopush on;
tcp_nodelay on;
# Сбрасывать соединение если клиент перестал читать ответ
reset_timedout_connection on;
# Разрывать соединение по истечению таймаута при получении заголовка и тела запроса
client_header_timeout 3;
client_body_timeout 5;
# Разрывать соединение, если клиент не отвечает в течение 3 секунд
send_timeout 3;
# Задание буфера для заголовка и тела запроса
client_header_buffer_size 2k;
client_body_buffer_size 256k;
# Ограничение на размер тела запроса
client_max_body_size 12m;
# Отключаем лог доступа
access_log off;
# Подключаем дополнительные конфиги
include /etc/nginx/conf.d/*.conf;
}
server {
# Ожидать локального соединения на 81 порту
listen 127.0.0.1:81;
# Корневая директория и индексовый файл
root /var/www/site.ru/public_html;
index index.php;
# Включить gzip-сжатие на выходе бэкенда. В кэш пойдут уже сжатые версии файлов. Здесь происходит сжатие на 9 уровне компрессии. Обратите внимание, среди типов отсутствует text/plain, его мы сжимаем во фронтенде на 1 уровне компрессии, чтобы избежать высокой степени загрузки CPU при отдаче динамических данных
gzip on;
gzip_comp_level 9;
gzip_min_length 512;
gzip_buffers 8 64k;
gzip_types text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/svg+xml;
gzip_proxied any;
# Имя хоста
server_name site.ru www.site.ru;
# Запрет на доступ к скрытым файлам
location ~ /\. {
deny all;
}
# Запрет на доступ к загруженным скриптам
location ~* /(?:uploads|files)/.*\.php$ {
deny all;
}
# Поиск запрашиваемого URI по трем путям
location / {
try_files $uri $uri/ /index.php?$args;
}
# Добавление слэша в конце для запросов */wp-admin
rewrite /wp-admin$ $scheme://$host$uri/ permanent;
location ~ \.php$ {
# При ошибке 404 выдавать страницу, сформированную WordPress
try_files $uri =404;
# При обращении к php передавать его на интерпретацию FPM
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/var/run/php5-fpm.sock;
}
}
server {
# Редирект на HTTPS
listen REAL_IP:80;
server_name site.ru www.site.ru;
return 301 https://$server_name$request_uri;
}
server {
listen 93.170.105.102:443 ssl;
server_name site.ru www.site.ru;
# Устанавливать Keep-Alive соединения с посетителями
keepalive_timeout 60 60;
# Сжатие данных перед отправкой клиенту. Обратите внимание, из типов здесь присутствует только text/plain, для других данных применяется сжатие на бэкенде на более высоком уровне компрессии, после чего эти данные отправляются в кэш. Сделано для того, чтобы избежать нагрузок на CPU при сжатии динамических документов.
gzip on;
gzip_comp_level 1;
gzip_min_length 512;
gzip_buffers 8 64k;
gzip_types text/plain;
gzip_proxied any;
# Отдавать предпочтение шифрам, заданным на сервере
ssl_prefer_server_ciphers on;
# Установка длительности TLS сессии в 2 минуты
ssl_session_cache shared:TLS:2m;
ssl_session_timeout 2m;
# Задание файла, содержащего сертификат сайта и сертификат УЦ
ssl_certificate /etc/ssl/combined.crt;
# Указание закрытого ключа
ssl_certificate_key /etc/ssl/3_site.ru.key;
# Файл с параметрами Диффи-Хеллмана
ssl_dhparam /etc/ssl/dh2048.pem;
# Поддерживаемые протоколы
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
# Наборы шифров, данный набор включает forward secrecy
ssl_ciphers EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA512:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:ECDH+AESGCM:ECDH+AES256:DH+AESGCM:DH+AES256:RSA+AESGCM:!aNULL:!eNULL:!LOW:!RC4:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS;
# Передача Strict-Transport-Secutiry заголовка
add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains';
location / {
# Проксирование на Varnish
proxy_pass http://127.0.0.1:6081/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Port 443;
}
}
service nginx reload
apt-get install varnish
DAEMON_OPTS="-a :6081 -T 127.0.0.1:6082 -f /etc/varnish/default.vcl -S /etc/varnish/secret -s malloc,128m"
# Сообщаем компилятору о том, что используется новая версия VCL 4
vcl 4.0;
# Настройки бэкенда
backend default {
.host = "127.0.0.1";
.port = "81";
}
# Диапазон IP/Хостов, которым разрешено выполнять PURGE-запросы для очистки кэша
acl purge {
"localhost";
"127.0.0.1";
}
# Получение запроса от клиента
sub vcl_recv {
# Разрешить очистку кэша вышеописанному диапазону
if (req.method == "PURGE") {
# Если запрос не из списка, то разворачивать
if (!client.ip ~ purge) {
return(synth(405, "This IP is not allowed to send PURGE requests."));
}
return (purge);
}
# POST-запросы а также страницы с Basic-авторизацией пропускать
if (req.http.Authorization || req.method == "POST") {
return (pass);
}
# Пропускать админку и страницу входа
if (req.url ~ "wp-(login|admin)" || req.url ~ "preview=true") {
return (pass);
}
# Пропускать sitemap и файл robots, у меня sitemap генерируется плагином Google XML Sitemaps
if (req.url ~ "sitemap" || req.url ~ "robots") {
return (pass);
}
# Удаляем cookies, содержащие "has_js" и "__*", добавляемые CloudFlare и Google Analytics, так как Varnish не будет кэшировать запросы, для которых установлены cookies.
set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(_[_a-z]+|has_js)=[^;]*", "");
# Удаление префикса ";" в cookies, если вдруг будет обнаружен
set req.http.Cookie = regsub(req.http.Cookie, "^;\s*", "");
# Удаляем Quant Capital cookies (добавляются некоторыми плагинами)
set req.http.Cookie = regsuball(req.http.Cookie, "__qc.=[^;]+(; )?", "");
# Удаляем wp-settings-1 cookie
set req.http.Cookie = regsuball(req.http.Cookie, "wp-settings-1=[^;]+(; )?", "");
# Удаляем wp-settings-time-1 cookie
set req.http.Cookie = regsuball(req.http.Cookie, "wp-settings-time-1=[^;]+(; )?", "");
# Удаляем wp test cookie
set req.http.Cookie = regsuball(req.http.Cookie, "wordpress_test_cookie=[^;]+(; )?", "");
# Удаляем cookie, состоящие только из пробелов (или вообще пустые)
if (req.http.cookie ~ "^ *$") {
unset req.http.cookie;
}
# Для статических документов удаляем все cookies, пусть себе кэшируются
if (req.url ~ "\.(css|js|png|gif|jp(e)?g|swf|ico|woff|svg|htm|html)") {
unset req.http.cookie;
}
# Если установлены cookies "wordpress_" или "comment_" пропускаем напряиую к бэкенду
if (req.http.Cookie ~ "wordpress_" || req.http.Cookie ~ "comment_") {
return (pass);
}
# Если cookie не найдено, удаляем данный параметр из пришедшего запроса как таковой
if (!req.http.cookie) {
unset req.http.cookie;
}
# Не кэшировать запросы с установленными cookies, это уже не касается WordPress
if (req.http.Authorization || req.http.Cookie) {
# Not cacheable by default
return (pass);
}
# Кэшировать всё остальное
return (hash);
}
sub vcl_pass {
return (fetch);
}
sub vcl_hash {
hash_data(req.url);
return (lookup);
}
# Приём ответа от бэкенда
sub vcl_backend_response {
# Удаляем ненужные заголовки
unset beresp.http.Server;
unset beresp.http.X-Powered-By;
# Не хранить в кэше robots и sitemap
if (bereq.url ~ "sitemap" || bereq.url ~ "robots") {
set beresp.uncacheable = true;
set beresp.ttl = 30s;
return (deliver);
}
# Для статических файлов, которые отдаёт бэкенд...
if (bereq.url ~ "\.(css|js|png|gif|jp(e?)g)|swf|ico|woff|svg|htm|html") {
# Удаляем все куки
unset beresp.http.cookie;
# Устанавливаем срок хранения в кэше - неделю
set beresp.ttl = 7d;
# Устанавливаем заголовки Cache-Control и Expires, сообщая браузеру о том, что эти файлы стоит сохранить в кэше клиента и не нагружать лишниий раз наш сервер
unset beresp.http.Cache-Control;
set beresp.http.Cache-Control = "public, max-age=604800";
set beresp.http.Expires = now + beresp.ttl;
}
# Не кэшировать админку и страницу логина
if (bereq.url ~ "wp-(login|admin)" || bereq.url ~ "preview=true") {
set beresp.uncacheable = true;
set beresp.ttl = 30s;
return (deliver);
}
# Разрешить устанавливать куки только при обращении к этим путям, всё остальное будет резаться
if (!(bereq.url ~ "(wp-login|wp-admin|preview=true)")) {
unset beresp.http.set-cookie;
}
# Не кэшировать результат ответа на POST-запрос или Basic авторизации
if ( bereq.method == "POST" || bereq.http.Authorization ) {
set beresp.uncacheable = true;
set beresp.ttl = 120s;
return (deliver);
}
# Не кэшировать результаты поиска
if ( bereq.url ~ "\?s=" ){
set beresp.uncacheable = true;
set beresp.ttl = 120s;
return (deliver);
}
# Не кэшировать страницы ошибок, только нужные вещи в кэше!
if ( beresp.status != 200 ) {
set beresp.uncacheable = true;
set beresp.ttl = 120s;
return (deliver);
}
# Хранить в кэше всё прочее на протяжении одного дня
set beresp.ttl = 1d;
# Срок жизни кэша после истечения его TTL
set beresp.grace = 30s;
return (deliver);
}
# Действия перед отдачей результата пользователю
sub vcl_deliver {
# Удаляем ненужные заголовки
unset resp.http.X-Powered-By;
unset resp.http.Server;
unset resp.http.Via;
unset resp.http.X-Varnish;
return (deliver);
}
service varnish restart
[Unit]
Description=Varnish HTTP accelerator
[Service]
Type=forking
LimitNOFILE=131072
LimitMEMLOCK=82000
ExecStartPre=/usr/sbin/varnishd -C -f /etc/varnish/default.vcl
ExecStart=/usr/sbin/varnishd -a :6081 -T 127.0.0.1:6082 -f /etc/varnish/default.vcl -S /etc/varnish/secret -s malloc,128m
ExecReload=/usr/share/varnish/reload-vcl
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
service varnish restart
apt-get install php5-fpm php5-mysqlnd
listen = 127.0.0.1:9000
listen = /var/run/php5-fpm.sock
; Динамическое изменение количества воркеров
pm = dynamic
; Максимальное число воркеров, создаются под нагрузкой, не может быть меньше pm.max_spare_servers.
pm.max_children = 10
; Сколько воркеров запускать при старте FPM
pm.start_servers = 1
; Минимальное количество запасных воркеров (остаются в памяти при отсутствии нагрузки)
pm.min_spare_servers = 1
; Максимальное количество запасных воркеров (при простое, остальные неиспользуемые будут завершаться)
pm.max_spare_servers = 3
; Максимальное количество запросов, которые выполняет один воркер, прежде чем перезапуститься
pm.max_requests = 500
upload_max_filesize = 10M
post_max_size = 12M
allow_url_fopen = Off
service php5-fpm reload
apt-get install mariadb-server
# Кэш для работы с ключами и индексами
key_buffer = 64M
# Кэш запросов
query_cache_size = 32M
# Установка MyISAM в качестве стандартного движка
default-storage-engine=MyISAM
# Отключение движка InnoDB
skip-innodb
service mysql restart
К сожалению, не доступен сервер mySQL