Установка и настройка связки nginx и php-fpm

Nginx в силу своей архитектуры быстро обрабатывает запросы и отдает статичные данные, в результате чего проект завоевал свою нишу на рынке, вытеснив такого мамонта, как Apache HTTP Server. Наличие в nginx поддержки интерфейсов (CGI, FastCGI и т.п.)  позволило использовать его в связке с внешними приложениями, например, PHP, Perl, Python и другими. Описываемый в статье механизм не является новым и используется на высоконагруженных серверах уже давно. Статья написана в качестве заметки для себя и опубликована лишь только с той целью, что материал может оказаться полезным другим.

Итак, nginx является HTTP-сервером, а также умеет проксировать протоколы TCP,  UDP,  IMAP, POP3, HTTP и другие. Насколько мне известно, при написании nginx были использованы принципы событийно-ориентированного программирования, что и позволило добиться быстрой и эффективной обработки запросов с минимальными затратами  ресурсов.

Ранее применение nginx было оправдано прежде всего для статических веб-сайтов. Однако с популяризацией данного продукта во многих приложениях появились возможности для привязки nginx без дополнительных ухищрений. Так, в PHP с версии 5.3.3 включен менеджер процессов FastCGI (PHP-FPM), который управляет ресурсами, а также созданием и уничтожением процессов PHP. Таким образом, появилась возможность из коробки использовать nginx в связке с PHP без включения в цепочку Apache HTTP Server с mod_php. Из недостатков данной связки следует отметить отсутствие в nginx аналогичного .htaccess в Apache механизма, который поддерживается практическими всеми веб-приложениями и позволяет гибко переносить их конфигурацию. Из-за этого, настройка веб-сервера осуществляется только в конфигурационном файле nginx, а директивы, указанные в .htaccess, приходится переводить в указанный конфиг. Опять же, нет худа без добра, отказ от данного механизма положительно сказывается на производительности, в результате снижения количества запросов к носителю информации (в конце статьи есть ссылка на заметку с комментариями разработчиков по этому поводу).

В портах FreeBSD доступно три вариации nginx: lite, full и обычная, которые различаются набором компилируемых модулей. Я ставлю обычную версию со своим набором опций и из своего репозитория. Также устанавливаем PHP с включенной опцией FPM (в официальном репозитории пакетов она включена).

pkg install nginx php74

Далее, немного расскажу о иерархии директорий сайтов у меня на сервере. У каждого пользователя домашней директорией является /home/username/data с правами  0750, в которой есть следующие поддиректории: logs, www, tmp. В директории www хранятся сайты, logs   логи виртуальных хостов, а в tmp временные файлы PHP. Чтобы nginx мог получить к этим директориям доступ нужно добавить пользователя www в группу пользователя или можно воспользоваться ACL,ками, как сделал я: 

cd /home/h8/data

mkdir logs tmp www

chown www:www logs
chown h8:h8 tmp www
chmod 0755 logs
chmod 0750 tmp
chmod 0751 www

setfacl -m u:www:x /home/h8
setfacl -m u:www:x /home/h8/data
setfacl -m u::rwx,g::rx,o::---,u:www:rx /home/h8/data/www
setfacl -d -m u::rwx,g::rx,o::---,u:www:rx /home/h8/data/www

PHP-FPM будет порождать процессы PHP от конкретного пользователя, поэтому дополнительная настройка прав тут не требуется. Настройка PHP-FPM осуществляется посредством конфигурационного файла /usr/local/etc/php-fpm.conf, пулы PHP процессов настраиваются в отдельных конфигурационных файлах, размещаемых в /usr/local/etc/php-fpm.d. У меня настройки заданы следующим образом:
 

;;;;;;;;;;;;;;;;;;;;;
; Конфигурация FPM  ;
;;;;;;;;;;;;;;;;;;;;;

; Конфигурационный файл заполняется в формате INI файла.
; Все относительные пути задаются относительно директории установки PHP (/usr/local).
; Данный префикс может быть изменен через параметр командной строки -p.

;;;;;;;;;;;;;;;;;;;;;;;;
; Глобальные параметры ;
;;;;;;;;;;;;;;;;;;;;;;;;

[global]
; файл с идентификатором Pid
; Учтите: для данного параметра префикс: /var
; Значение по умолчанию: none
pid = run/php-fpm.pid

; Файл для записи ошибок
;  Если задано как "syslog", то логи будут направляться в syslogd, а не в локальный файл.
; Учтите: для данного параметра префикс: /var
; Значение по умолчанию: log/php-fpm.log
error_log = log/php-fpm.log

; Используется для указания, какой тип программ будет логировать сообщения.
; Смотрите ман syslog(3) для получения информации о доступных значениях.
; Значение по умолчанию: daemon
;syslog.facility = daemon

