Содержание статьи
Запускаем сайты от разных пользователей в связке nginx + PHP-FPM
Сегодня нередко берут VDS в складчину и на одном сервере размещают свои сайты несколько пользователей. В итоге получается дешевле при большей суммарной мощности сервера на один сайт. Или как вариант: к серверу, помимо админа, нужен доступ разработчику для сопровождения сайта. Осталось обеспечить всем возможность доступа только к своим файлам, но таким образом, чтобы пользователи не могли прочитать файлы друг друга.
Если физический доступ к файлам легко настраивается с помощью стандартной системы прав *nix и домашних каталогов, то с веб-сайтами чуть сложнее. В Apache для решения этой задачи прибегают к suEXEC или suPHP, которые позволяют запускать процессы от имени нужной учетной записи. При установке стандартной связки LEMP используется один пул PHP-FPM, обрабатывающий все PHP-скрипты для всех сайтов от имени одной учетной записи (обычно совпадающей с той, под которой работает веб-сервер).
Это создает несколько проблем. Пользователи не могут нормально и безопасно работать только со своими сайтами, ведь для доступа придется включать всех в группу веб-сервера. Даже с очень строгими ограничениями в этом случае можно получить доступ ко всем сайтам. Если нельзя напрямую зайти в каталог, то делается симлинк на своем сайте, и можно читать чужие файлы через веб-сервер. Скомпрометированный сайт может служить проблемой для всех остальных приложений на этом сервере. Зараженные мини-хостинги — это, поверь, далеко не редкость. Хакер, взломав один, может получить доступ к файлам конфигурации и БД абсолютно всех.
При использовании nginx доступ разграничивают, создавая отдельные PHP-FPM-пулы для каждого пользователя. Процесс при этом запускается с правами конкретного пользователя, и он будет без проблем редактировать свои файлы в FTP-клиенте, не рискуя, что кто-то еще может к ним подобраться. Создаем учетную запись и подкаталоги для работы:
$ sudo adduser example
$ mkdir -p /home/example/example.org/{tmp,logs}
Единственный момент: если используются домашние каталоги пользователей, то веб-сервер и PHP должны получать доступ на чтение списка файлов и к каталогам выше (как минимум право на выполнение — х
). Традиционно пулы PHP располагаются в каталоге /etc/php5/fpm/pool.d
. Сам каталог подключается в /etc/php5/fpm/php-fpm.conf
инструкцией include
(она бывает закомментирована):
include=/etc/php5/fpm/pool.d/*.conf
После установки внутри обычно находится один файл www.conf, настройки которого и используются всеми процессами. Его можно взять как шаблон, скопировав и изменив параметры:
$ cd /etc/php5/fpm/pool.d
$ sudo cp www.conf example.org.conf
Правим под новый сайт:
$ sudo nano example.org.conf
[example.org]
listen = /var/run/php5-example.org.sock
listen.mode = 0664
# Пользователь и группа, под которыми будет работать пул
user = example
group = example
# По умолчанию сокет работает под теми же учетками, что указаны в user/group, nginx должен его читать
# Иногда нужно использовать другие учетные данные
listen.owner = nginx
listen.group = nginx
chdir = /home/example/example.org
error_log = /home/example/example.org/logs.азь-php.error.log
# Под планируемую нагрузку проставляем количество процессов
pm = dynamic
pm.max_children = 10
pm.start_servers = 3
pm.min_spare_servers = 3
pm.max_spare_servers = 6
И при необходимости указываем специфические для сайта установки PHP:
php_admin_value[session.save_path] = /home/example/example.org/tmp
php_admin_value[open_basedir] = "/home/example/example.org/"
php_admin_value[post_max_size] = 100M
php_admin_value[cgi.fix_pathinfo] = 0
Теперь процесс фактически заперт внутри каталога, с четко установленными правами. Все параметры файла можно найти в документации.
Настройки сайта для nginx в целом стандартные. Необходимо лишь указать сокет, который будет использоваться для обработки PHP:
$ sudo /etc/nginx/sites-available/example.org.conf
server {
listen 80;
server_name example.org;
root /home/example/example.org/;
...
location ~ \.php$ {
include /etc/nginx/fastcgi_params;
fastcgi_pass unix://var/run/php5-example.org.sock;
}
...
}
$ ln -s sudo /etc/nginx/sites-available/example.org.conf /etc/nginx/sites-enabled/example.org.conf
Перезапускаем PHP-FPM и nginx:
$ sudo /etc/init.d/php5-fpm restart
$ sudo /etc/init.d/nginx reload
Если вместо сокета нужно использовать сетевое соединение, то для каждого пула указывается отдельный сетевой порт:
$ sudo nano example.org.conf
[example.org]
listen = 127.0.0.1:9001
...
$ sudo /etc/nginx/sites-available/example.org.conf
location ~ \.php$ {
include /etc/nginx/fastcgi_params;
fastcgi_pass 127.0.0.1:9001;
...
}
Осталось залить на сервер файлы и установить права: 640
на файлы и 750
на каталог.
Настраиваем WordPress в подпапке домена nginx
Нередко портал использует несколько CMS, доступ к которым организован из меню Landing Page. При размещении в поддомене с ссылкой вроде blog.example.org проблем нет, настраивается это стандартными правилами. А в случае использования подкаталога (http://example.org/blog
) стандартные установки уже не подходят.
Разберем на примере WordPress. В инструкции на сайте WP при таком расположении предлагается переместить index.php
и .htaccess
из каталога с WordPress в корневой каталог сайта и указать в index.php
новое расположение сайта. Вместо
require('./wp-blog-header.php');
вписать новый путь:
require('./blog/wp-blog-header.php');
Загвоздка в том, что в корневом каталоге уже может быть такой файл от основного сайта или нужно подключать несколько CMS со своими ссылками. В Apache это не проблема, а вот в nginx придется чуть отойти от стандартной конфигурации.
В начале идет основной сайт. Здесь все как обычно:
server {
server_name .example.org;
root /var/www/;
...
}
Блог на WP к основному сайту подключается как location. В параметре root указываем полный путь к каталогу. В случае nginx нет ничего плохого в размещении root-каталога внутри location. Для проверки наличия файлов в nginx есть очень полезная инструкция try_files
, которая просматривает существование файлов в указанном порядке и при первом совпадении использует его для обработки. Обработка делается в контексте этого же location в соответствии с директивами root и alias. Если в конце имени указать слеш, то проверяется каталог (например, $uri/
). Если совпадения не найдены, то делается внутреннее перенаправление на uri, заданное последним параметром.
Переменная $uri
, используемая в конфигурации, указывает на текущий URI запроса в нормализованном виде, при обработке запроса его значение может изменяться. $uri вообще очень полезная директива, при помощи которой можно перенаправлять запросы, блокировать доступ к файлам, перенаправлять на 404, если файла нет, и многое другое.
location ^~ /blog {
root /var/www/blog;
index index.php;
try_files $uri $uri/ /blog/index.php?$args;
access_log /var/log/nginx/blog.access.log;
}
location ~* .php$ {
include fastcgi_params;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass 127.0.0.1:9000;
}
Если сайт расположен в пределах корневого каталога веб-сайта, такая схема работает без проблем. Но если location находится вне корневого каталога веб-сервера (что, кстати, очень не рекомендуют сами разработчики), то у него не будет доступа к корневому каталогу. То есть описанная конфигурация работать не станет. Например, не будут грузиться картинки или стили, и нужно дополнительно указать веб-серверу, где их искать.
Основная часть кода остается без изменений, правим только ту, что касается самого блога:
location /blog {
root /home/blog/;
index index.php;
try_files $uri $uri/ /blog/index.php?$args;
access_log /var/log/nginx/blog.access.log;
error_log /var/log/nginx/blog.error.log;
location ~* ^/blog/(.+\.(jpg|jpeg|gif|css|png|js|ico|html|xml|txt|woff|ttf))$ {
root /home/blog/;
}
}
Ставим бесплатный SSL-сертификат от Let’s Encrypt на nginx
Не так давно использование SSL-шифрования считалось просто фишкой отдельных сайтов и применялось только на тех ресурсах, где в этом действительно была необходимость. Теперь это уже почти обязательное требование. Google, например, повышает в рейтингах сайты с включенным SSL, поэтому сегодня все больше владельцев переводят свои ресурсы на этот протокол.
Сгенерировать сертификат можно и самому:
$ sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/nginx.key -out /etc/nginx/ssl/nginx.crt
Только вот самоподписанный сертификат будет, наоборот, отпугивать посетителей сообщением браузера о том, что владельца проверить нельзя. Поэтому такой вариант подходит больше для внутренних ресурсов.
Сертификат можно купить. Некоторые хостеры дают его «бесплатно» в старших тарифных планах. Но есть еще один вариант: относительно молодой проект Let’s Encrypt предлагает бесплатно общедоступные сертификаты, которым доверяют большинство браузеров и которые позволяют получить высокий рейтинг на Qualys SSL и securityheaders.io. Плюс инструменты для создания и обновления сертификатов. Но проект имеет два ограничения: сертификат действителен 90 дней и для домена нельзя запрашивать больше пяти сертификатов в неделю.
Разберемся, как установить и настроить Let’s Encrypt и подключить сертификат к nginx. В некоторых дистрибутивах уже есть нужный пакет.
$ sudo apt-get install letsencrypt
Если нет, то забираем последнюю версию при помощи Git:
$ sudo apt-get install git bc
$ sudo git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt
Запускаем создание сертификата, указав имя домена и каталог, в котором размещаются файлы. В Ubuntu команда выполняется без sudo:
$ cd /opt/letsencrypt
$ export DOMAINS="example.org,www.example.org"
$ ./letsencrypt-auto certonly -a webroot --webroot-path=/var/www/example.org -d $DOMAINS
Теперь вводим пароль root. После установки дополнительных пакетов будет запрошен email для восстановления ключей и инфосообщений проекта. Подтверждаем условия использования. По окончании в /etc/letsencrypt
будет создан подкаталог с сертификатами домена (в примере live/example.org).
Для повышения уровня безопасности с Perfect forward secrecy желательно создать 2048-битный ключ по алгоритму Диффи — Хеллмана (это может занять время):
$ sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048
Теперь подключаем SSL в nginx. В самом простом случае сайт будет поддерживать оба варианта: без HTTPS или с HTTPS.
server {
server_name example.org;
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
listen 443 ssl;
ssl on;
ssl_certificate /etc/letsencrypt/live/example.org/fullchain.pem; # Файл сертификата
ssl_certificate_key /etc/letsencrypt/live/example.org/privkey.pem; # Файл ключа
...
}
Проверяем корректность конфигурационного файла и перезапускаем nginx:
$ sudo nginx -t && sudo nginx -s reload
Проверяем, зайдя по HTTPS. Можно расширить эту схему. Например, использование стандарта HTTP/2, если его поддерживает клиент:
listen 443 ssl http2;
Разрешаем использование более защищенного TLS, убрав менее безопасные SSLv2/SSLv3. Но это отсеет клиентов, работающих под старыми версиями ОС. TLSv1 будет поддерживаться до середины 2018 года.
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
Указываем список алгоритмов шифрования:
ssl_ciphers EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers On;
Использование для проверки статуса SSL-сертификата протокола OCSP (Online Certificate Status Protocol), обеспечивающего более быструю проверку:
ssl_stapling on;
ssl_stapling_verify on;
Кеширование параметров сессии:
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
и так далее.
Если нужно, чтобы сайт отвечал после установки сертификата только на 443-м порту, то настраиваем редирект. Обычно пишут так:
server {
listen 80;
server_name example.org;
return 301 https://$server_name$request_uri;
}
Но лучше использовать переменную $scheme
, указывающую на протокол:
location / {
if ($scheme = http) {
return 301 https://$server_name$request_uri;
}
}
Сертификаты на сервере обновляются командой
$ /opt/letsencrypt/letsencrypt-auto renew
Первый раз ее можно выполнить вручную, чтобы проверить работоспособность. Затем добавляем задание в cron.
Настраиваем AWStats для мониторинга посетителей
Одно из наиболее популярных средств получения информации о посетителях — Perl-скрипт AWStats. Периодически просматривая журналы веб-сервера, он генерирует HTML-отчеты. Проблема в том, что изначально он хорошо ставится под Apache или lighttpd. Для nginx необходимо немного понастраивать. Устанавливаем:
$ sudo apt install awstats
Базовая настройка AWStats стандартна. Создаем копию шаблона с именем, соответствующим веб-сайту:
$ sudo cp /etc/awstats/awstats.conf /etc/awstats/awstats.example.org.conf
Отредактируем под наш сайт, указав домен, файл журнала и куда складывать статистику:
$ sudo nano /etc/awstats/awstats.example.org.conf
SiteDomain="example.org"
HostAliases="www.example.org"
LogFile="/var/log/nginx/access.log"
DirData="/var/www/example.org/awstats"
Создадим каталог для статистики:
$ sudo mkdir -p /var/www/example.org/awstats
Сгенерируем первый отчет. В принципе, это необязательно, но, так как он может занять некоторое время, лучше первый раз выполнить это вручную и посмотреть на вывод, на наличие ошибок.
$ sudo /usr/lib/cgi-bin/awstats.pl -update -config=example.org
В Ubuntu при установке из пакетов уже есть cron-задание для периодического сбора статистик со всех возможных хостов, описанных в /etc/awstats, и ротации журналов. Обычно больше ничего для настройки AWStats делать не нужно. Для работы AWStats в nginx нам понадобится FastCGI-модуль для Perl:
$ sudo apt install libfcgi-perl -y
Скачиваем готовый FastCGI-враппер для запуска Perl-сценариев и init-скрипт:
$ sudo wget http://nginxlibrary.com/downloads/perl-fcgi/fastcgi-wrapper -O /usr/bin/fastcgi-wrapper.pl
$ sudo wget http://nginxlibrary.com/downloads/perl-fcgi/perl-fcgi -O /etc/init.d/perl-fcgi
Делаем файлы исполняемыми:
$ chmod +x /usr/bin/fastcgi-wrapper.pl
$ chmod +x /etc/init.d/perl-fcgi
В зависимости от дистрибутива потребуется отредактировать init-скрипт. В Ubuntu вместо su нужно использовать sudo. То есть вместо
su - $FASTCGI_USER -c $PERL_SCRIPT
пишем
sudo -u $FASTCGI_USER $PERL_SCRIPT
Это можно сделать в редакторе или выполнив следующую команду:
$ sudo sed -i -e 's/su\ -/sudo\ -u/g' -e '/sudo/s/-c\ //g' /etc/init.d/perl-fcgi
Ставим на автозапуск и запускаем:
$ sudo update-rc.d perl-fcgi defaults
$ sudo service perl-fcgi start
Враппер perl-fcgi будет принимать соединения на 8999-м порту. Его можно изменить, установив другое значение в строке:
$socket = FCGI::OpenSocket( "127.0.0.1:8999", 10 );
Проверяем, чтобы порт слушался:
$ netstat -anp | grep -i 8999
Указываем nginx в настройках сайта, как обрабатывать pl-файлы:
location ~ \.pl$ {
try_files $uri =404;
gzip off;
fastcgi_pass 127.0.0.1:8999;
fastcgi_index index.pl;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
Можно для статистик сделать свой поддомен, но чаще используют подкаталог. Добавляем location для файлов AWStats:
location /awstats/ {
root /usr/lib/cgi-bin;
index index.html index.pl;
}
location /awstatsclasses/ {
alias /usr/share/awstats/lib/;
}
location /awstats-icon/ {
alias /usr/share/awstats/icon/;
}
location /awstatscss {
alias /usr/share/doc/awstats/examples/css/;
}
Проверяем корректность конфигурационного файла и перезапускаем nginx:
$ sudo nginx -t && sudo nginx -s reload
После этого статистика будет доступна по адресу http://example.org/awstats/awstats.pl?config=example.org
.
Заключение
На самом деле в nginx некоторые вещи настраиваются даже проще и легче, чем в Apache. Нужно только привыкнуть.