Apache2. Установка и настройка. Часть 2 из 2
Настройка поддержки HTTPS
У нас тестовый веб-сервер, нужно выпустить сертификат для локального домена example.net
. Это делается в несколько этапов.
Сначала создаем приватный и публичный ключ Центра Сертификации (CA, Certificate Authority). Потом публичный ключ CA подписываем приватным ключом CA. Таким образом получаем самоподписанный сертификат CA, который надо добавить в доверенные в браузере.
Далее создаем приватный и публичный ключ для домена example.net
. Публичный ключ домена example.net
подписываем приватным ключом CA — получаем сертификат для домена. Чтобы подписать публичный ключ домена — нужно создать запрос на подпись.
# mkdir ~/ca # chdir ~/ca
Для создания пары ключей и подписи будем использовать утилиту openssl
$ openssl команда опции
Создание приватного ключа Центра Сертификации
# openssl genpkey \ > -aes256 \ > -algorithm RSA \ > -pkeyopt rsa_keygen_bits:4096 \ > -pass pass:qwerty \ > -out root-ca.key
Команда genpkey
(заменяет genrsa
, gendh
и gendsa
) означает создание приватного ключа (generate private key). Алгоритм ключа RSA, дина 4096 бит, зашифрован алгоритмом aes256. Опция -pass
задает пароль qwerty
для обеспечения безопасности ключа. Опция -out
указывает на имя файла для сохранения, без этой опции файл будет выведен в стандартный вывод.
Создание самоподписанного корневого сертификата
# openssl req \ > -x509 \ > -new \ > -key root-ca.key \ > -days 7300 \ > -subj "/C=RU/ST=Moscow/L=Moscow/O=Demo Inc/OU=IT Dept/CN=Demo CA" \ > -addext "basicConstraints = critical,CA:TRUE" \ > -addext "subjectKeyIdentifier = hash" \ > -addext "authorityKeyIdentifier = keyid:always,issuer" \ > -addext "keyUsage = critical,keyCertSign,cRLSign" \ > -out root-ca.crt
Enter pass phrase for root-ca.key: qwerty
Команда req
означает запрос на подпись (CSR, Certificate Signing Request). Опция -new
означает создание нового сертификата. Опция -key
указывает на имя файла приватного ключа. Опция -days
— срок действия сертификата.
Просмотр самоподписанного сертификата (не обязательно, просто для проверки, что все правильно)
# openssl x509 -text -noout -in root-ca.crt
Создание файла приватного ключа и файла запроса на подпись (сразу две операции одной командой)
# openssl req -newkey rsa:3072 \ > -keyform PEM \ > -outform PEM \ > -nodes \ > -subj "/C=RU/ST=Moscow/L=Moscow/O=Demo Inc/OU=IT Dept/CN=example.net" \ > -addext "basicConstraints = CA:FALSE" \ > -addext "subjectKeyIdentifier = hash" \ > -addext "keyUsage=digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment" \ > -addext "extendedKeyUsage=serverAuth" \ > -addext "subjectAltName=DNS:example.net,DNS:www.example.net" \ > -keyout example-net.key \ > -out example-net.csr
Просмотр запроса на подпись (не обязательно, просто для проверки, что все правильно)
# openssl req -text -noout -in example-com.csr
Подпись запроса на подпись и получение сертификата на год
# openssl x509 -req \ > -in example-net.csr \ > -CA root-ca.crt \ > -CAkey root-ca.key \ > -CAcreateserial \ > -days 365 \ > -out example-net.crt \ > --copy_extensions=copyall
Certificate request self-signature ok
subject=C = RU, ST = Moscow, L = Moscow, O = Demo Inc, OU = IT Dept, CN = example.net
Enter pass phrase for root-ca.key: qwerty
Просмотр подписанного сертификата (не обязательно, просто для проверки, что все правильно)
# openssl x509 -text -noout -in example-net.crt
Скопируем приватный ключ и сертификат в директорию /etc/ssl
# cp /root/ca/example-net.crt /etc/ssl/certs/ # cp /root/ca/example-net.key /etc/ssl/private/
Активируем модуль ssl_module
# a2enmod ssl # systemctl restart apache2
Посмотрим на файл конфигурации
# nano /etc/apache2/mods-available/ssl.conf
<IfModule ssl_module> # Один или несколько источников для заполнения PRNG # (генератор псевдослучайных чисел) библиотеки SSL SSLRandomSeed startup builtin SSLRandomSeed startup file:/dev/urandom 512 SSLRandomSeed connect builtin SSLRandomSeed connect file:/dev/urandom 512 ## Глобальные настройки SSL ## ## Применяются к серверу в целом и всем виртуальным ## хостам с поддержкой SSL # MIME-типы для скачивания браузерами действительных # и отозванных сертификатов (CRL) AddType application/x-x509-ca-cert .crt AddType application/x-pkcs7-crl .crl # Когда Apache запускается, он должен прочитать файлы сертификатов (см. SSLCertificateFile) # и закрытых ключей (см. SSLCertificateKeyFile) виртуальных хостов с поддержкой SSL. Файлы # закрытого ключа обычно зашифрованы, поэтому модуль ssl должен запросить пароль. SSLPassPhraseDialog exec:/usr/share/apache2/ask-for-passphrase # Во избежание повторяющихся SSL «рукопожатий» для параллельных HTTP-запросов (например, # когда веб-браузер загружает несколько картинок одновременно), должно быть разрешено # SSL кэширование. Если установить значение none — производительность резко снизится. SSLSessionCache shmcb:${APACHE_RUN_DIR}/ssl_scache(512000) # Механизм dbm имеет утечки памяти и не должен использоваться #SSLSessionCache dbm:${APACHE_RUN_DIR}/ssl_scache # Сколько секунд должно пройти до истечения SSLSessionCache. Следует установить 300…900 # секунд. Зависит от того, сколько времени пользователи находятся на веб-сервере. Если # максимальное время 15 минут, тогда значение должно быть 15 * 60 = 900. SSLSessionCacheTimeout 600 # SSL Cipher Suite — наборы SSL шифров, которые могут использовать клиенты. Это алгоритмы, # которые защищающают соединение с помощью шифрования. Разрешаем только безопасные шифры. SSLCipherSuite HIGH:!aNULL #SSLHonorCipherOrder on # Значение on предписывает использовать приоритеты наборов шифров, заданные для сервера. # Вместо того, чтобы браузеры навязывали свои предпочтения. Значение по умолчанию — off. #SSLHonorCipherOrder on # Разрешенные SSL протоколы. Доступные значения — all, SSLv3, TLSv1, TLSv1.1, TLSv1.2. # Протокол SSL v2 больше не поддерживается. Разрешаем все протоколы, за исключением # SSL v3, у которого есть критическая уязвимость. SSLProtocol all -SSLv3 # Значение on разрешает небезопасное повторное согласование с устаревшими браузерами. При # этом соединения SSL будут уязвимы для атаки «man middle». Значение по умолчанию — off. #SSLInsecureRenegotiation on # Индикация имени сервера (SNI или Server Name Indication) — расширение TLS протокола, # которое указывает позволяет серверу определить имя виртуального хоста для установки # защищенного соединения. Это позволяет иметь несколько доменов, привязанных к одному # и тому же ip-адресу и порту. И на каждый домен установить отдельный SSL сертификат. # Значение on запрещает браузерам, которые не поддерживают SNI, доступ к виртуальным # хостам на основе имен. Значение по умолчанию — off. #SSLStrictSNIVHostCheck On </IfModule>
Редактируем файл конфигурации виртуального хоста
# nano /etc/apache2/sites-available/example.net.conf
<VirtualHost *:80 *:443> ServerName example.net ServerAlias www.example.net ServerAdmin webmaster@example.net <IfModule ssl_module> SSLEngine On SSLCertificateFile /etc/ssl/certs/example-net.crt SSLCertificateKeyFile /etc/ssl/private/example-net.key </IfModule> DocumentRoot /var/www/example.net/html DirectoryIndex index.php index.html Options -Indexes <FilesMatch ".+\.ph(ar|p|tml)$"> SetHandler "proxy:unix:/run/php/php8.1-fpm-example-net.sock|fcgi://localhost" </FilesMatch> ErrorLog ${APACHE_LOG_DIR}/example-net-error.log CustomLog ${APACHE_LOG_DIR}/example-net-access.log combined </VirtualHost>
Перезагружаем сервер, чтобы применить новые настройки
# systemctl restart apache2.service
Теперь нужно скопировать корневой сертификат /root/ca/root-ca.crt
на свой компьютер и добавить его в доверенные в браузере. Для примера, в браузере Chrome нужно зайти в настройки, перейти в «Безопасность и конфиденциальность», потом «Безопасность», дальше «Настроить сертификаты». И добавить сертификат в «Доверенные корневые центры сертификации».
Модуль преобразования URL
1. Активация модуля
Модуль rewrite_module
используется для преобразования URL адресов. С его помощью можно настраивать редиректы, изменять URL адреса, блокировать доступ и т.д.
# a2enmod rewrite
На время отладки правил преобразования URL адресов есть смысл включить детальную запись логов для модуля rewrite_module
в файле конфигурации виртуального хоста.
# nano /etc/apache2/sites-available/example.net.conf
<VirtualHost *:80 *:443> ServerName example.net ServerAlias www.example.net ServerAdmin webmaster@example.net <IfModule rewrite_module> # временно, для отладки правил — уровень trace3 для модуля rewrite; # доступны уровни детализации лога от trace1 (min) до trace8 (max) LogLevel warn rewrite:trace3 # формат лога ошибок: только дата-время, название модуля и сообщение ErrorLogFormat "[%t] [%-m] %M" # Разрешить работу модуля rewrite для этого виртуального хоста RewriteEngine On # перенаправление с http на https с кодом 301 Moved Permanently RewriteCond %{HTTPS} =off # RewriteCond %{SERVER_PORT} =80 # RewriteCond %{REQUEST_SCHEME} =http RewriteRule .* https://example.net%{REQUEST_URI} [R=301] </IfModule> <IfModule ssl_module> SSLEngine On SSLCertificateFile /etc/ssl/certs/example-net.crt SSLCertificateKeyFile /etc/ssl/private/example-net.key </IfModule> DocumentRoot /var/www/example.net/html DirectoryIndex index.php index.html Options -Indexes <FilesMatch ".+\.ph(ar|p|tml)$"> SetHandler "proxy:unix:/run/php/php8.1-fpm-example-net.sock|fcgi://localhost" </FilesMatch> ErrorLog ${APACHE_LOG_DIR}/example-net-error.log CustomLog ${APACHE_LOG_DIR}/example-net-access.log combined </VirtualHost>
Перезагружаем сервер, чтобы применить новые настройки
# systemctl restart apache2.service
Набираем в адресной строке браузера http://www.example.net/index.php
и смотрим файл лога
# cat /var/log/apache2/example-net-error.log | grep rewrite […] [rewrite] … […][…] init rewrite engine with requested uri /index.php […] [rewrite] … […][…] applying pattern '.*' to uri '/index.php' […] [rewrite] … […][…] pass through /index.php
2. Директивы модуля
Директива RewriteEngine
включает или выключает механизм перезаписи во время выполнения. Позволяет отключить правила в определенном контексте, а не комментировать все RewriteRule
директивы.
Директива RewriteCond
задает условия срабатывания правила перезаписи RewriteRule
. Таких условий перед правилом RewriteRule
может быть несколько. Директива RewriteCond
не является обязательной и может отсутствовать.
Директива RewriteRule
— здесь указывается само правило перезаписи, которое для конкретного преобразования должно быть единственным.
3. Директива RewriteRule
Синтаксис директивы
RewriteRule Шаблон Подстановка [Флаги]
Шаблон
— условие, выполнение которого запускает исполнение правилаПодстановка
— правило изменения (преобразования) URL[Флаги]
— дополняют преобразование URL
Примеры директивы
# Преобразуем URL www.example.net/page/123/ в URL # www.example.net/index.php?show=page&id=123 # (внутренний редирект) RewriteRule ^/page/(\d+)/$ /index.php?show=page&id=$1 [L]
# Запрет посещений веб-сайта для робота поисковой системы # Google (при вызове возвращает ошибку 403 Forbidden) RewriteCond %{USER_AGENT} Googlebot # Дефис означает, что преобразование URL не требуется RewriteRule .* - [F]
# Исправление пропущенной буквы при наборе адреса пользователем # /index.htm => /index.html, /path/index.htm => /path/index.html RewriteRule ^(.*)/([a-z]+)\.htm$ $1/$2.html [R=301]
Некоторые флаги директивы
[NC]
— делаетШаблон
нечувствительным к регистру, когдаШаблон
применяется к текущему URL[QSA]
— добавить строку запроса из исходного URL к строке запроса, созданной правилами перезаписи[L]
— остановить процесс преобразования на этом месте и не применять больше никаких правил преобразований[N]
— перезапустить процесс преобразований (начав с первого правила). В этом случае URL снова сопоставляется неким условиям, но не оригинальный URL, а URL вышедший из последнего правила преобразования.[F]
— сервер возвращает браузеру ошибку с кодом 403.[R]
— редирект с кодом ответа браузеру 302 (временно перемещен).[R=code]
— редирект с кодом ответа браузеруcode
.
4. Директива RewriteCond
Синтаксис директивы
RewriteCond Строка Условие [Флаги]
Строка
— строка, которая будет проверятся на соответствие выражению, указанному в параметреУсловие
.Условие
— это логическое выражение, по которому проверяется параметрСтрока
. Часто вУсловие
применяют регулярные выражения.[Флаги]
— позволяют задать дополнительные опции, например, можно установить логику объединения правилRewriteCond
через логическоеИ
(по умолчанию) или через логическоеИЛИ
. Или, будет ли сравнение в условииRewriteCond
выполнятся с учетом регистра или без учета регистра.
Примеры нескольких директив RewriteCond
# Одна точка входа, все запросы (кроме файлов и директорий) # на /index.php (внутренний редирект) RewriteCond $1 !=favicon.ico [AND] RewriteCond %{REQUEST_FILENAME} !-f [AND] RewriteCond %{REQUEST_FILENAME} !-d RewriteRule (.*) /index.php
# Редирект с URL www.example.net/index.php?show=page&id=123 # на новый URL www.example.net/page/123/ с кодом 301 RewriteCond %{QUERY_STRING} show=page [NC] RewriteCond %{QUERY_STRING} id=(\d+) [NC] RewriteRule .* /page/%1/ [R=301]
Несмотря на то, что директива RewriteCond
стоит выше, чем правило RewriteRule
, модуль rewrite
сначала проверяет строку на соответствие с шаблоном в RewriteRule
, и если строка совпадает с шаблоном, он смотрит на указанные выше условия в RewriteCond
. Если условия тоже совпадают, происходит преобразование согласно правилу RewriteRule
.
Строка
может, к примеру, содержать часть или весь URL. Подставить в параметр Строка
часть URL можно при помощи переменных подстановки ($1
, $2
, $3
), которые были созданы в соответствующем RewriteRule
. Также параметр Строка
может содержать различные переменные окружения сервера — %{REQUEST_URI}
, %{HTTP_HOST}
, %{QUERY_STRING}
и т.д.
Условие
может иметь вид
-d
— проверка, что директория существует;-f
— проверка, что файл существует.
Дополнительно, перед условием, допускается использование логических символов
!Условие
— инвертирование значения, т.е. сравниваемая строка должна не соответствовать шаблону условия=Условие
—Условие
считается простой строкой и лексически сравнивается сСтрока
. Истинно, если эти две строки полностью одинаковы (символ в символ). ЕслиУсловие
имеет вид""
— это сравниваетСтрока
с пустой строкой.
Некоторые флаги директивы
[NC]
(отNo Case
) — регистр не имеет значения, как вСтрока
так и вУсловие
. Этот флаг эффективен только для сравнений междуСтрока
иУсловие
, он не работает при проверках в файловой системе.[OR]
— логическоеИЛИ
, достаточно совпадения любогоRewriteCond
.[AND]
— логическоеИ
, требуется совпадение всехRewriteCond
.
5. Переменные подстановки
Если в директивах RewriteCond
и/или RewriteRule
часть символов заключить в круглые скобки, то можно обращаться к содержимому в этих скобках через переменные $1
, $2
, $3
и/или %1
, %2
, %3
:
$n
— позволяет использовать группу символов из шаблона директивыRewriteRule
%n
— позволяет использовать группу символов из шаблона директивыRewriteCond
# Редирект с адреса с www на адрес без www RewriteCond %{HTTP_HOST} ^www\.example\.net$ [NC] RewriteRule ^(.*)$ http://example.net/$1 [R=301]
# Редирект с адреса с www на адрес без www RewriteCond %{HTTP_HOST} ^www\.(.*) [NC] RewriteRule ^(.*)$ http://%1/$1 [R=301]
# Редирект с адреса без www на адрес с www RewriteCond %{HTTP_HOST} ^example\.net$ [NC] RewriteRule ^(.*)$ http://www.example.net/$1 [R=301]
# Редирект с адреса без www на адрес с www RewriteCond %{HTTP_HOST} !^www\.(.*) [NC] RewriteRule ^(.*)$ http://www.%1/$1 [R=301]
6. Внешние и внутрение редиректы
Редиректы могут быть внешние и внутрение. Внешний редирект — отправка клиенту HTTP-кода 301 или 302, которые предписывают запросить новый URL. Внутренний редирект — перезапись URI и поиск подходящего файла, который нужно отдать клиенту.
Допустим, в корневой директории виртуального хоста есть поддиректория info
, в которой размещается файл about.html
.
# nano /etc/apache2/sites-available/example.net.conf
<VirtualHost *:80> # .......... прочие директивы конфигурации .......... <IfModule rewrite_module> # временно, для отладки правил — уровень trace3 для модуля rewrite; # доступны уровни детализации лога от trace1 (min) до trace8 (max) LogLevel error rewrite:trace3 # формат лога ошибок: только дата-время, название модуля и сообщение ErrorLogFormat "[%t] [%-m] %M" # Разрешить работу модуля rewrite для этого виртуального хоста RewriteEngine On # Внутренний редирект /info/about.html => /data/index.php?show=info?name=about RewriteRule ^/([a-z]+)/([a-z]+)\.html$ /data/index.php?show=$1&name=$2 [L] </IfModule> # .......... прочие директивы конфигурации .......... </VirtualHost>
При запросе URI /info/about.html
, пользователю кажется, будто оно просматривает файл about.html
в директории info
, но на самом деле отрабатывает скрипт index.php
в директории data
, который формирует страницу «на лету».
# nano /var/www/example.net/html/info/about.html
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Чем мы занимаемся</title> </head> <body> <h1>Чем мы занимаемся</h1> <p>Файл /var/www/example.net/html/info/about.html</p> </body> </html>
# nano /var/www/example.net/html/data/index.php
<?php $show = $_GET['show'] ?? ''; $name = $_GET['name'] ?? ''; if (!preg_match('~[a-z]+~', $show)) not_found(); if (!preg_match('~[a-z]+~', $name)) not_found(); $file = $_SERVER['DOCUMENT_ROOT'] . '/' . $show . '/' . $name . '.html'; if (!is_file($file)) not_found(); readfile($file); echo 'Сформировано скриптом ', __FILE__; function not_found() { header('HTTP/1.1 404 Not Found'); echo '<h1>404 Page Not Found</h1>'; echo 'Сформировано скриптом ', __FILE__; die(); }
Посмотрим файл лога — какие правила перезаписи сработали
# cat /var/log/apache2/example-net-error.log | grep rewrite […] [rewrite] … […][…] init rewrite engine with requested uri /info/about.html […] [rewrite] … […][…] applying pattern '^/([a-z]+)/([a-z]+)\\.html$' to uri '/info/about.html' […] [rewrite] … […][…] rewrite '/info/about.html' -> '/data/index.php?show=info&name=about' […] [rewrite] … […][…] split uri=/data/index.php?show=info&name=about -> uri=/data/index.php, args=show=info&name=about […] [rewrite] … […][…] local path result: /data/index.php […] [rewrite] … […][…] prefixed with document_root to /var/www/example.net/html/data/index.php […] [rewrite] … […][…] go-ahead with /var/www/example.net/html/data/index.php [OK]
Усложним задачу. Мы хотим сообщить клиентам, что страница /info/about.html
теперь расположена по другому адресу — это /page/info/about/
. Но это виртуальный путь, директория page
и ее поддиректории физически не существуют. А показывать страницу /info/about.html
будет скрипт /data/index.php
.
# nano /etc/apache2/sites-available/example.net.conf
<VirtualHost *:80> # .......... прочие директивы конфигурации .......... <IfModule rewrite_module> # временно, для отладки правил — уровень trace3 для модуля rewrite; # доступны уровни детализации лога от trace1 (min) до trace8 (max) LogLevel error rewrite:trace3 # формат лога ошибок: только дата-время, название модуля и сообщение ErrorLogFormat "[%t] [%-m] %M" # Разрешить работу модуля rewrite для этого виртуального хоста RewriteEngine On # Внешний редирект /info/about.html => /page/info/about/ с кодом 301 RewriteRule ^/([a-z]+)/([a-z]+)\.html$ /page/$1/$2/ [R=301] # Внутренний редирект /page/info/about/ => /data/index.php?show=info?name=about RewriteRule ^/page/([a-z]+)/([a-z]+)/$ /data/index.php?show=$1&name=$2 [L] </IfModule> # .......... прочие директивы конфигурации .......... </VirtualHost>
Выполним запрос страницы /info/about.html
с помощью утилиты curl
$ curl -i -L http://example.net/info/about.html HTTP/1.1 301 Moved Permanently Date: Sat, 06 Apr 2024 09:12:40 GMT Server: Apache/2.4.52 (Ubuntu) Location: http://example.net/page/info/about/ Content-Length: 320 Content-Type: text/html; charset=iso-8859-1 HTTP/1.1 200 OK Date: Sat, 06 Apr 2024 09:12:40 GMT Server: Apache/2.4.52 (Ubuntu) Vary: Accept-Encoding Transfer-Encoding: chunked Content-Type: text/html; charset=UTF-8 <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Чем мы занимаемся</title> </head> <body> <h1>Чем мы занимаемся</h1> <p>Файл /var/www/example.net/html/info/about.html</p> </body> </html> Сформировано скриптом /var/www/example.net/html/data/index.php
Посмотрим файл лога — какие правила перезаписи сработали
# cat /var/log/apache2/example-net-error.log | grep rewrite […] [rewrite] … […][…] init rewrite engine with requested uri /info/about.html […] [rewrite] … […][…] applying pattern '^/([a-z]+)/([a-z]+)\\.html$' to uri '/info/about.html' […] [rewrite] … […][…] rewrite '/info/about.html' -> '/page/info/about/' […] [rewrite] … […][…] explicitly forcing redirect with http://example.net/page/info/about/ […] [rewrite] … […][…] escaping http://example.net/page/info/about/ for redirect […] [rewrite] … […][…] redirect to http://example.net/page/info/about/ [REDIRECT/301] […] [rewrite] … […][…] init rewrite engine with requested uri /page/info/about/ […] [rewrite] … […][…] applying pattern '^/([a-z]+)/([a-z]+)\\.html$' to uri '/page/info/about/' […] [rewrite] … […][…] applying pattern '^/page/([a-z]+)/([a-z]+)/$' to uri '/page/info/about/' […] [rewrite] … […][…] rewrite '/page/info/about/' -> '/data/index.php?show=info&name=about' […] [rewrite] … […][…] split uri=/data/index.php?show=info&name=about -> uri=/data/index.php, args=show=info&name=about […] [rewrite] … […][…] local path result: /data/index.php […] [rewrite] … […][…] prefixed with document_root to /var/www/example.net/html/data/index.php […] [rewrite] … […][…] go-ahead with /var/www/example.net/html/data/index.php [OK]
7. Полезные внешние редиректы
Для сайта нужно настроить 301 редирект с домена www.example.net
на домен example.net
. Или наоборот, с домена example.com
на домен www.example.com
. Кроме того, нужно настроить редирект с http
на https
. И желательно — редирект с /some/path/index.php
на /some/path/
.
# nano /etc/apache2/sites-available/example.net.conf
<VirtualHost *:80 *:443> ServerName example.net ServerAlias www.example.net ServerAdmin webmaster@example.net # .......... прочие директивы конфигурации .......... <IfModule rewrite_module> # Разрешить работу модуля rewrite для этого виртуального хоста RewriteEngine On # Внешний редирект с http на https с кодом 301 Moved Permanently RewriteCond %{HTTPS} =off RewriteRule .* https://example.net%{REQUEST_URI} [R=301] # Внешний редирект с www.example.net на example.net с кодом 301 RewriteCond %{HTTP_HOST} ^www [NC] RewriteRule .* https://example.net%{REQUEST_URI} [R=301] # Внешний 301-й редирект с /some/path/index.php на /some/path/ RewriteCond %{REQUEST_URI} index\.php$ # возможны варианты /index.php => / и /path/index.php => /path/ RewriteRule ^(/|/.+/)index\.php$ https://example.net$1 [R=301] </IfModule> # .......... прочие директивы конфигурации .......... </VirtualHost>
Базовая HTTP-авторизация
Базовая авторизация HTTP — один из самых простых методов закрытия доступа к сайту. Давайте создадим файл .htpasswd
для доступа двух пользователей с помощью утилиты htpasswd
, которая входит в пакет apache2-utils
. Опция -c
создает новый файл, так что использовать ее нужно только один раз. Обратите внимание, что повтороное использование опции удалит существующий файл паролей.
# htpasswd -c /var/www/example.net/.htpasswd evgeniy New password: 123456 Re-type new password: 123456 Adding password for user evgeniy # htpasswd /var/www/example.net/.htpasswd sergey New password: qwerty Re-type new password: qwerty Adding password for user sergey # cat /var/www/example.net/.htpasswd evgeniy:$apr1$fLWbkbFP$p9XstREKS16XdvYOhZWoV/ sergey:$apr1$GYo5yHM3$HmiviAK47iP8SD5AkA6Ht/
Защищаем директорию admin
виртуального хоста паролем
# nano /etc/apache2/sites-available/example.net.conf
<VirtualHost *:80 *:443> ServerName example.net ServerAlias www.example.net ServerAdmin webmaster@example.net <IfModule rewrite_module> # Разрешить работу модуля rewrite для этого виртуального хоста RewriteEngine On # Внешний редирект с http на https с кодом 301 Moved Permanently RewriteCond %{HTTPS} =off [OR] # Внешний редирект с www.example.net на example.net с кодом 301 RewriteCond %{HTTP_HOST} ^www [NC] RewriteRule .* https://example.net%{REQUEST_URI} [R=301] </IfModule> <IfModule ssl_module> SSLEngine On SSLCertificateFile /etc/ssl/certs/example-net.crt SSLCertificateKeyFile /etc/ssl/private/example-net.key </IfModule> DocumentRoot /var/www/example.net/html DirectoryIndex index.php index.html Options -Indexes # Доступ к этой директории только по логину и паролю <Directory "/var/www/example.net/html/admin"> AuthType Basic AuthName "Restricted Content" AuthBasicProvider file AuthUserFile /var/www/example.net/.htpasswd Require valid-user </Directory> <FilesMatch ".+\.ph(ar|p|tml)$"> SetHandler "proxy:unix:/run/php/php8.1-fpm-example-net.sock|fcgi://localhost" </FilesMatch> ErrorLog ${APACHE_LOG_DIR}/example-net-error.log CustomLog ${APACHE_LOG_DIR}/example-net-access.log combined </VirtualHost>
Файл конфигурации .htaccess
Файл конфигурации .htaccess
(сокращение от «hypertext access») переопределяет для директории настройки, заданные для сервера в целом или виртуального хоста. Нет особого смысла в использовании .htaccess
, если есть доступ к главному файлу конфигурации. Чаще всего .htaccess
нужен при использовании виртуального хостинга.
Чтобы разрешить переопределение настроек через .htaccess
для какой-то директории и ее поддиректорий — нужно задать подходящее значение директивы AllowOverride
для этой директории. Для поддиректории можно создать свой .htaccess
— настройки в нем будет иметь более высокий приоритет.
# nano /etc/apache2/sites-available/example.net.conf
<VirtualHost *:80> ServerName example.net ServerAlias www.example.net ServerAdmin webmaster@example.net DocumentRoot /var/www/example.net/html DirectoryIndex index.php index.html Options -Indexes # Разрешаем изменять настройки через файл .htaccess <Directory "/var/www/example.net/html"> AllowOverride All </Directory> # .......... прочие директивы конфигурации .......... </VirtualHost>
Пример файла .htaccess
в корневой директории виртуального хоста
AddDefaultCharset utf-8 Options -Indexes ErrorDocument 404 /error/404.php ErrorDocument 403 /error/403.php
Если PHP установлен как модуль Apache — в файле .htaccess
можно переопределять настройки, заданные в файле php.ini
.
# поиск файлов header.php и footer.php выполняется в директориях, # которые перечислены в директиве include_path файла php.ini php_value auto_prepend_file header.php php_value auto_append_file footer.php
Переменные окружения сервера
В файлах конфигурации Apache можно использовать переменные из операционной системы, которые были созданы до запуска веб-сервера (имя начинается с $
). Такие переменные можно создать самостоятельно в файле конфигурации /etc/apache2/envvars
. Еще можно использовать переменные среды веб-сервера — их создает сам Apache (имя начинается с %
).
%{HTTP_ACCEPT}
— заголовок сообщает типы контента, которые клиент способен понимать. Сервер затем выбирает одно из предложений, использует его и информирует клиента о своём выборе заголовком ответа Content-Type
.
%{HTTP_COOKIE}
— содержит сохраненные HTTP-файлы cookie
, ранее отправленные сервером с заголовком Set-Cookie
.
%{HTTP_FORWARDED}
— содержит информацию с клиентской стороны, которая изменена или потеряна когда в путь запроса вовлечён прокси.
%{HTTP_HOST}
— указывает доменное имя сервера или виртуального хоста, куда клиент отправляет HTTP-запрос. Другими словами, это содержимое заголовка Host
клиента.
%{HTTP_PROXY_CONNECTION}
— определяет, остается ли сетевое соединение открытым после завершения текущей транзакции. Если отправляется keep-alive
, соединение является постоянным и не закрытым, что позволяет выполнять последующие запросы в рамках этого соединения.
%{HTTP_REFERER}
— содержит адрес предыдущей веб-страницы, с которой была сделана ссылка на запрашиваемую страницу.
%{HTTP_USER_AGENT}
— содержит строку-характеристику, которая позволяет идентифицировать тип приложения, операционную систему, поставщика программного обеспечения запрашивающего программного агента.
%{DOCUMENT_ROOT}
— содержит значение директивы DocumentRoot
текущего виртуального хоста.
%{SERVER_NAME}
— содержит значение директивы ServerName
или ServerAlias
текущего виртуального хоста.
%{SERVER_ADDR}
— содержит ip-адрес виртуального хоста, обслуживающего запрос.
%{SERVER_PORT}
— содержит номер порта виртуального хоста, обслуживающего запрос.
%{SERVER_PROTOCOL}
— содержит протокол, используемый клиентом для запроса.
%{REQUEST_METHOD}
— HTTP метод входящего запроса (например, GET, POST, HEAD).
%{QUERY_STRING}
— строка запроса (после символа ?
, например /some/path/index.php?foo=bar
).
%{REMOTE_ADDR}
— ip-адрес удалённого хоста (клиента, от которого пришел запрос).
%{REMOTE_HOST}
— доменное имя удалённого хоста (клиента, от которого пришел запрос).
%{HTTPS}
— содержит строку on
для SSL-соединения, в противном случае — строку off
.
%{REMOTE_ADDR}
— ip-адрес удалённого хоста (см.модуль mod_remoteip
).
%{REQUEST_SCHEME}
— содержит схему запроса, обычно http
или https
.
%{REQUEST_URI}
— компонент пути запрошенного URI, например /some/path/index.php
.
Пользовательские переменные
1. Директивы SetEnv и UnsetEnv
Эти директивы предоставляются модулем env_module
и позволяют создать или удалить переменную среды. Контекст переменной — сервер, виртуальный хост, директория, файл .htaccess
. Переменная создается после выполнения большинства ранних директив обработки запроса, таких как управление доступом и сопоставление URI с именем файла. Если переменная предназначена для использования на этой ранней стадии (например, в директивах модуля перезаписи) — нужно создать ее с помощью SetEnvIf
.
# Есть два виртуальных хоста, которые обслуживают директории develop.example.net и testing.example.net # для разработки и для тестирования. Время от времени изменения переносятся из директории для разработки # в директорию для тестирования. Уже протестированные изменения выкладываются на production сервер. Для # разработки и тестирования нужны разные настройки PHP — например, показ ошибок. При этом виртуальные # хосты работают с одним пулом процессов PHP-FPM службы, но с двумя разными базами данных (хотя сервер # БД один). Возможно, для разработки и тестирования есть смысл создать отдельные пулы процессов PHP-FPM. <VirtualHost *:80> ServerAdmin webmaster@localhost DocumentRoot /var/www/html DirectoryIndex index.html ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> <VirtualHost *:80> SetEnv PHPAPP_CONFIG /var/www/develop.example.net/app.config.php SetEnv DATABASE_HOST 192.168.110.250 SetEnv DATABASE_NAME develop_db_name SetEnv DATABASE_USER develop_db_user SetEnv DATABASE_PASS qwerty123456 SetEnv DATABASE_PORT 3306 ServerName develop.example.net ServerAdmin webmaster@example.net DocumentRoot /var/www/develop.example.net/html DirectoryIndex index.php index.html Options -Indexes <FilesMatch ".+\.ph(ar|p|tml)$"> SetHandler "proxy:unix:/run/php/php8.1-fpm-example-net.sock|fcgi://localhost" </FilesMatch> ErrorLog ${APACHE_LOG_DIR}/develop-example-net-error.log CustomLog ${APACHE_LOG_DIR}/develop-example-net-access.log combined </VirtualHost> <VirtualHost *:80> SetEnv PHPAPP_CONFIG /var/www/testing.example.net/app.config.php SetEnv DATABASE_HOST 192.168.110.250 SetEnv DATABASE_NAME testing_db_name SetEnv DATABASE_USER testing_db_user SetEnv DATABASE_PASS 123456qwerty SetEnv DATABASE_PORT 3306 ServerName testing.example.net ServerAdmin webmaster@example.net DocumentRoot /var/www/testing.example.net/html DirectoryIndex index.php index.html Options -Indexes <FilesMatch ".+\.ph(ar|p|tml)$"> SetHandler "proxy:unix:/run/php/php8.1-fpm-example-net.sock|fcgi://localhost" </FilesMatch> ErrorLog ${APACHE_LOG_DIR}/testing-example-net-error.log CustomLog ${APACHE_LOG_DIR}/testing-example-net-access.log combined </VirtualHost>
Получить в php-скрипте доступ к переменным окружения, заданным в конфигурации виртуального хоста, можно с помощью функции getenv()
или через суперглобальный массив $_SERVER
.
<h3>Суперглобальный массив $_SERVER</h3> <pre> <?php print_r($_SERVER); ?> </pre> <h3>Подключаем файл настроек PHP<h3> <?php require_once getenv('PHPAPP_CONFIG'); ?> <h3>Данные для подключения к БД<h3> <pre> <?php echo 'DATABASE_HOST=', $_SERVER['DATABASE_HOST'], PHP_EOL; echo 'DATABASE_NAME=', $_SERVER['DATABASE_NAME'], PHP_EOL; echo 'DATABASE_USER=', $_SERVER['DATABASE_USER'], PHP_EOL; echo 'DATABASE_PASS=', $_SERVER['DATABASE_PASS'], PHP_EOL; echo 'DATABASE_PORT=', $_SERVER['DATABASE_PORT'], PHP_EOL; ?> </pre>
<?php # файл /var/www/develop.example.net/app.config.php error_reporting(E_ALL); ini_set('display_errors', true);
2. Директивы SetEnvIf и SetEnvIfNoCase
Модуль setenvif_module
позволяет устанавливать переменные среды в зависимости от того, соответствуют ли заголовки http-запроса указанным регулярным выражениям. Эти переменные могут использоваться позже для принятия решений о действиях, которые необходимо предпринять, а также становятся доступными для сценариев CGI и страниц SSI.
SetEnvIf[NoCase] attribute regexp [!]env-variable[=value] [[!]env-variable[=value]]
Первый аргумент attribute
может быть
- Заголовок HTTP-запроса, например —
Host
,User-Agent
,Referer
,Accept-Language
. Допускается использование регулярного выражения для указания набора заголовков запроса. - Предопределенное значение из списка —
Remote_Host
,Remote_Addr
,Request_Method
,Request_Protocol
,Request_URI
. - Имя переменной среды веб-сервера, в том числе — установленной выше директивой
SetEnvIf[NoCase]
.
Второй аргумент regexp
представляет собой регулярное выражение. Если регулярное выражение соответствует атрибуту, то оцениваются остальные аргументы.
Остальные аргументы задают имена и значения переменных, которые нужно установить. Если значение переменной не задано — устанавливается значение 1
. Если перед именем переменной есть !
— эта переменная удаляется.
# Создаем переменную, если заголовок Host равен www.example.net SetEnvIf Host ^www\.example\.net$ IS_WWW_HOST=yes # Выполняем редирект на example.net, если существует IS_WWW_HOST RewriteCond %{ENV:IS_WWW_HOST} =yes RewriteRule .* https://example.net%{REQUEST_URI} [R=301]
# Создаем переменную, если клиент запрашивает изображение SetEnvIf Request_URI \.(png|gif|jpg)$ IS_IMG_REQUEST=yes # Не записываем в лог, если клиент запрашивает изображение CustomLog ${APACHE_LOG_DIR}/access.log combined env=!IS_IMG_REQUEST
3. Директивы Define и IfDefine
Директива Define
позволяет задать переменную, к которой можно обращаться в файлах конфигурации с использованием синтексиса ${VAR}
. Переменная всегда определена глобально и не ограничивается областью окружающего раздела конфигурации. Используется вместе с директивой IfDefine
, которая позволяет запускать сервер с одним файлом конфигурации — но при этом с разными настройками.
# Есть два виртуальных хоста, каждый из которых может обслуживать две директории — для разработки и # для тестирования. Время от времени изменения переносятся из директории для разработки в директорию # для тестирования. Веб-сервер перезапускается, чтобы обслуживать директорию для тестирования вместо # директории для разработки. Уже протестированные изменения выкладываются на production сервер. А # веб-сервер опять перезапускается для обслуживания директории для разработки. Define RUN_TESTING_EXAMPLE_NET # Define RUN_TESTING_EXAMPLE_ORG <IfDefine RUN_TESTING_EXAMPLE_NET> Define SERVER_EXAMPLE_NET testing.example.net </IfDefine> <IfDefine !RUN_TESTING_EXAMPLE_NET> Define SERVER_EXAMPLE_NET develop.example.net </IfDefine> <IfDefine RUN_TESTING_EXAMPLE_ORG> Define SERVER_EXAMPLE_ORG testing.example.org </IfDefine> <IfDefine !RUN_TESTING_EXAMPLE_ORG> Define SERVER_EXAMPLE_ORG develop.example.org </IfDefine> <VirtualHost *:80> ServerAdmin webmaster@localhost DocumentRoot /var/www/html DirectoryIndex index.html ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> <VirtualHost *:80> ServerName ${SERVER_EXAMPLE_NET} ServerAdmin webmaster@example.net DocumentRoot /var/www/${SERVER_EXAMPLE_NET}/html DirectoryIndex index.html Options -Indexes ErrorLog ${APACHE_LOG_DIR}/${SERVER_EXAMPLE_NET}-error.log CustomLog ${APACHE_LOG_DIR}/${SERVER_EXAMPLE_NET}-access.log combined </VirtualHost> <VirtualHost *:80> ServerName ${SERVER_EXAMPLE_ORG} ServerAdmin webmaster@example.org DocumentRoot /var/www/${SERVER_EXAMPLE_ORG}/html DirectoryIndex index.html Options -Indexes ErrorLog ${APACHE_LOG_DIR}/${SERVER_EXAMPLE_ORG}-error.log CustomLog ${APACHE_LOG_DIR}/${SERVER_EXAMPLE_ORG}-access.log combined </VirtualHost>
Созданную ранее переменную можно удалить, используя директиву UnDefine
.
Страницы ошибок 404, 403, 50x
В случае проблемы или ошибки Apache можно настроить для выполнения одного из четырех действий
- вывести простое жестко закодированное сообщение об ошибке
- вывести настроенное администратором сообщение об ошибке
- внутренний редирект на локальный URI для обработки ошибки
- внешний редирект на любой URL-адрес для обработки ошибки
Первый вариант используется по умолчанию, варианты 2-4 настраиваются с помощью директивы ErrorDocument
, за которой следует код ответа HTTP + URI|URL для редиректа или сообщение. Для варианта по умолчанию можно использовать специальное значение default
.
# жестко закодированное сообщение об ошибке Apache ErrorDocument 500 default # внешнее перенаправление на какой-то URL-адрес ErrorDocument 500 http://example.com/cgi-bin/error.cgi # внутреннее перенаправление на локальный URI ErrorDocument 500 /error/500.html ErrorDocument 404 /error/404.html # настроенное администратором сообщение об ошибке ErrorDocument 403 "Access denied"
При внутреннем редиректе на локальный URI устанавливаются дополнительные переменные среды. Переменные REDIRECT_URL
, REDIRECT_STATUS
и REDIRECT_QUERY_STRING
будут гарантированно установлены, остальные переменные будут установлены только в том случае, если они существовали до возникновения ошибки. То есть, если до редиректа существовала переменная SERVER_NAME
, то после редиректа будет установлена переменная REDIRECT_SERVER_NAME
.
Давайте создадим директорию error
и разместим в ней страницы ошибок
# mkdir /var/www/example.net/html/error
# nano /var/www/example.net/html/error/404.html
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Страница не найдена</title> </head> <body> <h3>Страница не найдена</h3> <p>Файл /var/www/example.net/html/error/404.html</p> </body> </html>
# nano /var/www/example.net/html/error/500.html
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Внутренняя ошибка сервера</title> </head> <body> <h3>Внутренняя ошибка сервера</h3> <p>Файл /var/www/example.net/html/error/500.html</p> </body> </html>Теперь настроим для виртуального хоста
example.net
показ этих страниц
# nano /etc/apache2/sites-available/example.net.conf
<VirtualHost *:80 *:443> ServerName example.net ServerAlias www.example.net ServerAdmin webmaster@example.net <IfModule rewrite_module> # разрешить работу модуля rewrite для этого виртуального хоста RewriteEngine On # Внешний редирект с http на https с кодом 301 Moved Permanently RewriteCond %{HTTPS} =off [OR] # Внешний редирект с www.example.net на example.net с кодом 301 RewriteCond %{HTTP_HOST} ^www [NC] RewriteRule (.*) https://example.net$1 [R=301] # Внешний 301 редирект с /some/path/index.php на /some/path/ RewriteCond %{REQUEST_URI} index\.php$ RewriteRule ^(/|/.+/)index\.php$ https://example.net$1 [R=301] </IfModule> <IfModule ssl_module> SSLEngine on SSLCertificateFile /etc/ssl/certs/example-net.crt SSLCertificateKeyFile /etc/ssl/private/example-net.key </IfModule> DocumentRoot /var/www/example.net/html DirectoryIndex index.php index.html Options -Indexes # Доступ к этой директории только по логину и паролю <Directory "/var/www/example.net/html/admin"> AuthType Basic AuthName "Restricted Content" AuthBasicProvider file AuthUserFile /var/www/example.net/.htpasswd Require valid-user </Directory> <FilesMatch ".+\.ph(ar|p|tml)$"> SetHandler "proxy:unix:/run/php/php8.1-fpm-example-net.sock|fcgi://localhost" </FilesMatch> ErrorDocument 404 /error/404.html ErrorDocument 403 /error/403.html ErrorDocument 500 /error/500.html ErrorDocument 503 /error/503.html ErrorLog ${APACHE_LOG_DIR}/example-net-error.log CustomLog ${APACHE_LOG_DIR}/example-net-access.log combined </VirtualHost>
Есть смысл добавить ещё один блок в конфигурацию виртуального хоста, чтобы клиенты не могли запрашивать страницы ошибок напрямую. Тут возможны разные варианты, расмотрим два из них. Мы можем просто запрещать доступ к этим страницам. Или сообщать клиентам, будто этих страниц вовсе нет.
<VirtualHost *:80 *:443> # .......... прочие директивы конфигурации .......... ErrorDocument 404 /error/404.html ErrorDocument 403 /error/403.html ErrorDocument 500 /error/500.html ErrorDocument 503 /error/503.html # Доступ по URI /error/(40x|50x).html возможен только после внутреннего перенаправления, # который задается директивой ErrorDocument. При попытке прямого доступа — 403 Forbidden. <LocationMatch "^/error/"> Require env REDIRECT_STATUS </LocationMatch> # .......... прочие директивы конфигурации .......... </VirtualHost>
<VirtualHost *:80 *:443> # .......... прочие директивы конфигурации .......... ErrorDocument 404 /error/404.html ErrorDocument 403 /error/403.html ErrorDocument 500 /error/500.html ErrorDocument 503 /error/503.html # При попытке прямого доступа к /error/(40x|50x).html — показываем страницу 404 Not Found # (первая директива RewriteRule) или 403 Forbidden (вторая директива RewriteRule). <Directory "/var/www/example.net/html/error"> RewriteCond %{ENV:REDIRECT_STATUS} ^$ RewriteRule .* - [R=404] # RewriteRule .* - [F] </Directory> # .......... прочие директивы конфигурации .......... </VirtualHost>
Хорошо бы еще оперативно узнавать о срабатывании директивы ErrorDocument
— давайте напишем скрипт, который будет сообщать об ошибках в телегам-чат. И укажем этот скрипт в качестве второго аргумента директивы ErrorDocument
.
<VirtualHost *:80 *:443> # .......... прочие директивы конфигурации .......... ErrorDocument 404 /error/handler.php ErrorDocument 403 /error/handler.php ErrorDocument 500 /error/handler.php ErrorDocument 503 /error/handler.php # .......... прочие директивы конфигурации .......... </VirtualHost>
<?php $status = isset($_SERVER['REDIRECT_STATUS']) ? $_SERVER['REDIRECT_STATUS'] : 'empty'; $name = $status . '.html'; $file = is_file($name) ? $name : '500.html'; readfile($file); $url = isset($_SERVER['REDIRECT_URL']) ? $_SERVER['REDIRECT_URL'] : 'empty'; $message = "Произошла ошибка! STATUS=${status}, URL=${url}"; telegram($message); function telegram($message) { $token = '..........'; $chatId = '.........'; $data = http_build_query([ 'chat_id' => $chatId, 'text' => $message ]); $opts = [ 'http' => [ 'method' => 'POST', 'header' => 'Content-type: application/x-www-form-urlencoded', 'content' => $data ] ]; $context = stream_context_create($opts); $response = file_get_contents('https://api.telegram.org/bot' . $token . '/sendMessage', false, $context); }
Использование шаблонов
Модуль macro_module
предоставляет возможность использования шаблонов в файлах конфигурации. Шаблон — это фрагмент конфигурации, который можно использовать многократно. Внутри блока Macro
можно использовать параметры типа ${param}
— которые заменяются настоящими значениями.
# a2enmod macro # systemctl restart apache2.service
<Macro VirtHost ${name} ${host} ${port}> <VirtualHost *:${port}> ServerName ${host} ServerAlias www.${host} ServerAdmin webmaster@${host} DocumentRoot "/var/www/${host}/html" DirectoryIndex index.php index.html Options -Indexes <FilesMatch ".+\.ph(ar|p|tml)$"> SetHandler "proxy:unix:/run/php/php8.1-fpm-${name}.sock|fcgi://localhost" </FilesMatch> ErrorLog ${APACHE_LOG_DIR}/${name}-error.log CustomLog ${APACHE_LOG_DIR}/${name}-access.log combined </VirtualHost> </Macro>
Use VirtHost example-org example.org 80 Use VirtHost example-com example.com 443 # рекомендуется отменить определение макроса после его использования UndefMacro VirtHost
Настройка реверс-прокси
У меня есть виртуальная машина apache
, на которую установлен тестовый веб-сервер. Этот веб-сервер обслуживает домены example.net
и example.org
. Давайте создим еще две виртуальные машины в той же локальной сети — и установим на каждой веб-сервер Apache. А виртуальную машину apache
настроим как прокси-сервер, который будет перенаправлять запросы на backend-серверы www-one
(ip-адрес 192.168.110.10
) и www-two
(ip-адрес 192.168.110.20
).
Виртуальная машина apache
Нам потребуется основной модуль proxy_module
и несколько дополнительных, которые расширяют функционал основного
proxy_module
— главный модуль Apache для перенаправления соединенийproxy_http_module
— позволяет использовать прокси для протокола HTTPproxy_balancer_module
иlbmethod_byrequests_module
— обеспечивают балансировку нагрузки
# a2enmod proxy # a2enmod proxy_http # a2enmod proxy_balancer # a2enmod lbmethod_byrequests
# systemctl restart apache2
Редактируем файл конфигурации виртуального хоста example.net
# nano /etc/apache2/sites-available/example.net.conf
<VirtualHost *:80 *:443> ServerName example.net ServerAlias www.example.net ServerAdmin webmaster@example.net <IfModule rewrite_module> RewriteEngine On RewriteCond %{HTTPS} =off [OR] RewriteCond %{HTTP_HOST} ^www [NC] RewriteRule (.*) https://example.net$1 [R=301] RewriteCond %{REQUEST_URI} index\.php$ RewriteRule ^(/|/.+/)index\.php$ https://example.net$1 [R=301] </IfModule> <IfModule ssl_module> SSLEngine on SSLCertificateFile /etc/ssl/certs/example-net.crt SSLCertificateKeyFile /etc/ssl/private/example-net.key </IfModule> # Основная директива настройки реверс-прокси: все, что идет после # URI / — будет отправлено на бэкенд-сервер. Например, если пришел # запрос на /foo — он будет отправлен на http://192.168.110.10/foo ProxyPass / http://192.168.110.10/ # Директива должна иметь такое же значение, как и ProxyPass. Она # сообщает Apache, как изменить заголовки в ответе от backend. И # браузер клиента будет правильно перенаправлен на прокси-адрес. ProxyPassReverse / http://192.168.110.10/ # Отправлять backend-серверу заголовок Host — так backend будет # считать, что он обслуживает домен, а не просто ip-адрес. ProxyPreserveHost on # ProxyRequests следует отключать при использовании ProxyPass. # Это предотвращает использование сервера как forward-прокси. ProxyRequests off ErrorLog ${APACHE_LOG_DIR}/example-net-error.log CustomLog ${APACHE_LOG_DIR}/example-net-access.log combined </VirtualHost>
Редактируем файл конфигурации виртуального хоста example.org
# nano /etc/apache2/sites-available/example.org.conf
<VirtualHost *:80> ServerName example.org ServerAlias www.example.org ServerAdmin webmaster@example.org <IfModule rewrite_module> RewriteEngine On RewriteCond %{HTTP_HOST} ^www [NC] RewriteRule (.*) https://example.org$1 [R=301] RewriteCond %{REQUEST_URI} index\.php$ RewriteRule ^(/|/.+/)index\.php$ https://example.org$1 [R=301] </IfModule> # Основная директива настройки реверс-прокси: все, что идет после # URI / — будет отправлено на бэкенд-сервер. Например, если пришел # запрос на /foo — он будет отправлен на http://192.168.110.20/foo ProxyPass / http://192.168.110.20/ # Директива должна иметь такое же значение, как и ProxyPass. Она # сообщает Apache, как изменить заголовки в ответе от backend. И # браузер клиента будет правильно перенаправлен на прокси-адрес. ProxyPassReverse / http://192.168.110.20/ # Отправлять backend-серверу заголовок Host — так backend будет # считать, что он обслуживает домен, а не просто ip-адрес. ProxyPreserveHost on # ProxyRequests следует отключать при использовании ProxyPass. # Это предотвращает использование сервера как forward-прокси. ProxyRequests off ErrorLog ${APACHE_LOG_DIR}/example-org-error.log CustomLog ${APACHE_LOG_DIR}/example-org-access.log combined </VirtualHost>
Включаем виртуальный хост example.org
# a2ensite example.org.conf # systemctl resart apache2.service
Виртуальная машина www-one
Создаем корневую директорию виртуального хоста example.net
# mkdir -p /var/www/example.net/html
Создаем файл конфигурации виртуального хоста example.net
# nano /etc/apache2/sites-available/example.net.conf
<VirtualHost *:80> ServerName example.net ServerAlias www.example.net ServerAdmin webmaster@example.net DocumentRoot /var/www/example.net/html DirectoryIndex index.php index.html Options -Indexes <FilesMatch ".+\.ph(ar|p|tml)$"> SetHandler "proxy:unix:/run/php/php8.1-fpm.sock|fcgi://localhost" </FilesMatch> ErrorDocument 404 /error/handler.php ErrorDocument 403 /error/handler.php ErrorDocument 500 /error/handler.php ErrorDocument 503 /error/handler.php <LocationMatch "^/error/"> Require env REDIRECT_STATUS </LocationMatch> ErrorLog ${APACHE_LOG_DIR}/example-net-error.log CustomLog ${APACHE_LOG_DIR}/example-net-access.log combined </VirtualHost>
Устанавливаем службу PHP-FPM для обработки php-скриптов
# apt install php-fpm
Разрешаем взаимодействие сервера Apache и службы PHP-FPM
# a2enmod proxy_fcgi setenvif # a2enconf php8.1-fpm # systemctl restart apache2.service
Создаем файлы в корневой директории виртуального хоста
# nano /var/www/example.net/html/index.html
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Виртуальная машина www-one</title> </head> <body> <h3>Виртуальная машина www-one</h3> <p>Файл /var/www/example.net/html/index.html</p> </body> </html>
# nano /var/www/example.net/html/phpinfo.php
<h3>Виртуальная машина www-one</h3> <?php phpinfo(); ?>
# nano /var/www/example.net/html/server.php
<h3>Виртуальная машина www-one</h3> <pre> <?php print_r($_SERVER); ?> </pre>
Виртуальная машина www-two
Здесь все будет аналогично, так что только файл конфигурации виртуального хоста example.org
# nano /etc/apache2/sites-available/example.org.conf
<VirtualHost *:80> ServerName example.org ServerAlias www.example.org ServerAdmin webmaster@example.org DocumentRoot "/var/www/example.org/html" DirectoryIndex index.php index.html Options -Indexes <FilesMatch ".+\.ph(ar|p|tml)$"> SetHandler "proxy:unix:/run/php/php8.1-fpm.sock|fcgi://localhost" </FilesMatch> ErrorLog ${APACHE_LOG_DIR}/example-org-error.log CustomLog ${APACHE_LOG_DIR}/example-org-access.log combined </VirtualHost>
Реальный ip-адрес клиента
При использовании прокси сервера теряется правильное значение переменной сервера REMOTE_ADDR
— вместо ip-адреса клиента она содержит ip-адрес прокси-сервера. Но при этом прокси сервер добавляет HTTP-заголовок HTTP_X_FORWARDED_FOR
и записывает в него ip-адрес клиента. На веб-сервере www-one
мы можем восстановить настоящий ip-адрес клиента с помощью модуля remoteip_module
.
# a2enmod remoteip # systemctl restart apache2
# nano /etc/apache2/sites-available/example.net.conf
<VirtualHost *:80> ServerName example.net ServerAlias www.example.net ServerAdmin webmaster@example.net DocumentRoot /var/www/example.net/html DirectoryIndex index.php index.html Options -Indexes <IfModule remoteip_module> # восстановить настроящий ip-адрес клиента из этого заголовка RemoteIPHeader X-Forwarded-For # доверять ip-адресу 192.168.110.50, это наш прокси-сервер RemoteIPInternalProxy 192.168.110.50 </IfModule> <FilesMatch ".+\.ph(ar|p|tml)$"> SetHandler "proxy:unix:/run/php/php8.1-fpm.sock|fcgi://localhost" </FilesMatch> ErrorDocument 404 /error/handler.php ErrorDocument 403 /error/handler.php ErrorDocument 500 /error/handler.php ErrorDocument 503 /error/handler.php <LocationMatch "^/error/"> Require env REDIRECT_STATUS </LocationMatch> ErrorLog ${APACHE_LOG_DIR}/example-net-error.log CustomLog ${APACHE_LOG_DIR}/example-net-access.log combined </VirtualHost>
# systemctl restart apache2
Балансировка нагрузки
Давайте распределим запросы к example.net
между backend-серверами www-one
и www-two
. Для этого на вартуальной машине www-two
добавим виртуальный хост example.net
. При этом нам нужно, чтобы содержимое директории /var/www/example.net/html
было полностью идентичным на www-one
и www-two
. Например, с некоторой периодичностью синхронизировать эти директории с помощью rsync
. Или вынести эту директорию на отдельный сервер, а www-one
и www-two
будут ее монтировать по NFS.
Создаем файл конфигурации виртуального хоста example.net
на виртуальной машине www-two
# nano /etc/apache2/sites-available/example.net.conf
<VirtualHost *:80> ServerName example.net ServerAlias www.example.net ServerAdmin webmaster@example.net DocumentRoot /var/www/example.net/html DirectoryIndex index.php index.html Options -Indexes <IfModule remoteip_module> RemoteIPHeader X-Forwarded-For RemoteIPInternalProxy 192.168.110.50 </IfModule> <FilesMatch ".+\.ph(ar|p|tml)$"> SetHandler "proxy:unix:/run/php/php8.1-fpm.sock|fcgi://localhost" </FilesMatch> ErrorDocument 404 /error/handler.php ErrorDocument 403 /error/handler.php ErrorDocument 500 /error/handler.php ErrorDocument 503 /error/handler.php <LocationMatch "^/error/"> Require env REDIRECT_STATUS </LocationMatch> ErrorLog ${APACHE_LOG_DIR}/example-net-error.log CustomLog ${APACHE_LOG_DIR}/example-net-access.log combined </VirtualHost>
Редактируем файл конфигурации виртуального хоста example.net
на виртуальной машине apache
<VirtualHost *:80 *:443> ServerName example.net ServerAlias www.example.net ServerAdmin webmaster@example.net <IfModule rewrite_module> RewriteEngine On RewriteCond %{HTTPS} =off [OR] RewriteCond %{HTTP_HOST} ^www [NC] RewriteRule (.*) https://example.net$1 [R=301] RewriteCond %{REQUEST_URI} index\.php$ RewriteRule ^(/|/.+/)index\.php$ https://example.net$1 [R=301] </IfModule> <IfModule ssl_module> SSLEngine on SSLCertificateFile /etc/ssl/certs/example-net.crt SSLCertificateKeyFile /etc/ssl/private/example-net.key </IfModule> # Этот URI не надо проксировать на backend-сервер ProxyPass /balancer-manager ! # По URI /balancer-manager будет доступен менеджер <Location "/balancer-manager"> SetHandler balancer-manager # Доступ разрешен только с ip-адреса 192.168.110.2 Require ip 192.168.110.2 </Location> <Proxy balancer://wwwcluster> # На backend-сервер www-one балансировщик будет отправлять в # два раза больше запросов, чем на backend-сервер www-two BalancerMember http://192.168.110.10 loadfactor=2 BalancerMember http://192.168.110.20 loadfactor=1 ProxySet lbmethod=byrequests </Proxy> ProxyPass / balancer://wwwcluster/ ProxyPassReverse / balancer://wwwcluster/ ProxyPreserveHost on ProxyRequests off ErrorLog ${APACHE_LOG_DIR}/example-net-error.log CustomLog ${APACHE_LOG_DIR}/example-net-access.log combined </VirtualHost>
Модуль proxy_balancer
использует алгоритм балансировки, который предоставляет модуль lbmethod_byrequests
. Можно использовать еще три модуля — lbmethod_bytraffic
, lbmethod_bybusyness
, lbmethod_heartbeat
.
Кроме балансировки нагрузки мы добавили возможность изменения настроек балансировщика на странице https://example.net/balancer-manager
. Но изменения, внесенные на этой странице, не сохраняются при перезагрузке веб-сервера. Чтобы это исправить — нужно добавить директиву BalancerPersist
и установить для нее значение On
.
Обеспечение «липкости»
Балансировщик нагрузки должен обеспечивать «липкость». Когда запрос пересылается на backend-сервер, все последующие запросы от того же пользователя должны быть перенаправлены на тот же backend-сервер. Модуль proxy_balancer
реализует липкость «липкость» с помощью cookie или параметра запроса. Модуль получает значение route
из cookie или параметра — и проксирует запрос на соответствующий backend-сервер. Параметр к каждой ссылке обычно добавляется на backend-е, на прокси-сервере это сделать трудно. А вот добавить cookie легко как на backend-серверах, так и на прокси-сервере. Разумеется, добавлять нужно либо на прокси-сервере, либо на backend-серверах, дублировать не нужно.
Добавляем cookie на прокси-сервере, виртуальная машина apache
<VirtualHost *:80 *:443> # .......... прочие директивы конфигурации .......... ProxyPass /balancer-manager ! <Location "/balancer-manager"> SetHandler balancer-manager Require ip 192.168.110.2 </Location> # Добавляем cookie BACKEND_SERVER для обеспечения «липкости», # только если cookie не добавляется на backend-серверах Header set Set-Cookie "BACKEND_SERVER=.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED <Proxy balancer://wwwcluster> BalancerMember http://192.168.110.10 loadfactor=2 route=www-one BalancerMember http://192.168.110.20 loadfactor=1 route=www-two ProxySet lbmethod=byrequests # Имя cookie BACKEND_SERVER, в нее записываем .www-one или .www-two ProxySet stickysession=BACKEND_SERVER </Proxy> ProxyPass / balancer://wwwcluster/ ProxyPassReverse / balancer://wwwcluster/ ProxyPreserveHost on ProxyRequests off # .......... прочие директивы конфигурации .......... </VirtualHost>
Добавляем cookie на backend-сервере, виртуальная машина www-one
<VirtualHost *:80> ServerName example.net # .......... прочие директивы конфигурации .......... Header set Set-Cookie "BACKEND_SERVER=.www-one; path=/" # .......... прочие директивы конфигурации .......... </VirtualHost>
Добавляем cookie на backend-сервере, виртуальная машина www-two
<VirtualHost *:80> ServerName example.net # .......... прочие директивы конфигурации .......... Header set Set-Cookie "BACKEND_SERVER=.www-two; path=/" # .......... прочие директивы конфигурации .......... </VirtualHost>
Cookie устанавливается при каждом ответе backend-сервера, но можно сделать чуть лучше — устанавливать cookie только в том случае, если не была установлена раньше.
<VirtualHost *:80> ServerName example.net # .......... прочие директивы конфигурации .......... SetEnvIf Cookie BACKEND_SERVER BACKEND_SERVER=yes Header set Set-Cookie "BACKEND_SERVER=.www-one; path=/" env=!BACKEND_SERVER # .......... прочие директивы конфигурации .......... </VirtualHost>
<VirtualHost *:80> ServerName example.net # .......... прочие директивы конфигурации .......... SetEnvIf Cookie BACKEND_SERVER BACKEND_SERVER=yes Header set Set-Cookie "BACKEND_SERVER=.www-two; path=/" env=!BACKEND_SERVER # .......... прочие директивы конфигурации .......... </VirtualHost>
- Apache2. Установка и настройка. Часть 1 из 2
- PHP-FPM. Установка и настройка
- Nginx. Установка и настройка. Часть 3 из 3
- Nginx. Установка и настройка. Часть 2 из 3
- Nginx. Установка и настройка. Часть 1 из 3
- Установка OpenVPN на Ubuntu 18.04 LTS. Часть 12 из 12
- Установка OpenVPN на Ubuntu 18.04 LTS. Часть 11 из 12
Поиск: Apache • CGI • FastCGI • SSL • Конфигурация • Настройка • Сервер • Установка