; Предшествует любому сообщению. Если у вас запущено несколько экземпляры FPM, 
; вы можете изменить значение по умолчанию на то, которое вам необходимо.
; Значение по умолчанию: php-fpm
;syslog.ident = php-fpm

; Уровень журналирования ошибок.
; Возможные значения: alert, error, warning, notice, debug
; Значение по умолчанию: notice
log_level = warning

; Максимальное количество символов в строке. 
; Если в сообщении будет превышен лимит символов, тогда оно будет разбито
; на несколько сообщений. Также учитываются символы, которые подставляются
; из prefix и suffix. However, the new line character does not count into it as it is present 
; only when logging to a file descriptor. It means the new line character is not present
; when logging to syslog.
; Значение по умолчанию: 1024
log_limit = 4096

; Переменная управляет поведением записи данных в файл.
; Если значение установлено в false , тогда данные записываются незамедлительно.
; Данный механизм является экспериментальным и разработан для 
; сокращения количества записей на диск и потребляемой памяти.
; Опция игнорируется, если лог ведется в syslog.
; Значение по умолчанию: yes
log_buffering = yes

; При указанном здесь количестве рабочих процессов, завершённых с SIGSEGV 
; или SIGBUS за промежуток времени, установленный emergency_restart_interval, 
; FPM будет перезагружен. Если параметр щадан в 0, тогда данный функционал
; не применяется.
; Значение по умолчанию: 0
;emergency_restart_threshold = 0

; Интервал времени, используемый emergency_restart_interval, чтобы определить, 
; когда FPM будет перезагружен. Это может быть необходимо для избежания случайных 
; повреждений в общей памяти ускорителя (accelerator). Доступные единицы измерения:
;  s (секунды), m (минуты), h (часы), или d (дни).
; Единица измерения по умолчанию: секунды. 
; Значение по умолчанию: 0 (Выключено). 
;emergency_restart_interval = 0

; Время, в течение которого дочерние процессы ожидают ответа на сигналы, 
; направленные  мастер-процесса.  Доступные единицы измерения: 
; s (секунды), m (минуты), h (часы) или d (дни). 
; Единица ; измерения по умолчанию: секунды.
; Значение по умолчанию: 0. 
process_control_timeout = 120s

; Максимальное количество процессов, которое может создать FPM. 
; Опция необходима, чтобы контролировать общее количество порожденных процессов.
; Используйте с осторожностью, с учетом имеющихся ресурсов сервера. 
; Учтите: если установлено в "0", значит ограничение отсутствует.
; Значение по умолчанию: 0
process.max = 512

; Приоритет (Unix nice(2)) мастер-процесса (только если установлено).
; Допустимые значения от -19 (максимальный приоритет) до 20 (минимальный).
; Учтите: будет работать, если мастер-процесс запускается под root. В данном случае
; дочерние процессы будут наследовать приоритет мастер-процесса.
; Значение по умолчанию: no set
process.priority = 19

; Запускать FPM в фоновом режиме. Если установлено в "no", 
; то FPM запустится в режиме отладки. 
; Значение по умолчанию: yes
daemonize = yes

; Максимальное разрешенное количество одновременно открытых дескрипторов
; файлов для мастер-процесса.
; Значение по умолчанию: взятое с системы
;rlimit_files = 1024

; Максимальный размер корки для мастер-процесса.
; Доступные значения: 'unlimited' или целочисленное значения большее либо равное 0
; Значение по умолчанию: взятое с системы
;rlimit_core = 0

; Механизм обработки событий. Доступные значения ( в зависимости от ОС):
; - select     (any POSIX os)
; - poll       (any POSIX os)
; - epoll      (linux >= 2.5.44)
; - kqueue     (FreeBSD >= 4.1, OpenBSD >= 2.9, NetBSD >= 2.0)
; - /dev/poll  (Solaris >= 7)
; - port       (Solaris >= 10)
; Значение по умолчанию: not set (auto detection)
events.mechanism = kqueue

; Если FPM собран с поддержкой systemd, тогда здесь возможно
; указать интервал времени в секундах, между оповещениями systemd 
; о своём состоянии.
; Установите в 0, чтобы отключить.
; Доступные единицы измерения: s (секунды), m (минуты), h (часы).
; Единица ; измерения по умолчанию: секунды
; Значение по умолчанию: 10
;systemd_interval = 10

;;;;;;;;;;;;;;;;;;;;
; Pool Definitions ;
;;;;;;;;;;;;;;;;;;;;

; Каждый пул процессов может быть запущен на своем IP и порту (либо unix сокете)
; с различными параметрами выполнения. Имя пула должно быть уникальным
; и будет использоваться в логах и статистике. Количество пулов неограниченно.
;  Your system will tell you anyway :)

