Панель управления на сервере обещает сайт за десять минут, но берёт плату памятью, лишним слоем программ и молчанием: когда что-то ломается, приходится разбирать чужие скрипты и гадать, что они сделали с конфигурацией. Пустой VPS честнее. Каждый пакет появляется по одной команде, каждая строка настроек открыта для чтения, и любая ошибка объясняется за минуты, а не за вечер. Ниже разобран полный путь от свежевыданной машины до работающего сайта на WordPress: стек из nginx, PHP-FPM и MariaDB, база данных, сертификат HTTPS, системный планировщик cron и постоянные ссылки. Попутно отмечены места, где спотыкаются чаще всего.
Вся дорога от пустой машины до готового сайта раскладывается на семь шагов:
- обновление пакетов и создание пользователя с правами sudo;
- установка nginx, PHP-FPM и сервера MariaDB;
- создание базы данных и отдельного пользователя MySQL;
- загрузка ядра WordPress и заполнение wp-config.php;
- виртуальный хост nginx с поддержкой постоянных ссылок;
- выпуск сертификата HTTPS и автоматическое продление;
- системный планировщик cron вместо встроенного WP-Cron.
Подготовка чистого сервера и первые шаги защиты
Хостер выдаёт IP-адрес, логин root и пароль. Обновление делается сразу, откладывать его нет смысла: дыры в старых версиях лучше закрыть до появления сайта. Первый вход и апгрейд занимают пару минут:
ssh Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.
apt update && apt full-upgrade -y
Работать под root постоянно - скверная привычка: одна опечатка в команде с rm или chmod, и вечер уходит на восстановление. Практичнее завести отдельного пользователя с правами sudo и входить под ним:
adduser siteadmin
usermod -aG sudo siteadmin
Знакомая многим картина: сайт ещё пуст, а в журнале уже сотни попыток входа под root от ботов. Вход по ключу вместо пароля закрывает дверь вежливо и надолго. Пара ключей генерируется на домашней машине, открытая половина копируется на VPS:
ssh-keygen -t ed25519 -f ~/.ssh/vps_key
ssh-copy-id -i ~/.ssh/vps_key.pub Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.
Парольный вход в настройках службы ssh отключается после проверки, что ключ работает. Брандмауэр ufw включается с уже разрешённым SSH, иначе доступ обрежется у самого себя. Сайту нужны три порта: 22 для консоли, 80 и 443 для веба:
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
Часовой пояс задаётся под расписание публикаций и резервных копий, планировщику важна предсказуемость времени запуска:
timedatectl set-timezone Europe/Moscow
Установка nginx PHP-FPM и MariaDB из пакетов дистрибутива
Для WordPress без Apache собирается связка из трёх программ: nginx раздаёт статические файлы, PHP-FPM исполняет код в отдельных процессах, MariaDB хранит содержимое. Выбор объясняется экономией. Apache с модулем mod_php поднимает тяжёлый интерпретатор на каждый запрос и перечитывает .htaccess при каждом обращении к каталогу. Nginx держит конфигурацию в памяти и не разрешает менять её из каталогов сайта, что на машине с гигабайтом памяти даёт ощутимую разницу:
apt install -y nginx mariadb-server
apt install -y php-fpm php-mysql php-curl php-gd php-imagick php-intl php-mbstring php-xml php-zip
Пакет php-fpm тянет конкретную ветку, служба называется php8.3-fpm или php8.2-fpm в зависимости от дистрибутива. На момент подготовки текста активны ветки PHP 8.2, 8.3, 8.4 и 8.5 со свежими выпусками от сентября 2026 года, и WordPress версии 7.1.3 спокойно живёт на любой из них. В стабильном Debian сейчас лежит сервер MariaDB серии 11.8, в других сборках версия может отличаться, порядок действий от этого не меняется:
systemctl enable --now nginx mariadb
systemctl enable --now php8.3-fpm
nginx -v && php -v && mariadb --version
Полезная деталь: сервер базы сразу после установки не слушает сеть наружу, соединения принимаются только с самой машины. Для сайта, живущего на том же VPS, этого достаточно с запасом.
Пулу PHP-FPM нужна осмысленная настройка памяти. Один процесс с типичным набором плагинов занимает 50-70 МБ. На машине с 1 ГБ под систему, nginx и MariaDB уходит примерно 350 МБ, остальное достаётся обработчикам: (1024 - 350) / 70 = 9 процессов в теории, на практике одиночному сайту хватает пяти. Правки вносятся в два файла:
# /etc/php/8.3/fpm/php.ini
memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 300
# /etc/php/8.3/fpm/pool.d/www.conf
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
systemctl reload php8.3-fpm
Создание базы данных и отдельного пользователя в MariaDB
Первым делом запускается штатный скрипт безопасности: он задаёт пароль root, удаляет анонимные учётки и тестовую базу, о которой обычно никто не вспоминает:
mysql_secure_installation
Дальше root входит в консоль без пароля: свежий MariaDB верит пользователю системы через плагин unix_socket, и это не дыра, а удобство, пока вход возможен только с самого сервера. WordPress получит собственную учётку:
mariadb
CREATE DATABASE wpsite CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wpuser'@'localhost' IDENTIFIED BY 'Slozhnyi_Parol_2026_77';
GRANT ALL PRIVILEGES ON wpsite.* TO 'wpuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;
В четырёх строках спрятаны три решения. Кодировка utf8mb4 четырёхбайтная, без неё в базу не пишутся редкие символы и эмодзи в комментариях. Хост 'localhost' привязывает учётку к самой машине: WordPress ходит в базу через сокет, наружу учётка не видна. Отдельная база и отдельный пользователь на каждый сайт держат принцип минимальных прав: утечка одной учётки не открывает соседей. Пароль генерируется длинный, пишется в конфиг один раз и живёт там годами.
Загрузка ядра WordPress и заполнение файла wp-config.php
Ядро ставится двумя способами: распаковкой архива или утилитой wp-cli. Вторая превращает рутину в короткие команды и ещё пригодится для обновлений и планировщика, так что поставить её стоит сразу:
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
mv wp-cli.phar /usr/local/bin/wp
wp --info
Скачивание ядра, создание конфига и установка занимают меньше минуты:
mkdir -p /var/www/example.com/public
cd /var/www/example.com/public
wp core download --locale=ru_RU
wp config create --dbname=wpsite --dbuser=wpuser --dbpass='Slozhnyi_Parol_2026_77' --dbhost=localhost
wp config shuffle-salts
wp core install --url='http://example.com' --title='Новый сайт' --admin_user=chief --admin_password='Eshche_Odin_Parol_9' --admin_email=Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript. ' --skip-email
Команда shuffle-salts генерирует соли для шифрования cookies, руками их писать не нужно. В готовый wp-config.php добавляются четыре строки, каждая решает конкретную боль:
define('WP_MEMORY_LIMIT', '256M');
define('WP_MAX_MEMORY_LIMIT', '512M');
define('DISALLOW_FILE_EDIT', true);
define('WP_AUTO_UPDATE_CORE', 'minor');
Лимиты памяти не дают фронтенду и админке падать на тяжёлых плагинах, DISALLOW_FILE_EDIT закрывает встроенный редактор файлов тем и плагинов, которым любят пользоваться при взломе учётной записи администратора, а minor включает автообновления безопасности ядра. Файлы сайта отдаются пользователю www-data, от которого работает PHP-FPM:
chown -R www-data:www-data /var/www/example.com
find /var/www/example.com -type d -exec chmod 750 {} \;
find /var/www/example.com -type f -exec chmod 640 {} \;
Виртуальный хост nginx и правила rewrite для постоянных ссылок
У nginx нет механизма .htaccess, поэтому правила адресов страниц живут прямо в хосте. За WordPress отвечает try_files: сначала ищется файл, потом каталог, затем запрос уходит в index.php со всеми параметрами. Без этой строки работает только главная, остальные адреса отдают 404, и это ошибка номер один на свежем сервере:
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com/public;
index index.php index.html;
client_max_body_size 64m;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
}
location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?)$ {
expires 30d;
access_log off;
}
location = /wp-config.php { deny all; }
}
Файл сохраняется в каталоге sites-available под именем домена, включается символической ссылкой и проверяется на синтаксис:
ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
Постоянные ссылки включаются в админке разделом "Настройки" и пунктом "Постоянные ссылки" или одной командой:
wp rewrite structure '/%category%/%postname%/'
wp rewrite flush --hard
Формат '/%postname%/' даёт короткие адреса страниц, '/%category%/%postname%/' добавляет рубрику, варианты с датой делают ссылки длинными и понятными только в день публикации. Ошибка номер два - белая страница или код 502 Bad Gateway: почти всегда это либо нехватка памяти PHP, либо неверный путь к сокету PHP-FPM в директиве fastcgi_pass. Сокет сверяется с фактом мгновенно:
ls /run/php/
Бесплатный сертификат HTTPS и автоматическое продление
Домен к этому моменту должен указывать на сервер двумя A записями: основной домен и зеркало с именем www в зоне DNS. Сертификат выпускает certbot, в Debian и Ubuntu ставится пакет с модулем для nginx:
apt install -y certbot python3-certbot-nginx
certbot --nginx -d example.com -d www.example.com
Утилита спросит почту для уведомлений, согласие с правилами и способ перенаправления незашифрованных запросов. После ответов certbot сам перепишет хост: добавит строки с сертификатами и перенаправление 301 с http на https. Сертификат живёт 90 дней, продление проверяется тут же:
certbot renew --dry-run
systemctl list-timers | grep certbot
Таймер системы обновит сертификат в фоне, вручную возвращаться к нему не придётся. Осталось перевести адреса в базе на https. WordPress хранит часть настроек в сериализованных строках, и наивная замена через SQL их ломает, wp-cli делает это аккуратно:
wp search-replace 'http://example.com' 'https://example.com' --all-tables --precise
Системный планировщик cron вместо встроенного WP-Cron
Встроенный планировщик WordPress срабатывает от визитов: при каждой загрузке страницы ядро заглядывает в очередь отложенных задач. Отсюда две беды. На непосещаемом сайте публикация черновика, назначенная на девять утра, ждёт первого гостя до полудня. На популярном каждая страница тащит лишнюю проверку. Системный cron работает по часам независимо от трафика, поэтому встроенный механизм отключается одной строкой в wp-config.php:
define('DISABLE_WP_CRON', true);
Расписание заводится для пользователя www-data, от которого живёт сайт:
crontab -u www-data -e
*/5 * * * * cd /var/www/example.com/public && /usr/local/bin/wp cron event run --due-now >/dev/null 2>&1
Каждые пять минут wp-cli исполняет задачи с наступившим сроком: публикует отложенные записи, проверяет обновления, чистит корзину, запускает задания плагинов резервного копирования. Запуск от www-data, а не от root, чтобы файлы сайта не сменили владельца. Кто не держит wp-cli, обходится curl, разница только в способе обращения к тому же механизму:
*/5 * * * * curl -s -o /dev/null 'https://example.com/wp-cron.php?doing_wp_cron'
Очередь задач просматривается командой wp cron event list, интервал в пять минут - разумный компромисс между точностью расписания и нагрузкой. Сервер после всей процедуры перестаёт быть чужой машиной: каждый компонент знаком по имени, и у каждой ошибки короткий путь диагностики. Код 502 смотрит на сокет PHP-FPM, ошибка соединения с базой проверяется парой команд в MariaDB, 404 на внутренних страницах указывает на try_files. Панель экономит время на старте, ручная настройка экономит его потом, когда счёт идёт на минуты. Обновления укладываются в пару команд: apt обновляет систему, wp core update и wp plugin update --all поднимают ядро и плагины, а certbot продлевает сертификат сам по расписанию.