Сервер: Настройка AlmaLinux 10: nginx + Apache + PHP-FPM + MariaDB

Настраиваем новый 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 и MariaDB 3306.
  • Используйте 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: подходят, если нужна изоляция сервисов и воспроизводимое окружение.
  • Панель управления: удобна при размещении большого количества типовых сайтов, но создаёт дополнительный слой зависимостей.
up: Просмотров: 21.9k
Материал помог? Оценка помогает находить устаревшие и спорные решения.
Оценки до обновления материала
  • Полезно1

Комментарии

0

Нашли ошибку или хотите дополнить материал?

Комментариев пока нет.Можно первым дополнить материал или сообщить об ошибке.
Telegram