; Подключите  один или несколько файлов.
; Include one or more files. If glob(3) exists, it is used to include a bunch of
; files from a glob(3) pattern. This directive can be used everywhere in the file.
; Допустимо использовать относительные пути. Префикс может быть таким:
;  - глобальный префикс, заданный через параметр -p
;  - либо /usr/local
include=/usr/local/etc/php-fpm.d/*.conf

Пример настройки пула /usr/local/etc/php-fpm.d/h8.conf:

; Объявляем новый пул с именем 'h8'.
; Переменная $pool может быть использована в любой директиве и будет заменена
; на имя пула, в данном случае на "h8".
[h8]
; Префикс для пула. Используется только в следующих параметрах:
; - 'access.log'
; - 'slowlog'
; - 'listen' (unixsocket)
; - 'chroot'
; - 'chdir'
; - 'php_values'
; - 'php_admin_values'
; В случае если префикс не задан, используется глобальный (или /usr/local).
; Учтите: значение директивы может быть относительным глобального префикса.
; Значение по умолчанию: none
prefix = /home/$pool/data

; Имя и группа, под которыми будет работать процессы в пуле.
; Учтите: указание директивы user обязательно. Если директива group не задана,
; тогда будет использована основная группа пользователя, указанного в user.
user = h8
group = h8

; Адрес, на котором пул процессов будет принимать запросы.
; Допустимые значения:
;   'ip.add.re.ss:port'    - прием посредством TCP на конкретных IPv4 и порту
;   '0.0.0.0:port'         - прием посредтсвом TCP на любом IPv4 и конкретном порту
;   '[ip:6:addr:ess]:port' - прием посредством TCP на конкретных IPv6 и порту
;   'port'                 - прием посредством TCP на любых (IPv6 and IPv4-mapped) и конкретном порту
;                            Учтите: IPv4-mapped addresses are disabled by-default in
;                                  FreeBSD for security reasons;
;   '/path/to/unix/socket' - прием посредством unix-сокета.
; Учтите: значение указывается в обязательном порядке.
listen = /var/run/h8.sock

; В случае использования unix-сокета указываем права доступа к нему. В Linux, права
; read/write должны быть установлены таким образом, чтобы мог подключиться веб-сервер.
; Некоторые BSD системы допускают подключение независимо от прав доступа. Владелец
; сокета может быть указан по имени name либо ID.
; Значение по уполчанию: user и group мастер-процесса и права доступа 0660
listen.owner = h8
listen.group = www
listen.mode = 0660

; В случае наличия поддержки POSIX Access Control Lists возможно задать
; через запятую user/group. Если этот параметр задан, тогда listen.owner
; и listen.group игнорируются.
;listen.acl_users =
;listen.acl_groups =

; Приоритет (Unix nice(2)) для процессов в пуле. Допустимые значения
; от -19 (максимальный приоритет) до 20 (минимальный).
; Учтите: будет работать, если мастер-процесс запускается под root. В данном случае
; дочерние процессы будут наследовать приоритет мастер-процесса, если параметр не задан.
; Значение по умолчанию: no set
process.priority = 19

; Установить флаг процесса dumpable (PR_SET_DUMPABLE prctl), даже если пользователь
; процесса или группа отличается от пользователя мастер-процесса. Указанное позволяет
; создавать дамп памяти процесса и выполнить его ptrace.
; Значение по умолчанию: no.
process.dumpable = no

; Здесь задается способ порождения процессов.
; Допустимые значения:
;   static  - фиксированное число дочерних процессов (pm.max_children)
;   dynamic - переменное число дочерних процессов, задаётся на основании следующих директив.
;             (With this process management, there will be always at least 1 children)
;             pm.max_children
;             pm.start_servers 
;             pm.min_spare_servers
;             pm.max_spare_servers 
;  ondemand - дочерние процессы создаются только по требованию, задаётся
;             на основании следующих директив:
;             pm.max_children
;             pm.process_idle_timeout
; Учтите: задание параметра является обязательным.
pm = dynamic

; Количество процессов, создаваемых в режиме "static", а в случае режимов
; "dynamic" или "ondemand" :nbsp; максимальное количество процессов, 
; работающих одновременно. Фактически данный параметр ограничивает
; количество одновременно обрабатываемых запросов.
; Действие параметра аналогично директиве ApacheMaxClients сервера Apache
; с MPM модудем mpm_prefork и аналогично переменной окружения PHP_FCGI_CHILDREN 
; в оригинальном PHP CGI. Значение по умолчанию выбрано без учета мощности оборудования.
; Необходимо настраивать параметры pm.* под свои нужды.
; Учтите: указание значения обязательно.
pm.max_children = 7

; Количество процессов в пуле, создаваемых на этапе запуска.
; Учтите: оказывает влияние только в режиме 'dynamic'.
; Значение по умолчанию: (min_spare_servers + max_spare_servers) / 2
; pm.start_servers = 1

; Минимальное количество неактивных (простаивающих) процессов пула.
; Используется только, когда pm установлено в "dynamic".
; Учтите: указание значения обязательно.
pm.min_spare_servers = 1

; Макималное количество неактивных (простаивающих) процессов пула.
; Используется только, когда pm установлено в "dynamic".
; Учтите: указание значения обязательно.
pm.max_spare_servers = 5

; Время в секундах, по истечению которого неактивный (простаивающий)
; процесс будет уничтожен.
; Используется только, когда pm установлено в "ondemand".
; Значение по уполчанию: 10s
;pm.process_idle_timeout = 10s;

; Количество обработанных каждым процессом из пула запросов, после
; которого процесс будет уничтожен и создан заного. Данный функционал может
; быть полезен для профилактики утечки памяти в случае наличия багов в сторонних
; библиотеках. Отключение функции осуществляется установкой параметра в 0.
; По своей сути функционал аналогичен PHP_FCGI_MAX_REQUESTS.
; Значение по умолчанию: 0
pm.max_requests = 1000

; Путь до файла, в который будут записываться данные об обработанных запросах (лог доступа).
; Значение по умолчанию: not set
;access.log = log/$pool.access.log

; Формат записей в лог доступа.
; Допустим следующий синтаксис:
;  %%: the '%' character
;  %C: %CPU used by the request
;      it can accept the following format:
;      - %{user}C for user CPU only
;      - %{system}C for system CPU only
;      - %{total}C  for user + system CPU (default)
;  %d: time taken to serve the request
;      it can accept the following format:
;      - %{seconds}d (default)
;      - %{miliseconds}d
;      - %{mili}d
;      - %{microseconds}d
;      - %{micro}d
;  %e: an environment variable (same as $_ENV or $_SERVER)
;      it must be associated with embraces to specify the name of the env
;      variable. Some exemples:
;      - server specifics like: %{REQUEST_METHOD}e or %{SERVER_PROTOCOL}e
;      - HTTP headers like: %{HTTP_HOST}e or %{HTTP_USER_AGENT}e
;  %f: script filename
;  %l: content-length of the request (for POST request only)
;  %m: request method
;  %M: peak of memory allocated by PHP
;      it can accept the following format:
;      - %{bytes}M (default)
;      - %{kilobytes}M
;      - %{kilo}M
;      - %{megabytes}M
;      - %{mega}M
;  %n: pool name
;  %o: output header
;      it must be associated with embraces to specify the name of the header:
;      - %{Content-Type}o
;      - %{X-Powered-By}o
;      - %{Transfert-Encoding}o
;      - ....
;  %p: PID of the child that serviced the request
;  %P: PID of the parent of the child that serviced the request
;  %q: the query string
;  %Q: the '?' character if query string exists
;  %r: the request URI (without the query string, see %q and %Q)
;  %R: remote IP address
;  %s: status (response code)
;  %t: server time the request was received
;      it can accept a strftime(3) format:
;      %d/%b/%Y:%H:%M:%S %z (default)
;      The strftime(3) format must be encapsuled in a %{<strftime_format>}t tag
;      e.g. for a ISO8601 formatted timestring, use: %{%Y-%m-%dT%H:%M:%S%z}t
;  %T: time the log has been written (the request has finished)
;      it can accept a strftime(3) format:
;      %d/%b/%Y:%H:%M:%S %z (default)
;      The strftime(3) format must be encapsuled in a %{<strftime_format>}t tag
;      e.g. for a ISO8601 formatted timestring, use: %{%Y-%m-%dT%H:%M:%S%z}t
;  %u: remote user
;
; Значение по умолчанию: "%R - %u %t \"%m %r\" %s"
;access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%"

; Путь до файла, в который будут записываться данные о медленных запросах.
; Значение по умолчанию: not set
; Учтите: задание параметра обязательно при указании параметра request_slowlog_timeout
;slowlog = log/$pool.log.slow

; Время, после которого выполняемых запрос будет считаться медленным, в результате чего
; данные об этом будут записаны в файл 'slowlog'. Значение '0' отключает данный функционал.
; Доступные единицы измерения: s (секунды, по умолчанию), m (минуты), h (часы) или d (дни).
; Значение по умолчанию: 0
;request_slowlog_timeout = 0

; Информативность (глубина) записей в slowlog.
; Значение по умолчанию: 20
;request_slowlog_trace_depth = 20

; Максимальное время обработки запроса, при превышении которого
; процесс будет уничтожен. Данный параметр может оказаться полезным,
; в случае если PHP параметр 'max_execution_time' не сработает по какой-либо
; причине. Значение в 0 отключает данный функционал.
; Доступные единицы измерения: s (секунды, по умолчанию), m (минуты), h (часы) или d (дни).
; Значение по уполчанию: 0
;request_terminate_timeout = 0

; The timeout set by 'request_terminate_timeout' ini option is not engaged after
; application calls 'fastcgi_finish_request' or when application has finished and
; shutdown functions are being called (registered via register_shutdown_function).
; This option will enable timeout limit to be applied unconditionally
; even in such cases.
; Default Value: no
;request_terminate_timeout_track_finished = no

; Вы полнить chroot() в указанную здесь директорию после запуска процесса.
; Путь к директории должен быть полным (абсолютным). Если значение не задано,
; тогда chroot() не применяется. 
; Учтите: возможно использовать пеерменную $prefix, указанную ранее для пула.
; Если префикс для пула не указывался, тогда будет использован глобальный префикс.
; Учтите: chroot является мощным интрументом безопасности и должен
; использоваться во всевозможных случаях. Однако следует учитывать,
; что все пути для PHP будут относительны указанной здесь директории
;       (error_log, sessions.save_path, ...).
; Значение по умолчанию: not set
;chroot =

; Выполнить chdir в директорию после старта.
; Учтите: может быть указан относительный путь.
; Значение по уполчанию:  текущая директория или / при использовании chroot. 
;chdir = /var/www

; Перенаправление STDOUT и STDERR рабочего процесса в главный лог ошибок.
; Если не установлен, тогда вывод STDOUT и STDERR будет перенаправлен в /dev/null
;  в соответствии со спецификацией FastCGI.
; Note: on highloaded environement, this can cause some delay in the page
; process time (several ms).
; Значение по умолчанию: no. 
;catch_workers_output = yes

; Decorate worker output with prefix and suffix containing information about
; the child that writes to the log and if stdout or stderr is used as well as
; log level and time. This options is used only if catch_workers_output is yes.
; Settings to "no" will output data as written to the stdout or stderr.
; Default value: yes
;decorate_workers_output = no

; Удалить переменные окружения для процессов из пула, таким образом
; предотвратив утечку чувствительных данных через указанные переменные
; Установка в "no" будет означать, что к информации из переменных окружения,
; действовавших на этапе создания процесса, можно будет получить доступ
; в скриптах через getenv(), $_ENV и $_SERVER.
; Значение по уполчанию: yes
clear_env = yes

; Здесь возможно указать расширения файлов, которые PHP-FPM
; будет воспринимать в качестве скриптов. Данный механизм
; поможет обезопасить сервер при наличии упущений в конфигурации
; веб-сервера (например, всем известная фишка с cgi.fix_path_info).
; Следует ограничить FPM обрабатывать файлы только с расширеним .php
; для исключения подстановки вредоносного когда через файлы
; с иными расширениями.
; Учтите: оставьте значение пустым, чтобы разрешить FPM выполнять любые файлы
; Значение по уполчанию: .php
security.limit_extensions = .php .php3 .php4 .php5 .php7

; Задать переменные окружения для скриптов.. Все $VARIABLEs будут
; взяты из исходного окружения.
; Значение по умолчанию: clean env
;env[HOSTNAME] = $HOSTNAME
env[PATH] = /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:/root/bin
env[TMP] = /home/h8/data/tmp
env[TMPDIR] = /home/h8/data/tmp
env[TEMP] = /home/h8/data/tmp

; Additional php.ini defines, specific to this pool of workers. These settings
; overwrite the values previously defined in the php.ini. The directives are the
; same as the PHP SAPI:
;   php_value/php_flag             - you can set classic ini defines which can
;                                    be overwritten from PHP call 'ini_set'.
;   php_admin_value/php_admin_flag - these directives won't be overwritten by
;                                     PHP call 'ini_set'
; For php_*flag, valid values are on, off, 1, 0, true, false, yes or no.

; Defining 'extension' will load the corresponding shared extension from
; extension_dir. Defining 'disable_functions' or 'disable_classes' will not
; overwrite previously defined php.ini values, but will append the new value
; instead.

; Note: path INI options can be relative and will be expanded with the prefix
; (pool, global or /usr/local)

; Default Value: nothing is defined by default except the values in php.ini and
;                specified at startup with the -d argument

php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f admin@example.ru
php_value[error_reporting] = E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
php_flag[display_errors] = Off
php_value[default_charset] = "UTF-8"
php_flag[log_errors] = On
php_admin_value[error_log] = /home/h8/data/logs/php-errors.log
php_admin_value[max_input_time] = 120
php_admin_value[max_execution_time] = 120
php_admin_value[memory_limit] = 256M
php_admin_value[upload_max_filesize] = 16M
php_admin_value[post_max_size] = 32M
php_value[date.timezone] = Europe/Moscow
php_admin_value[open_basedir] = .:/home/h8/data/:/usr/local/share/pear/:/dev/urandom
php_admin_value[upload_tmp_dir] = /home/h8/data/tmp
php_admin_value[session.save_path] = /home/h8/data/tmp
php_admin_flag[session.bug_compat_42] = Off
php_admin_flag[session.bug_compat_warn] = Off
php_admin_value[session.gc_divisor] = 1000
php_admin_value[session.hash_bits_per_character] = 5
php_admin_flag[expose_php] = Off
php_admin_value[variables_order] = GPCS
php_admin_value[request_order] = GP
php_admin_flag[register_argc_argv] = Off
php_admin_value[auto_globals_jit] = On

; Drupal config from .htaccess
php_admin_value[assert.active] = 0
php_admin_flag[session.auto_start] = off
php_admin_value[mbstring.http_input] = pass
php_admin_value[mbstring.http_output] = pass
php_admin_flag[mbstring.encoding_translation] = off
; PHP 5.6 has deprecated $HTTP_RAW_POST_DATA and produces warnings
; if this is not set.
php_admin_value[always_populate_raw_post_data] = -1

php_admin_value[opcache.enable] = 1
php_admin_value[opcache.memory_consumption] = 128
php_admin_value[opcache.interned_strings_buffer] = 16
php_admin_value[opcache.max_accelerated_files] = 8000
php_admin_value[opcache.revalidate_freq] = 60
php_admin_value[opcache.save_comments] = 1
php_admin_value[opcache.fast_shutdown] = 1
php_admin_value[opcache.enable_cli] = 1

php_admin_value[apc.enabled] = 0

php_flag[verify_peer] = off
php_flag[verify_peer_name] = off

Стоит отметить одну особенность работы PHP-FPM, а именно невозможность загрузки различных модулей расширения PHP для пулов. То есть модули расширения загружаются однократно мастер-процессом и далее видны всем дочерним процессам. Если в какой-то ситуации необходимо создать пулы с различным набором подгруженных модулей , тогда целесообразно запускать несколько мастер-процессов с разной конфигурацией. Другим более простым вариантом является отключение функционала модулей через php_admin_value.
Итак, после настройки конфигов для PHP-FPM стоит попробовать его запустить, для чего делаем следующее:

echo 'php_fpm_enable="YES"' >> /etc/rc.conf
service php-fpm start
ps -ax | grep php
5302  -  INJ  7:01,56 php-fpm: pool h8 (php-fpm)
5303  -  INJ  7:07,16 php-fpm: pool h8 (php-fpm)
18592  -  INJ  0:35,04 php-fpm: pool h8 (php-fpm)
25324  -  INJ  0:20,32 php-fpm: pool h8 (php-fpm)
29026  -  INJ  5:26,86 php-fpm: pool h8 (php-fpm)
65536  -  SNsJ 0:06,70 php-fpm: master process (/usr/local/etc/php-fpm.conf) (php-fpm)

Если при запуске PHP-FPM не выкинул никаких ошибок, тогда его настройку можно считать завершенной. Теперь давайте настроим nginx, его основной конфигурационный файл находится тут /usr/local/etc/nginx/nginx.conf. Далее, приведу содержимое указанного файла с комментариями по ходу следования параметров:

# Задаём пользователя и группу, с правами которого будут работать рабочие процессы. 
# Если группа не задана, то используется группа, имя которой совпадает с именем пользователя. 
user  www;
# Задаёт число рабочих процессов. Оптимально значение равное количеству ядер в системе.
# Также возможно указать значение auto.
worker_processes  4;

# У меня nginx скомпилирован с модулем GeoIP2, поэтому я его тут подгружаю
load_module /usr/local/libexec/nginx/ngx_http_geoip2_module.so;

# This default error log path is compiled-in to make sure configuration parsing
# errors are logged somewhere, especially during unattended boot when stderr
# isn't normally logged anywhere. This path will be touched on every nginx
# start regardless of error log location configured here. See
# https://trac.nginx.org/nginx/ticket/147 for more info.
#
error_log  /var/log/nginx/error.log;

events {
 # Задаёт метод, используемый для обработки соединений (если не указан, 
 # то nginx сам выбирает наиболее эффективный метод). 
 use kqueue;
 # Задаёт максимальное число соединений, которые одновременно может открыть рабочий процесс. 
 worker_connections  1024;
}

http {
 # Разрешает или запрещает выдавать версию nginx’а на страницах ошибок
 # и в поле “Server” заголовка ответа. 
 server_tokens off;

 include       mime.types; # Подключаем файлик со сведениями о MIME.
 default_type  application/octet-stream; # все файлы с неизвестным MIME будем трактовать как бинарник

 # На официальном сайте к nginx есть описание всех доступных переменных
 log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
             '$status $body_bytes_sent "$http_referer" '
             '"$http_user_agent" "$http_x_forwarded_for"';

 # Параметры SSL
 # Время, в течение которого nginx будет хранить параметры SSL сессии клиента
 ssl_session_timeout 4h; 
 # Кэшируем SSL сесси для того чтобы сократить количество рукопожатий SSL с клиентами,
 # таким образом снижая нагрузку на процессор.Тип и размеры кэшей для хранения
 # параметров SSL сессий (20m ~ 80000 сессий)
 ssl_session_cache shared:nginxSSL:20m;
 ssl_session_tickets off; # Запрещаем возобновление сессий при помощи TLS session tickets. 
 ssl_prefer_server_ciphers off; # В данном случае клиентские шифры более приоритетны, чем серверные.

 # Данные параметры получены в генераторе от Mozilla (в конце статьи  есть ссылка)
 ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
 ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
 ssl_dhparam dhparam.pem; # создан командой: openssl dhparam -out dhparam.pem 2048

 sendfile        on;
 #tcp_nopush     on;

 # TCP options
 # Ничего особенного, подбираются индивидуально 
 keepalive_requests 1000;
 keepalive_time 5m;
 keepalive_timeout 65s;

 # GeoIP options
 geoip2 /usr/local/share/GeoIP/GeoLite2-Country.mmdb {
   auto_reload 1d; # через какой время nginx будт перезагружать базу

   # $variable_name metadata <field>
   # Доступные значения:
   #   build_epoch: время создания ьазы данных maxmind.
   #   last_check: время, когда  база последний раз была проверена на предмет изменений (when using auto_reload)
   #   last_change: время, когда  база последний раз была перезагружена (when using auto_reload)

   #$geoip2_metadata_country_build metadata build_epoch;

   # $variable_name [default=<value] [source=$variable_with_ip] path ...
   # Структуру базы можно подглядеть так:  mmdblookup --file /path/to/GeoLite2-Country.mmdb --ip 127.0.0.1
   $geoip2_data_continent_code continent code;
   $geoip2_data_country_code country iso_code;
   $geoip2_data_country_name country names en;
 }

 geoip2 /usr/local/share/GeoIP/GeoLite2-City.mmdb {
   auto_reload 1d;
   $geoip2_data_city_name city names en;
   $geoip2_data_region subdivisions 0 names en;
   $geoip2_data_location_latitude location latitude;
   $geoip2_data_location_longitude location longitude;
 }

 # Все виртуальные хосты у меня описаны в конфигурационных файлах в этой поддиректории
 include vhosts/*.conf;
}

В директории /usr/local/etc/nginx имеется файл fastcgi_params, в котором задаются переменные окружения, передаваемые клиенту. Я его немного подредактировал с учетом наличия модуля GeoIP2, а также убрал вывод версии nginx и добавил переменную SCRIPT_FILENAME.

fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  PATH_INFO          $fastcgi_path_info;
fastcgi_param  PATH_TRANSLATED    $document_root$fastcgi_path_info;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;
fastcgi_param  REQUEST_SCHEME     $scheme;
fastcgi_param  HTTPS              $https if_not_empty;

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param  REDIRECT_STATUS    200;

# GeoIP
fastcgi_param  COUNTRY_CODE       $geoip2_data_country_code;
fastcgi_param  COUNTRY_NAME       $geoip2_data_country_name;
fastcgi_param  CITY_NAME          $geoip2_data_city_name;
fastcgi_param  REGION_NAME        $geoip2_data_region;
fastcgi_param  LOCATION_LATITUDE  $geoip2_data_location_latitude;
fastcgi_param  LOCATION_LONGITUDE $geoip2_data_location_longitude;

Ну и теперь настраиваем виртуальный хост:

server {
 listen       80;
 server_name  example.org;
 index index.php;
 root /home/h8/data/www/example.org; # директория, содержащая веб-приложение
 charset utf-8; # кодировка по умолчанию
 autoindex off; # запрещаем вывод списка каталогов.

 # Пути до лог файлов
 error_log   /home/h8/data/logs/example.org.error.log;
 access_log  /home/h8/data/logs/example.org.access.log  main;

 # HTTP response headers borrowed from Drupal `.htaccess`
 add_header X-Content-Type-Options               "nosniff"       always;

 # Включаем сжатие gzip ( but do not remove ETag headers)
 gzip on;
 gzip_vary on;
 gzip_comp_level 4;
 gzip_min_length 256;
 gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
 gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;

 location = /robots.txt {
   allow all;
   log_not_found off;
   access_log off;
 }

 location = /favicon.ico {
   log_not_found off;
   access_log off;
 }

 location ~ \..*/.*\.php$                     { return 403; }
 location ~ ^/sites/.*/private/               { return 403; }
 # Block access to scripts in "site" "files" directory
 location ~ ^/sites/[^/]+/files/.*\.php$      { deny all; }
 # Allow "Well-Known URIs" as per RFC 5785
 location ~* ^/.well-known/                   { allow all; }
 # Deny access to .htaccess files, if Apache's document root
 # concurs with nginx's one
 location ~ /\.ht { deny  all; }

 location / {
   # try_files $uri @rewrite; # For Drupal <= 6
   try_files $uri /index.php?$query_string; # For Drupal >= 7
 }

 # Block access to "hidden" files and directories whose names begin with a
 # period. This includes directories used by version control systems such
 # as Subversion or Git to store control files.
 location ~ (^|/)\.                           { return 403; }

 # Don't allow direct access to PHP files in the vendor directory.
 location ~ /vendor/.*\.php$ {
   deny all;
   return 404;
 }

 # Protect files and directories from prying eyes.
 location ~* \.(engine|inc|install|make|module|profile|po|sh|.*sql|theme|twig|tpl(\.php)?|xtmpl|yml)(~|\.sw[op]|\.bak|\.orig|\.save)?$|^(\.(?!well-known).*|Entries.*|Repository|Root|Tag|Template|composer\.(json|lock)|web\.config)$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig|\.save)$ {
   deny all;
   return 404;
 }

 location @rewrite {
   #rewrite ^/(.*)$ /index.php?q=$1; # For Drupal <= 6
   rewrite ^ /index.php; # For Drupal >= 7
 }

 # Workaround Drupal bug #2583799 - https://www.drupal.org/node/2583799
 rewrite ^/core/authorize.php/core/authorize.php(.*) /core/authorize.php?$1 permanent;

 # In Drupal 8, we must also match new paths where the '.php' appears in
 # the middle, such as update.php/selection. The rule we use is strict,
 # and only allows this pattern with the update.php front controller.
 # This allows legacy path aliases in the form of
 # blog/index.php/legacy-path to continue to route to Drupal nodes. If
 # you do not have any paths like that, then you might prefer to use a
 # laxer rule, such as:
 #   location ~ \.php(/|$) {
 # The laxer rule will continue to work if Drupal uses this new URL
 # pattern with front controllers other than update.php in a future
 # release.
 location ~ '\.php$|^/update.php' {
   fastcgi_split_path_info ^(.+?\.php)(|/.*)$;

   # Ensure the php file exists. Mitigates CVE-2019-11043
   try_files $fastcgi_script_name =404;

   # Security note: If you're running a version of PHP older than the
   # latest 5.3, you should have "cgi.fix_pathinfo = 0;" in php.ini.
   # See http://serverfault.com/q/627903/94922 for details.

   include fastcgi_params;
   # Block httpoxy attacks. See "HTTPOXY vulnerability".
   fastcgi_param HTTP_PROXY "";
   fastcgi_hide_header X-Powered-by;

   fastcgi_intercept_errors on;
   fastcgi_pass unix:/var/run/h8.sock;
 }

 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|ttf|woff)$ {
   try_files $uri @rewrite;
   expires max;
   log_not_found off;
 }

 # Fighting with Styles? This little gem is amazing.
 # location ~ ^/sites/.*/files/imagecache/ { # For Drupal <= 6
 location ~ ^/sites/.*/files/styles/ { # For Drupal >= 7
   try_files $uri @rewrite;
 }

 # Handle private files through Drupal. Private file's path can come
 # with a language prefix.
 location ~ ^(/[a-z\-]+)?/system/files/ { # For Drupal >= 7
   try_files $uri /index.php?$query_string;
 }

 # Enforce clean URLs
 # Removes index.php from urls like www.example.com/index.php/my-page --> www.example.com/my-page
 # Could be done with 301 for permanent or other redirect codes.
 if ($request_uri ~* "^(.*/)index\.php/(.*)") {
   return 307 $1$2;
 }
}

