Веб-сервер на Debian — php 5/7/8, nginx+php-fpm, Let’s Encrypt

Дмитрий Корнев
9 октября 2018

В статье собрано все самое важное по настройке веб-сервера. Для использования будут доступны сразу несколько версий PHP. Никакого Apache, только Nginx+php-fpm. Установка MySQL, phpMyAdmin и автоматически обновляемые SSL-сертификаты Let’s Encrypt.

Статья периодически обновляется, всё сказанное проверенно в Debian 10 buster. Дополнения в комментариях приветствуются.

PHP 5.*, 7.*, 8.*

Подключим источник, откуда можно установить в систему одновременно несколько версий PHP.

apt install ca-certificates apt-transport-https 
wget -q https://packages.sury.org/php/apt.gpg -O- | apt-key add -
echo "deb https://packages.sury.org/php/ buster main" | tee /etc/apt/sources.list.d/php.list
apt update

Следующие команды — установка соответствующих версий PHP. Можно установить только нужное.

apt install php5.6-fpm php5.6-cli php5.6-common php5.6-curl php5.6-mbstring php5.6-mysql php5.6-xml php5.6-gd php5.6-zip php5.6-bcmath
apt install php7.4-fpm php7.4-cli php7.4-common php7.4-curl php7.4-mbstring php7.4-mysql php7.4-xml php7.4-gd php7.4-zip php7.4-bcmath
apt install php8.2-fpm php8.2-cli php8.2-common php8.2-curl php8.2-mbstring php8.2-mysql php8.2-xml php8.2-gd php8.2-zip php8.2-bcmath

Установка специально с указанием конкретных компонентов, чтобы не ставить Apache.

Конфиги располагаются здесь /etc/php/версия/fpm/php.ini.

После их настройки перезагрузка, соответственно:

service php5.6-fpm reload
service php7.4-fpm reload
service php8.2-fpm reload

Nginx

Установка:

apt install nginx

Простой пример конфига для сайта:

server {
    listen 80;
    listen [::]:80;
    server_name pctuner.club;
    root /var/www/pctuner.club;
    index index.php;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        # Используем PHP нужной версии.
        # fastcgi_pass unix:/var/run/php/php5.6-fpm.sock;
        fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
        # fastcgi_pass unix:/var/run/php/php8.0-fpm.sock;
    }

    access_log /var/log/nginx/pctuner_access.log;
    error_log /var/log/nginx/pctuner_error.log;
}

Конфиг записывается в файл с произвольным именем, но в определенный каталог, например /etc/nginx/sites-available/pctuner.club. После этого его необходимо активировать и перезагрузить Nginx:

ln -s /etc/nginx/sites-available/pctuner.club /etc/nginx/sites-enabled/pctuner.club
service nginx reload

Когда вносятся изменения в конфиг работающего сайта, то, чтобы избежать неудачного перезапуска Nginx и остановки работы сайта, полезно вначале выполнить тестирование:

nginx -t

HTTPS

Использовать будем Let’s Encrypt. Бесплатные SSL-сертификаты и автоматическое обновление, что еще надо? Достаточно один раз настроить и можно забыть.

Установка необходимого:

apt install certbot python-certbot-nginx

Регистрация в Let’s Encrypt, для нового сервера это нужно сделать лишь один раз:

certbot register --email ваша@эл.почта

Конфиг /etc/letsencrypt/cli.ini:

authenticator = webroot
webroot-path = /var/www/letsencrypt
post-hook = service nginx reload
text = True

Каталог /var/www/letsencrypt необходимо создать.

Чтобы с правами доступа точно все было в порядке:

chown -R www-data /var/www/letsencrypt && chmod -R 750 /var/www/letsencrypt

Для удобства настройки Nginx создадим пару файлов. Будем их подключать потом в нужных местах конфигов, чтобы не писать одно и тоже сто раз. Первый /etc/nginx/inc_letsencrypt — необходим для обновления SSL-сертификатов:

location ~ /.well-known {
    location ~ /.well-known/acme-challenge/(.*) {
        auth_basic off;
        default_type "text/plain";
        root /var/www/letsencrypt;
    }
}

Второй — /etc/nginx/inc_ssl_pctuner — включает все необходимое для работы https на доменах сайта pctuner.club:

ssl on;
ssl_certificate /etc/letsencrypt/live/pctuner.club/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/pctuner.club/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;

ssl_dhparam /etc/letsencrypt/live/pctuner.club/dh.pem;

ssl_protocols TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
ssl_prefer_server_ciphers on;

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/pctuner.club/chain.pem;
resolver 8.8.8.8;

Почитать подробнее про задействованные параметры можно здесь. Правда там на немецком, но онлайн-переводчик в помощь, это самая толковая инструкция, что удалось найти в свое время.

Немного расширенный конфиг сайта /etc/nginx/sites-available/pctuner.club:

# Вспомогательный блок.
server {
    listen 80;
    listen [::]:80;
    server_name pctuner.club www.pctuner.club pctuner.ru www.pctuner.ru;

    # Для обновления сертификатов.
    include inc_letsencrypt;

    # Редирект с http на https.
    location / {
        return 301 https://pctuner.club$request_uri;
    }
}

# Блок для редиректа с дополнительных доменов.
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name www.pctuner.club pctuner.ru www.pctuner.ru;

    # Подключаем все необходимое для ssl.
    include inc_ssl_pctuner;

    # Редирект на основной домен.
    return 301 https://pctuner.club$request_uri;
}

# Основной блок.
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name pctuner.club;

    # Подключаем все необходимое для ssl.
    include inc_ssl_pctuner;

    root /var/www/pctuner.club/;
    index index.php;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header x-xss-protection "1; mode=block" always;
    add_header X-Content-Type-Options "nosniff" always;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        # Используем PHP нужной версии.
        # fastcgi_pass unix:/var/run/php/php5.6-fpm.sock;
        fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
        # fastcgi_pass unix:/var/run/php/php8.0-fpm.sock;
    }

    access_log /var/log/nginx/pctuner_access.log;
    error_log /var/log/nginx/pctuner_error.log;
}

Изначально нужные сертификаты (файлы) отсутствуют, поэтому Nginx не примет конфиг в таком виде. До того момента пока сертификаты не будут получены необходимо закоментировать подключение файла inc_ssl_pctuner:

# include inc_ssl_pctuner;

Пример теста получения сертификата:

certbot certonly --dry-run -d pctuner.club -d www.pctuner.club

Один сертификат будет на 2 домена.

В одном сертификате имеет смысл объединять все домены, имеющие отношение к одному и тому же сайту. Больше доменов добавляются аналогично:

certbot certonly --dry-run -d pctuner.club -d www.pctuner.club -d pctuner.ru -d www.pctuner.ru

Здесь параметр --dry-run отвечает за запуск в тестовом режиме. Это позволяет убедиться, что с настройкой сервера все хорошо и сертификат точно будет получен.

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

В случае успешного теста будет ответ:

- The dry run was successful.

Кроме сертификата необходимо сгенерировать ключ:

openssl dhparam -outform PEM -out /etc/letsencrypt/live/pctuner.club/dh.pem 2048

На этом все. Включаем обратно подключение файла inc_ssl_pctuner в конфиге и перезапускаем Nginx.

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

Для автоматического обновления сертификатов в будущем ничего делать не нужно, файл /etc/cron.d/certbot с необходимыми для этого настройками уже был создан. Программа автоматически запускается дважды в сутки. Она видит полученные сертификаты всех сайтов на сервере, при необходимости обновляет их.

Можно протестировать процесс обновления, без получения реальных сертификатов:

certbot renew --dry-run

Basic-auth

Это простой способ организовать доступ по логину и паролю к какому-то каталогу или всему сайту. Он имеет свои изъяны, информацию можно найти в интернете. Поэтому не стоит его использовать, как единственную защиту. Ну и, конечно, HTTPS — обязателен.

Для генерации файлов с логином и паролем установим:

apt install apache2-utils

Генерация:

htpasswd -c /etc/nginx/my_auth логин_для_доступа

В процессе будет запрошен пароль.

Пример подключения файла в конфиге Nginx для всего сайта:

...
auth_basic "closed site";
auth_basic_user_file my_auth;
...

MySQL

Не знаю куда делся MySQL из штатного репозитория Debian 10, поэтому ставим следующим образом. Актуальную версию установочного файла можно посмотреть здесь.

cd /tmp
wget https://dev.mysql.com/get/mysql-apt-config_0.8.16-1_all.deb
dpkg -i mysql-apt-config_0.8.16-1_all.deb

Будет предложен выбор версии MySQL. Выберите 8.0 или 5.7.

Теперь можно продолжить:

apt update
apt install mysql-server

В процессе, если сможете, задайте пароль для root базы.

У меня не получилось, я оставил поле пароля пустым и далее сделал так:

mysql

В самом MySQL:

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'здесь_свой_пароль';
FLUSH PRIVILEGES;
exit

Настройки безопасности:

mysql_secure_installation

На следующие вопросы важно ответить положительно:

Remove anonymous users? : y
Disallow root login remotely? : y
Remove test database and access to it? : y
Reload privilege tables now? : y

Настройки MySQL обычно в /etc/mysql/mysql.conf.d/mysqld.cnf и /etc/mysql/my.cnf.

В секцию [mysqld] стоит добавить параметры:

# Настройка блокирует все подключения к MySQL, кроме подключений с локальной машины.
bind-address = 127.0.0.1

# Отключение функции, позволяющей получить доступ к файловой системе из MySQL.
# MySQL не будет загружать локальные файлы для пользователей без соответствующего уровня доступа.
local-infile = 0

# Отключение обработки символических ссылок, тоже в целях безопасности.
symbolic-links = 0

# Раскоментируйте, если на сервере мало памяти.
# performance_schema = off

Проверка настроек MySQL:

/usr/sbin/mysqld --help --verbose --skip-networking 1>/dev/null

Если ответ пустой, то всё хорошо. Полезно проверять после внесения изменений в конфиг, чтобы избежать ошибки перезапуска.

Перезапуск MySQL:

service mysql restart

MySQL должен проработать непрерывно хотя бы пару дней. Должна накопиться статистика именно по вашей базе, нагрузке и т.д. После этого можно запустить mysqltuner.pl и посмотреть его рекомендации:

wget http://mysqltuner.pl/ -O mysqltuner.pl
perl ./mysqltuner.pl

Чтобы работать на сервере с MySQL и не вводить каждый раз пароль, удобно создать файл ~/.my.cnf, в котором прописать необходимое:

[mysql]
user = пользователь
password = пароль

[mysqldump]
user = пользователь
password = пароль

Установка прав доступа на файл:

chmod 0600 ~/.my.cnf

phpMyAdmin

Установим Сomposer. Он всегда может пригодиться.

cd /usr/src
apt install curl
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

И конкретно сейчас он будет нужен для установки новейшего phpMyAdmin.

Лучше создать отдельный сайт для использования подобного рода утилит, что-то вроде dev.pctuner.club. Выше вся информация есть. Конечно это должен быть HTTPS-сайт, плюс можно добавить дополнительную защиту в виде basic-auth.

Переходим в подготовленный каталог сайта и запускаем установку:

cd /var/www/dev.pctuner.club
composer create-project phpmyadmin/phpmyadmin --repository-url=https://www.phpmyadmin.net/packages.json --no-dev

Он установится по пути /var/www/dev.pctuner.club/phpmyadmin и можно уже обращаться к нему соответствующим образом.

При авторизации внизу будет сообщение:

Хранилище конфигурации phpMyAdmin не полностью настроено, некоторые расширенные функции были отключены. Узнайте причину.
Или перейдите на вкладку 'Операции' любой базы данных, чтобы настроить хранилище в ней.

