7 min read

Настройка Nginx + Apache + несколько версий php

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

Все нижеперечисленное я буду выполнять в Debian stable запущенной в VirtualBox. На других системах некоторые действия могут отличаться, однако основной принцип настройки будет тот же.

Почему же настраиваем связку nginx + apache?  Ведь для того, чтобы отдавать сайт на каком-нибудь WordPress достаточно чего-нибудь одного? Различия и особенности одного и другого неплохо описаны вот тут. Если кратко, то сейчас в большинстве случаев Nginx используется как проксирующий сервер, который передает запросы к апачу, а сами же запросы обрабатываются уже апачем. Такая схема весьма неплохо работает на высоко нагруженных серверах с множеством сайтов, потому как nginx быстрей обрабатывает большое количество запросов, а апач лучше работает с динамическим контентом.

Итак, устанавливаем основной софт:

apt install -y nginx apache2 default-mysql-server

Настройка Nginx.

Так как у нас одновременно есть и nginx и apache то между собой они будут конфликтовать и бороться за 80 порт. Поэтому apache я временно выключил. Идем в конфиг nginx и прописываем минимальные настройки. У меня получился такой конфиг:

events {
worker_connections 1024; #максимальное число соединений.
multi_accept on; #за один раз процесс будет принимать все новые подключения.
}
http {
error_log /var/log/nginx/error.log warn; #Путь к логу ошибок и уровень логирования.
access_log off; # тут указывается путь в логам посещений, в нашем случае он отключен.
charset utf-8; #Добавляет указанную кодировку в поле “Content-Type” заголовка ответа.
server_tokens off; #Запрет на отправку версии nginx.
include /etc/nginx/mime.types; #Выносим конфиг mime type в отдельный файл.
default_type application/octet-stream; #Задаёт MIME-тип ответов по умолчанию.
reset_timedout_connection on; #Разрешает или запрещает сброс соединений по таймауту.
client_header_timeout 15; #Задаёт таймаут при чтении заголовка запроса клиента.
client_body_timeout 30; #Задаёт таймаут при чтении тела запроса клиента.
send_timeout 15; #Задаёт таймаут при передаче ответа клиенту.
keepalive_timeout 5; #задаёт таймаут, в течение которого keep-alive соединение с клиентом не будет закрыто со стороны сервера.
keepalive_requests 30; #Задаёт максимальное число запросов, которые можно сделать по одному keep-alive соединению.
client_max_body_size 8m; #Задаёт максимально допустимый размер тела запроса клиента, указываемый в поле “Content-Length” заголовка запроса.
limit_rate_after 30M; #Задаёт начальный объём данных, после передачи которого начинает ограничиваться скорость передачи ответа клиенту.
limit_rate 500K; #Ограничивает скорость передачи ответа клиенту.
open_file_cache max=10000 inactive=3m; #Задает объем кэша
open_file_cache_min_uses 2; #Задает минимальное число обращений к файлу в течении времени заданного параметром inactive необходимых для того чтобы файл оставался открытым в кэше
open_file_cache_valid 1m; #Определяет время, через которое следует проверять актуальность информации об элементе в open_file_cache.
include /etc/nginx/conf.d/*.conf; #Выносим остальные конфиги в отдельный файл.
}

Основной конфиг Nginx у нас есть, теперь нужно написать конфиг для виртуального хоста. У меня получился такой:

server { listen 80; #порт на котором слушает nginx 
server_name name.site www.name.site; #имя виртуального хоста 
location / { proxy_pass http://127.0.0.1:8080/; #Задаёт протокол и адрес проксируемого сервера, а также необязательный URI, на который должен отображаться location. 
proxy_read_timeout 300s; #Задаёт таймаут при чтении ответа проксированного сервера. 
proxy_set_header Host $host; #Позволяет переопределять или добавлять поля заголовка запроса, передаваемые проксируемому серверу. 
proxy_set_header X-Real-IP $remote_addr; #Передает проксируемому серверу реальный IP с которого идет запрос 
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
proxy_buffering off; # Разрешает или запрещает использовать буферизацию ответов проксируемого сервера. 
} 
location ~* \.(css|js|png|gif|jpg|jpeg|ico)$ 
{ 
root /var/www/html; expires 1d; }}

В целом с настройкой nginx закончено, теперь Nginx работает как прокси и перенаправляет запросы к апачу (так же передает реальный IP клиента). Больше информации по директивам в конфигурационных файлах nginx можно найти тут. Самое время перейти к настройке Apache.
 

Установка PHP.

Тут ничего сложного нет, в принципе можно просто все поставить из репозиториев, но у меня там есть только 7.3 Есть вариант подключить дополнительные репозитории и поставить все оттуда, но это не наш метод и к тому же это весьма скучно, поэтому будем собирать php из исходников. В качестве примера будем использовать 3 версии php – 7.0, 7.2 и последнюю на данный момент php 7.4. В принципе версий может быть сколько и сами версии могут быть любые.

И так начнем:

wget https://www.php.net/distributions/php-7.0.33.tar.gz && tar -xzf php-7.0.33.tar.gz && cd php-7.0.33
./configure --prefix=/opt/php70 --with-layout=PHP --with-pear --with-apxs2 --with-zlib --with-bz2 --enable-zip --enable-mbstring --with-iconv --enable-sockets --with-openssl --with-libdir=lib64 --with-mysql=mysqlnd --with-mysqli=mysqlnd --with-pdo-mysql=mysqlnd --with-config-file-path=/etc/php70 --with-xmlrpc --with-xsl --enable-maintainer-zts

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

make && make install

PHP собрался, а так же собрался модуль php для apache, он находится в /usr/lib/apache2/modules/libphp7.so . Так как мы будем собирать не только php7.0 но и другие версии php, во избежание конфликтов и перезаписи модуля переименуем его в libphp70.so

mv /usr/lib/apache2/modules/libphp7.so /usr/lib/apache2/modules/libphp70.so

Теперь проверим правильно ли все собралось и установилось, идем в директорию куда установился php и запускаем php с ключем -v:

root@debian:/opt/php70/bin# ./php -v

PHP 7.0.33 (cli) (built: Feb 18 2020 14:48:01) ( ZTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2017 Zend Technologies

C остальными версиями php поступаем аналогично.

Настройка Apache.

Так же хочется настроить apache на работу с несколькими версиями php и переключать их между собой с помощью того же nginx.

В настройке apache в данном случае нет ничего специфичного. Apache настраивается как обычно за исключением нескольких директив. В данном случае нам нужно настроить apache так чтобы он слушал порт отличный от 80(что в целом логично, так как 80 порт случает nginx) и желательно слушал его на localhost. При такой настройке apache, он будет получать запросы только от nginx и к нему нельзя будет обращаться напрямую.

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

PidFile /tmp/apache70.pid #указываем расположение PID для апача
Timeout 300
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5
User ${APACHE_RUN_USER}
Group ${APACHE_RUN_GROUP}
HostnameLookups Off
ErrorLog ${APACHE_LOG_DIR}/error.log
LogLevel warn
IncludeOptional mods-enabled/*.load
IncludeOptional mods-enabled/*.conf
listen 127.0.0.1:8080 #Обозначаем что будет слушать апач, в данном случае апач слушает на локалхосте порт 8080
LoadModule php7_module  /usr/lib/apache2/modules/libphp70.so #Указываем модуль и его расположение
AddHandler application/x-httpd-php .php #Указываем обработчик для файлов php
Options FollowSymLinks
AllowOverride None
Require all denied
AllowOverride None
Require all granted
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
AccessFileName .htaccess

Require all denied
LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %O" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent
IncludeOptional conf-enabled/*.conf
IncludeOptional sites-enabled/*.conf

Как видно из примера выше я поменять 4 строки в конфиге, все остальное стандартный конфиг для апача. Теперь можно запустить апач с кастомным конфигом:

root@debian:~# /usr/sbin/apachectl -f /etc/apache2/apache2-70.conf AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 127.0.1.1. Set the 'ServerName' directive globally to suppress this message

Ошибок нет, значит сервер запущен, проверим слушает ли апач порт:

root@debian:/home/ikky# netstat -anlp |grep 8080

tcp        0      0 127.0.0.1:8080          0.0.0.0:*               LISTEN      590/apache2

Теперь можно зайти через браузер и увидеть тестовую страницу апача.

1

Для остальных версий делаем аналогично. Теперь проверим php info. Для этого нужно создать файл с расширением php и следующим содержимым:

Теперь при обращении к странице с php info можно увидеть такую картину:

phpphp1

Так как Debian все делается через system V, а значит сервисы управляются утилитой systemctl, то для удобства я прописал для каждого апача с отдельной версией php свой сервис.

Apache + PHP 7.0

[Unit]
Description=The Apache HTTP Server
After=network.target remote-fs.target nss-lookup.target
Documentation=https://httpd.apache.org/docs/2.4/

[Service]
Type=forking
Environment=APACHE_STARTED_BY_SYSTEMD=true
ExecStart=/usr/sbin/apachectl -f /etc/apache2/apache2-70.conf -k start
ExecStop=/usr/sbin/apachectl -f /etc/apache2/apache2-70.conf -k stop
ExecReload=/usr/sbin/apachectl -f /etc/apache2/apache2-70.conf -k graceful
PrivateTmp=true
Restart=on-abort

[Install]
WantedBy=multi-user.target

Apache + PHP 7.2

[Unit]
Description=The Apache HTTP Server
After=network.target remote-fs.target nss-lookup.target
Documentation=https://httpd.apache.org/docs/2.4/

[Service]
Type=forking
Environment=APACHE_STARTED_BY_SYSTEMD=true
ExecStart=/usr/sbin/apachectl -f /etc/apache2/apache2-72.conf -k start
ExecStop=/usr/sbin/apachectl -f /etc/apache2/apache2-72.conf -k stop
ExecReload=/usr/sbin/apachectl -f /etc/apache2/apache2-72.conf -k graceful
PrivateTmp=true
Restart=on-abort

[Install]
WantedBy=multi-user.target

Apache + PHP 7.4

[Unit]
Description=The Apache HTTP Server
After=network.target remote-fs.target nss-lookup.target
Documentation=https://httpd.apache.org/docs/2.4/

[Service]
Type=forking
Environment=APACHE_STARTED_BY_SYSTEMD=true
ExecStart=/usr/sbin/apachectl -f /etc/apache2/apache2-74.conf -k start
ExecStop=/usr/sbin/apachectl -f /etc/apache2/apache2-74.conf -k stop
ExecReload=/usr/sbin/apachectl -f /etc/apache2/apache2-74.conf -k graceful
PrivateTmp=true
Restart=on-abort

[Install]
WantedBy=multi-user.target

Теперь можно запускать разные апачи вполне привычными командами

systemctl start|stop|restart apache70
systemctl start|stop|restart apache72
systemctl start|stop|restart apache74

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

#!/bin/bash
read -p "Change PHP version to 7.x: " version
case $version in
0) sed -i 's/proxy_pass\ http\:\/\/127.0.0.1\:808.\/\;/proxy_pass\ http\:\/\/127.0.0.1\:8080\/\;/g' /etc/nginx/conf.d/name.site.conf ;;
2) sed -i 's/proxy_pass\ http\:\/\/127.0.0.1\:808.\/\;/proxy_pass\ http\:\/\/127.0.0.1\:8082\/\;/g' /etc/nginx/conf.d/name.site.conf ;;
4) sed -i 's/proxy_pass\ http\:\/\/127.0.0.1\:808.\/\;/proxy_pass\ http\:\/\/127.0.0.1\:8084\/\;/g' /etc/nginx/conf.d/name.site.conf ;;
esac
systemctl reload nginx