Ну что же, пора запускать веб-сервер, не забыв проверить перед его запуском конфиг на предмет отсутствия синтаксических ошибок:

nginx -t
nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok
nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful

echo 'nginx_enable="YES"' >> /etc/rc.conf
service nginx start

ps -ax | grep nginx
50889  -  SJ   0:05,94 nginx: worker process (nginx)
50890  -  SJ   0:11,25 nginx: worker process (nginx)
50891  -  SJ   0:18,07 nginx: worker process (nginx)
50892  -  SJ   0:24,08 nginx: worker process (nginx)
64479  -  IsJ  0:00,21 nginx: master process /usr/local/sbin/nginx

Работает, а если нет, то смотрим логи. Добавлю, что как следует из конфига виртуального хоста nginx, он создан для сайта на движке Drupal. С учетом отсутствия в nginx поддержки обработки правил .htaccess, то по сравнению с Apache количество строк в конфиге виртуального хоста получилось примерно в 10 раз больше. В качестве образца я использовал пример конфига виртуального хоста для Drupal с официальной wiki nginx. На данном сайте есть множество полезных примеров настройки хостов для большинства популярных CMS и других веб-приложений. В случае отсутствия какого-либо примера, то разбираемся сами либо смотрим в документацию разработчика веб-приложения. Например, для Nextcloud пример конфигурирования виртуальног хоста можно посмотреть в его официальных доках.

Добавить комментарий

CAPTCHA
Этот вопрос задается для того, чтобы выяснить, являетесь ли Вы человеком или представляете из себя автоматическую спам-рассылку.
Яндекс.Метрика