Ничего страшного. Просто нужно создать базу, в которую phpMyAdmin будет сохранять свои настройки. Следуем по ссылке «Узнайте причину». И там нажимаем ссылку «Создать».

Еще одно уведомление внизу:

В конфигурационном файле необходимо задать парольную фразу (blowfish_secret).

Создаем файл конфигурации из файла-примера (копируем):

cp /var/www/dev.pctuner.club/phpmyadmin/config.sample.inc.php /var/www/dev.pctuner.club/phpmyadmin/config.inc.php

В файле редактируем параметр:

$cfg['blowfish_secret'] = 'nf4eDr5uFf6n7GfH......';

В качестве значения вписываете любые свои символы, порядка 40 знаков.

Теперь phpMyAdmin готов к полноценному использованию.

Настройка локали

Просмотр установленных локалей:

locale -a

Мастер настройки:

dpkg-reconfigure locales

Сначала вы можете выбрать необходимые локали. У меня все необходимое уже было, поэтому на первом шаге я оставил все без изменений. На втром шаге можно выбрать локаль по умолчанию.

Сервер необходимо перезапустить, чтобы увидеть изменения в действии.

Что еще?

Выполнена лишь базовая настройка. Сайты на таком веб-сервере будут работать, но для лучшего результата необходимо выполнить оптимизацию. Настроить кэширование. Также рекомендую обратить внимание на некоторые другие настройки:

8 комментариев

3a
Добрый вечер, огромное спасибо за статью! Вопрос, дошел до шага Basic-auth, перед ним вроде бы все заработало. Открывается заглушка по адресу сайта или ip сервера. Но только по http (с WWW и без). По https - ERR_CONNECTION_REFUSED. Это нормально для html или просто php заглушки? или где-то что-то упущено?
По ip https не будет работать, letsencrypt не дает сертификаты для ip. По адресу все должно работать. Посмотрите внимательнее инструкцию. По ней все неоднократно настроено, она точно рабочая.
3a
Понял, Вас. Все вроде заработало). Огромное спасибо! Единственное что, не могу снова на поддомен сделать сертификат: IMPORTANT NOTES: - The following errors were reported by the server: Domain: arma.gungame.ru Type: unauthorized Detail: Invalid response from ...ссылка [ххх.ххх.хх.ххх]: "<html>\r\n<head><title>404 Not Found</title></head>\r\n<body bgcolor=\"white\">\r\n<center><h1>404 Not Found</h1></center>\r\n<hr><center>" Domain: www.arma.gungame.ru Type: unauthorized Detail: Invalid response from ...ссылка [ххх.хх.хх.ххх]: "<html>\r\n<head><title>404 Not Found</title></head>\r\n<body bgcolor=\"white\">\r\n<center><h1>404 Not Found</h1></center>\r\n<hr><center>" To fix these errors, please make sure that your domain name was entered correctly and the DNS A/AAAA record(s) for that domain contain(s) the right IP address. Основной сайт и 1 поддомен уже прицепил, сегодня снова затык с новым. Возможно AАА записи у регистратора оооооочень долго обновляются, без https все быстро взлетало.
3a
Все.. сам дурак. Забыл симлинк! А до кучи, а как правильно разлинковать ls -s?
Линки разного типа удобно создавать через mc, им же просто удаляем не нужные, никаких команд знать не требуется.
АС
Для Nginx нужны какие-то дополнительные настройки безопасности? Или достаточно из коробки, был бы признателен за освещение и этой темы. И второй вопрос - этих настроек достаточно для работы на одном хостинге нескольких сайтов?
В любом продукте могут быть дыры. В новостях постоянно проскакивает, что где-то их нашли. На мой взгляд из коробки у Nginx все неплохо. Но, конечно, стоит обновлять версию, когда таковая становится доступна. Плюс в конфиге не наворотить лишнего. Поэтому желательно, чтобы конфиг был как можно проще. Пример выше этому соответствует. Сайтов может быть сколько угодно, главное, чтобы сервер тянул. Самое простое — это создать каждого сайта аналогичный конфиг.
АС
спасибо