Настраиваем новый VPS на AlmaLinux 10: устанавливаем nginx как внешний веб-сервер и reverse proxy, Apache как внутренний бэкенд, PHP-FPM и MariaDB. Дополнительно настраиваем SFTP, firewalld, SELinux, HTTPS и базовую защиту SSH.
Инструкция рассчитана на чистую установку AlmaLinux 10.x. Проверено на AlmaLinux 10.2. Перед выполнением команд на рабочем сервере сделайте snapshot или резервную копию.
Когда подходит связка nginx + Apache
- проект использует
.htaccess; - нужны модули Apache;
- переносится существующий сайт с Apache-хостинга;
- nginx должен отдавать статику и принимать HTTPS, а Apache — обрабатывать динамические запросы.
Когда лучше использовать другое решение
- Для нового PHP-проекта без зависимости от Apache проще использовать nginx + PHP-FPM.
- Если на сервере установлена панель управления, не меняйте конфигурацию вручную без проверки документации панели.
- Инструкция не предназначена для обновления CentOS 6 на месте. Для старого сервера безопаснее развернуть новый VPS и перенести сайты.
Схема работы
Пользователь ↓ HTTP / HTTPS nginx :80 / :443 ├── статические файлы └── динамические запросы ↓ Apache 127.0.0.1:8080 ↓ PHP-FPM Unix socket ↓ MariaDB localhost
1. Обновление системы
Работаем от пользователя с правами sudo. Не отключайте SELinux и firewalld: ниже настроим их штатными средствами.
sudo dnf update -y sudo reboot
После перезагрузки проверяем систему:
cat /etc/almalinux-release uname -r getenforce
Ожидаем AlmaLinux 10.x и режим SELinux Enforcing.
Необязательно: русская локаль
sudo dnf install -y glibc-langpack-ru sudo localectl set-locale LANG=ru_RU.UTF-8 localectl status
2. Установка пакетов
sudo dnf install -y \ nginx \ httpd \ mariadb-server \ php \ php-fpm \ php-cli \ php-opcache \ php-mysqlnd \ php-gd \ php-intl \ php-mbstring \ php-xml \ php-zip \ firewalld \ policycoreutils-python-utils \ curl \ wget \ unzip \ tar \ vim-enhanced
Проверяем установленные версии:
nginx -v httpd -v php -v mariadb --version
Используем версии из репозиториев AlmaLinux. Подключать сторонние репозитории только ради более нового номера версии без конкретной необходимости не стоит.
3. Настройка firewalld
Открываем только SSH, HTTP и HTTPS. Порты Apache, PHP-FPM и MariaDB наружу не публикуем.
sudo systemctl enable --now firewalld sudo firewall-cmd --permanent --add-service=ssh sudo firewall-cmd --permanent --add-service=http sudo firewall-cmd --permanent --add-service=https sudo firewall-cmd --reload sudo firewall-cmd --list-all
Не открывайте SMTP, POP3, IMAP, FTP и порт MariaDB, если соответствующие сервисы на сервере не используются.
4. Пользователь для деплоя и SFTP
Обычный FTP не устанавливаем. Передача файлов выполняется через SFTP поверх OpenSSH.
Создаём общую группу для nginx, Apache и пользователя деплоя:
sudo groupadd --system web 2>/dev/null || true id deploy >/dev/null 2>&1 || \ sudo useradd --create-home --shell /bin/bash --groups wheel,web deploy sudo usermod -aG web nginx sudo usermod -aG web apache
Добавляем публичный SSH-ключ. Замените PASTE_PUBLIC_SSH_KEY своим ключом:
sudo install -d \ -m 700 \ -o deploy \ -g deploy \ /home/deploy/.ssh sudo tee /home/deploy/.ssh/authorized_keys > /dev/null <<'EOF' PASTE_PUBLIC_SSH_KEY EOF sudo chown deploy:deploy /home/deploy/.ssh/authorized_keys sudo chmod 600 /home/deploy/.ssh/authorized_keys
Откройте второе SSH-соединение и убедитесь, что вход по ключу работает:
ssh deploy@SERVER_IP
Только после успешной проверки отключаем вход root и парольную авторизацию.
sudo tee /etc/ssh/sshd_config.d/10-hardening.conf > /dev/null <<'EOF' PermitRootLogin no PubkeyAuthentication yes PasswordAuthentication no KbdInteractiveAuthentication no MaxAuthTries 3 AllowUsers deploy EOF sudo sshd -t sudo systemctl reload sshd
Важно: не закрывайте текущее SSH-соединение, пока не проверили вход в новой вкладке терминала.
Нужно ли менять порт SSH
Смена порта уменьшает шум в логах, но не заменяет ключи и запрет входа root. Оставить порт 22 допустимо.
Если нужен порт 3302, сначала разрешаем его в firewalld и SELinux:
sudo firewall-cmd --permanent --add-port=3302/tcp sudo firewall-cmd --reload sudo semanage port -a -t ssh_port_t -p tcp 3302 2>/dev/null || \ sudo semanage port -m -t ssh_port_t -p tcp 3302
Добавляем в SSH-конфигурацию:
Port 3302
Проверяем и перезапускаем SSH:
sudo sshd -t sudo systemctl reload sshd
Проверяем новое подключение и только затем удаляем старое правило SSH при необходимости.
5. Каталоги сайта и права
В примерах используется домен example.com. Замените его на свой.
sudo mkdir -p /var/www/example.com/public
sudo mkdir -p /var/www/example.com/storage
sudo mkdir -p /var/www/acme/.well-known/acme-challenge
sudo chown -R deploy:web /var/www/example.com
sudo chown -R deploy:web /var/www/acme
sudo find /var/www/example.com -type d -exec chmod 2750 {} \;
sudo find /var/www/example.com -type f -exec chmod 0640 {} \;
sudo find /var/www/acme -type d -exec chmod 2750 {} \;
sudo find /var/www/acme -type f -exec chmod 0640 {} \;
sudo chmod 2770 /var/www/example.com/storage
Создаём тестовую страницу:
echo '<?php echo "PHP работает: " . PHP_VERSION;' | \ sudo tee /var/www/example.com/public/index.php > /dev/null sudo chown deploy:web /var/www/example.com/public/index.php sudo chmod 0640 /var/www/example.com/public/index.php
6. SELinux
Каталог сайта доступен веб-серверам только для чтения. Каталог storage разрешён для записи приложением.
sudo semanage fcontext -a \ -t httpd_sys_content_t \ '/var/www/example\.com/public(/.*)?' sudo semanage fcontext -a \ -t httpd_sys_rw_content_t \ '/var/www/example\.com/storage(/.*)?' sudo semanage fcontext -a \ -t httpd_sys_content_t \ '/var/www/acme(/.*)?' sudo restorecon -Rv /var/www/example.com sudo restorecon -Rv /var/www/acme
Разрешаем nginx подключаться к внутреннему Apache:
sudo setsebool -P httpd_can_network_connect 1
Не отключайте SELinux командой setenforce 0 как способ исправления ошибок. Сначала изучите журнал блокировок:
sudo ausearch -m AVC -ts recent
7. Настройка PHP-FPM
На AlmaLinux PHP-FPM обычно уже настроен для работы с Apache. Проверяем основные параметры:
sudo grep -E \ '^(user|group|listen|listen\.owner|listen\.group|listen\.mode|listen\.acl_users)' \ /etc/php-fpm.d/www.conf
Apache должен иметь доступ к Unix-сокету. Основные параметры должны выглядеть примерно так:
user = apache group = apache listen = /run/php-fpm/www.sock listen.acl_users = apache
Проверяем конфигурацию и запускаем PHP-FPM:
sudo php-fpm -t sudo systemctl enable --now php-fpm sudo systemctl status php-fpm --no-pager
8. Настройка Apache как внутреннего сервера
Apache слушает только 127.0.0.1:8080. Подключиться к нему напрямую из интернета нельзя.
Сначала находим стандартную директиву Listen 80:
sudo grep -R \ '^*Listen' \ /etc/httpd/conf/httpd.conf \ /etc/httpd/conf.d/
Комментируем стандартную строку Listen 80 в файле /etc/httpd/conf/httpd.conf:
# Listen 80
Создаём отдельный внутренний порт:
sudo tee /etc/httpd/conf.d/00-backend-listen.conf > /dev/null <<'EOF' Listen 127.0.0.1:8080 EOF
Добавляем базовые настройки безопасности Apache:
sudo tee /etc/httpd/conf.d/00-security.conf > /dev/null <<'EOF' ServerTokens Prod ServerSignature Off TraceEnable Off EOF
Создаём виртуальный хост:
sudo tee /etc/httpd/conf.d/example.com.conf > /dev/null <<'EOF' <VirtualHost 127.0.0.1:8080> ServerName example.com ServerAlias www.example.com DocumentRoot "/var/www/example.com/public" DirectoryIndex index.php index.html RemoteIPHeader X-Forwarded-For RemoteIPInternalProxy 127.0.0.1 <Directory "/var/www/example.com/public"> Options FollowSymLinks AllowOverride All Require all granted </Directory> <FilesMatch "\.php$"> SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost" </FilesMatch> ErrorLog "/var/log/httpd/example.com-error.log" CustomLog "/var/log/httpd/example.com-access.log" combined </VirtualHost> EOF
Если проект не использует .htaccess, безопаснее и быстрее заменить AllowOverride All на AllowOverride None, а правила перенести в конфигурацию виртуального хоста.
Проверяем модули и конфигурацию:
sudo httpd -M | grep -E 'proxy|proxy_fcgi|remoteip|rewrite' sudo apachectl configtest
Запускаем Apache:
sudo systemctl enable --now httpd sudo systemctl status httpd --no-pager
Проверяем внутренний сервер:
curl -i \ -H 'Host: example.com' \ http://127.0.0.1:8080/
9. Настройка nginx как reverse proxy
nginx принимает внешние соединения, отдаёт статические файлы и передаёт остальные запросы Apache.
sudo tee /etc/nginx/conf.d/example.com.conf > /dev/null <<'EOF'
upstream example_apache {
server 127.0.0.1:8080;
keepalive 16;
}
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com/public;
access_log /var/log/nginx/example.com-access.log;
error_log /var/log/nginx/example.com-error.log warn;
client_max_body_size 32m;
location ^~ /.well-known/acme-challenge/ {
root /var/www/acme;
default_type text/plain;
try_files $uri =404;
}
location ~* \.(?:css|js|jpg|jpeg|gif|png|webp|avif|svg|ico|woff|woff2)$ {
expires 7d;
access_log off;
try_files $uri @apache;
}
location / {
proxy_pass http://example_apache;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
proxy_connect_timeout 10s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
location @apache {
proxy_pass http://example_apache;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
}
location ~ /\.(?!well-known/) {
deny all;
}
}
EOF
Проверяем и запускаем nginx:
sudo nginx -t sudo systemctl enable --now nginx sudo systemctl status nginx --no-pager
Проверяем сайт:
curl -I http://example.com/
10. Настройка MariaDB
sudo systemctl enable --now mariadb sudo systemctl status mariadb --no-pager sudo mariadb-secure-installation
Создаём отдельную базу и пользователя приложения. Не подключайте сайт от имени root.
sudo mariadb
CREATE DATABASE ExampleDb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'ExampleUser'@'localhost' IDENTIFIED BY 'REPLACE_WITH_LONG_RANDOM_PASSWORD'; GRANT ALL PRIVILEGES ON ExampleDb.* TO 'ExampleUser'@'localhost'; FLUSH PRIVILEGES; EXIT;
Пароль храните в переменных окружения или закрытом конфигурационном файле вне публичного каталога. Не публикуйте его в Git.
Проверяем MariaDB:
sudo mariadb-admin ping sudo ss -lntp | grep 3306 || true
При стандартной локальной настройке MariaDB не должна слушать публичный IP.
11. HTTPS через Let’s Encrypt
Перед выпуском сертификата DNS-записи домена должны указывать на сервер, а порт 80 должен быть доступен из интернета.
Устанавливаем EPEL и Certbot:
sudo dnf install -y epel-release sudo dnf install -y certbot
Проверяем каталог ACME:
echo 'acme-test' | \ sudo tee /var/www/acme/.well-known/acme-challenge/test > /dev/null curl http://example.com/.well-known/acme-challenge/test
Выпускаем сертификат:
sudo certbot certonly \ --webroot \ --webroot-path /var/www/acme \ -d example.com \ -d www.example.com
После успешного выпуска заменяем конфигурацию nginx на HTTPS-вариант:
upstream example_apache {
server 127.0.0.1:8080;
keepalive 16;
}
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
location ^~ /.well-known/acme-challenge/ {
root /var/www/acme;
default_type text/plain;
try_files $uri =404;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name example.com www.example.com;
root /var/www/example.com/public;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
access_log /var/log/nginx/example.com-access.log;
error_log /var/log/nginx/example.com-error.log warn;
client_max_body_size 32m;
location ~* \.(?:css|js|jpg|jpeg|gif|png|webp|avif|svg|ico|woff|woff2)$ {
expires 7d;
access_log off;
try_files $uri @apache;
}
location / {
proxy_pass http://example_apache;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Connection "";
proxy_connect_timeout 10s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
location @apache {
proxy_pass http://example_apache;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Connection "";
}
location ~ /\.(?!well-known/) {
deny all;
}
}
Проверяем и применяем конфигурацию:
sudo nginx -t sudo systemctl reload nginx curl -I https://example.com/ sudo certbot renew --dry-run
Проверьте, что в системе есть таймер или cron-задача автоматического продления:
systemctl list-timers --all | grep -i certbot || true sudo crontab -l
12. Общая проверка
sudo nginx -t sudo apachectl configtest sudo php-fpm -t sudo mariadb-admin ping sudo systemctl is-active nginx sudo systemctl is-active httpd sudo systemctl is-active php-fpm sudo systemctl is-active mariadb sudo systemctl is-active firewalld sudo systemctl --failed sudo ss -lntp | grep -E ':(80|443|8080|3306)\b'
Ожидаемый результат:
- nginx слушает публичные порты 80 и 443;
- Apache слушает только
127.0.0.1:8080; - PHP-FPM работает через Unix-сокет;
- MariaDB доступна локально и не открыта в firewalld;
- сайт отвечает по HTTPS;
- в
systemctl --failedнет проблемных сервисов.
Краевые случаи
502 Bad Gateway
Проверяем Apache и SELinux:
sudo systemctl status httpd --no-pager sudo ss -lntp | grep 8080 sudo getsebool httpd_can_network_connect sudo tail -n 100 /var/log/nginx/example.com-error.log
403 Forbidden для файлов
namei -l /var/www/example.com/public/index.php ls -laZ /var/www/example.com/public/ sudo restorecon -Rv /var/www/example.com sudo ausearch -m AVC -ts recent
PHP-файл скачивается вместо выполнения
Проверьте PHP-FPM, модуль proxy_fcgi и секцию FilesMatch в виртуальном хосте Apache.
sudo systemctl status php-fpm --no-pager sudo httpd -M | grep proxy_fcgi sudo apachectl configtest
Не загружаются большие файлы
Согласуйте ограничения в нескольких местах:
client_max_body_sizeв nginx;upload_max_filesizeв PHP;post_max_sizeв PHP;- ограничения самого приложения.
Сервер находится за Cloudflare или другим CDN
Без дополнительной настройки nginx будет видеть IP прокси-сервера. Настройте real_ip_header и доверенные диапазоны CDN. Не доверяйте заголовкам реального IP от произвольных адресов.
Безопасность
- Не отключайте SELinux и firewalld.
- Не запускайте сайт от пользователя
root. - Не публикуйте порт Apache
8080и MariaDB3306. - Используйте SSH-ключи и запретите вход root.
- Не устанавливайте FTP, если достаточно SFTP.
- Не размещайте phpMyAdmin в открытом доступе. Используйте VPN, SSH-туннель или ограничение по IP.
- Для каждого сайта создавайте отдельного пользователя базы данных.
- Регулярно устанавливайте обновления безопасности.
- Храните резервные копии вне текущего VPS.
Производительность
nginx отдаёт статические файлы без обращения к Apache. Динамические запросы проходят через Apache и PHP-FPM.
Количество процессов PHP-FPM нельзя выбирать вслепую. Сначала измерьте среднее потребление памяти одного PHP-процесса:
ps --no-headers -o rss,cmd -C php-fpm
Приближённый расчёт:
pm.max_children = память, выделенная под PHP / среднее потребление одного PHP-процесса
После запуска сайта проверьте журналы, медленные SQL-запросы, OPcache и фактическую нагрузку. Не копируйте настройки высоконагруженного сервера на небольшой VPS без измерений.
Совместимость
- Основная система: AlmaLinux 10.x.
- Проверено на AlmaLinux 10.2.
- Большая часть команд применима к AlmaLinux 9.8, но версии пакетов и доступные потоки могут отличаться.
- Конфигурация рассчитана на Apache 2.4, современный nginx, PHP-FPM и MariaDB из репозиториев AlmaLinux.
Альтернативы
- nginx + PHP-FPM: лучший вариант для нового PHP-проекта без зависимости от
.htaccess. - Apache + PHP-FPM: проще, если nginx не нужен.
- Caddy: удобен для небольших проектов благодаря простой конфигурации HTTPS.
- Podman или Docker: подходят, если нужна изоляция сервисов и воспроизводимое окружение.
- Панель управления: удобна при размещении большого количества типовых сайтов, но создаёт дополнительный слой зависимостей.
Комментарии
0Нашли ошибку или хотите дополнить материал?