Roundcube за Nginx с PHP-FPM: production-развёртывание с Dovecot и Postfix
Roundcube ставят за пять минут по любому гайду — и получают webmail, который течёт данными, тормозит на вложениях и падает под первой волной перебора паролей. Разбираем развёртывание изнутри: TLS до Dovecot даже на localhost, тюнинг PHP-FPM и OPcache, четыре нужных плагина и защита логина через fail2ban плюс rate limiting на Nginx.
EvilMail Team30 июля 2026 г.14 мин чтения
Roundcube разворачивается по любому гайду за пять минут: распаковал тарбол, ткнул installer, ввёл localhost в поле IMAP — работает. Через месяц этот webmail отдаёт config.inc.php через открытый /config/, шлёт креденшелы в открытом виде на 143 порт, тормозит на каждом вложении и держит installer/ в вебруте как приглашение для RCE. А в logs/errors.log — сотни строк Login failed for от ботов, которые никто не банит.
Ключевая ошибка здесь концептуальная. Roundcube — это не «PHP-приложение, которое надо установить». Это stateless-фронтенд к вашему IMAP/SMTP-стеку. Собственного состояния у него почти нет: почта живёт в Dovecot, настройки и контакты — в его БД, а всё остальное он проксирует. Поэтому надёжность webmail на 80% определяется тем, как настроены Nginx, PHP-FPM и связка с Dovecot/Postfix, а не самим Roundcube. Разберём production-развёртывание именно с этой стороны.
Что мы собираем и почему архитектура именно такая
Поток запроса линеен: браузер по HTTPS приходит на Nginx, тот терминирует TLS, режет брутфорс и проксирует в PHP-FPM через unix-сокет. PHP-FPM выполняет Roundcube, который открывает два соединения наружу — IMAPS к Dovecot на 993 за почтой и Submission к Postfix на 587 для отправки. Настройки, адресная книга и кэш складываются в отдельную MariaDB.
Разделение ответственности жёсткое: Nginx делает TLS и защиту периметра, PHP-FPM изолирует процессы в отдельном пуле, а mailserver вообще ничего не знает про web — для Dovecot и Postfix Roundcube это обычный IMAP/SMTP-клиент, аутентифицирующийся как сам пользователь. По состоянию на 2026 актуальный набор: Roundcube 1.6.x (skin Elastic по умолчанию, Larry давно deprecated), PHP 8.3/8.4, Nginx 1.26+, Dovecot 2.3.x/2.4, Postfix 3.8+, MariaDB 10.11/11.
Окружение: PHP-FPM, расширения, база
Ставить Roundcube из apt не стоит — в дистро-пакетах версия отстаёт на год, а с 1.5 проект перешёл на Composer, и vendor/ в апт-сборках регулярно ломается. Берём тарбол complete с уже собранными зависимостями и распаковываем в /var/www/roundcube.
Расширения PHP не абстрактный список из документации, каждое несёт функцию:
intl нужен для корректной работы с IDN-доменами (без него пример.рф в адресах ломается), gd — для CAPTCHA и аватаров, zip — чтобы плагин zipdownload отдавал вложения архивом, mbstring — для кодировок заголовков, xml даёт DOM, без которого не собирается HTML-редактор писем. А вот php-imap в списке намеренно нет: Roundcube реализует протокол IMAP на чистом PHP и системное расширение не использует — его ставят по инерции, толку ноль, только лишний attack surface. Пропустишь intl — Roundcube молча деградирует на международных адресах.
База — отдельная, с пользователем на минимальных правах. Никаких ALL PRIVILEGES и общего mail-пользователя:
sql
CREATE DATABASE roundcube CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
GRANT SELECT,INSERT,UPDATE,DELETE ON roundcube.*
TO 'roundcube'@'localhost' IDENTIFIED BY 'сильный-пароль';
FLUSH PRIVILEGES;
Схему накатываем из комплекта, а не через web-installer:
bash
mysql roundcube < /var/www/roundcube/SQL/mysql.initial.sql
Права раздаём точечно. Владельцем всего вебрута должен быть не www-data — иначе скомпрометированный PHP перезапишет собственный код. Запись нужна только двум каталогам:
Гонять Roundcube в общем пуле www — значит делить лимиты памяти и процессов с любым другим сайтом на сервере. Заводим изолированный пул /etc/php/8.3/fpm/pool.d/roundcube.conf:
pm.max_children считают не «на глаз», а от памяти: max_children = (RAM, выделенная под PHP) / (средний процесс ~40–60 МБ). На VPS с 2 ГБ, где половину съедает MariaDB и Dovecot, реально держать 15–20 воркеров, не больше — иначе под нагрузкой упрётесь в swap и получите ровно тот самый «webmail тупит». post_max_size держим на 2 МБ выше upload_max_filesize, потому что multipart-обёртка добавляет накладные — иначе пользователь, приложивший файл ровно на 25 МБ, получит загадочную ошибку.
Главный рычаг скорости Roundcube — OPcache. Приложение состоит из тысяч мелких PHP-файлов, и без кэша байткода каждый запрос пересобирает их заново. В /etc/php/8.3/fpm/conf.d/10-opcache.ini:
validate_timestamps=0 в проде даёт максимум скорости, но требует явного systemctl reload php8.3-fpm при каждом обновлении Roundcube — иначе увидите старый код. JIT для I/O-bound webmail бесполезен, только жрёт память; выключаем.
Nginx: TLS, статика, проксирование в FPM
Server-блок делает три вещи: терминирует TLS, отдаёт статику напрямую и проксирует PHP в сокет. Плюс закрывает чувствительные пути — типичная дыра публичных инсталляций.
X-Frame-Options строго SAMEORIGIN, а не DENY — Elastic skin использует внутренние iframe и с DENY рассыпается. fastcgi_param HTTPS on обязателен: без него Roundcube думает, что работает по HTTP, и генерирует ссылки и куки без флага Secure. client_max_body_size держим синхронно с post_max_size PHP — рассинхрон даёт разные ошибки на разных слоях, и отладка превращается в гадание. Директиву listen ... ssl http2 в Nginx 1.25+ заменили на отдельный http2 on; — старую форму он ещё принимает, но с предупреждением.
Связка с Dovecot и Postfix: TLS даже на localhost
Здесь совершают главную ошибку: пишут imap_host = 'localhost' на 143 порту, рассуждая «это же loopback, кто перехватит». Перехватить может любой процесс на хосте, читающий loopback, а downgrade-атака при скомпрометированном соседнем сервисе снимет пароли открытым текстом. TLS до Dovecot обязателен даже на localhost. config/config.inc.php:
smtp_user = '%u' и smtp_pass = '%p' пробрасывают креденшелы самого залогиненного пользователя в Postfix — никаких сервисных аккаунтов, отправка идёт под тем же SASL-логином, что и в Dovecot. Используем FQDN mail.example.com, а не IP, чтобы работала проверка сертификата по SNI — иначе ssl:// бессмысленен, вы шифруете канал, но не знаете, с кем.
des_key меняем обязательно. Дефолтный ключ из примера конфига известен всему интернету, а им шифруются данные сессии — оставите стандартный, и любой сможет расшифровать перехваченную сессию.
Со стороны mailserver проверяем, что Postfix действительно принимает Submission с SASL и STARTTLS. В master.cf:
submission inet n - y - - smtpd
-o smtpd_sasl_auth_enable=yes
-o smtpd_tls_security_level=encrypt
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
Диагностика — не через web-интерфейс, а напрямую openssl, чтобы отделить проблему TLS от проблемы Roundcube:
Если первый показывает Verify return code: 0 (ok), а doveadm auth test проходит — проблема точно в конфиге Roundcube, а не в стеке.
Плагины: четыре нужных, остальное балласт
Соблазн натащить «десятки плагинов с гитхаба» надо давить: каждый плагин — это и attack surface, и лишний код в autoload на каждом запросе. Реально нужны четыре.
managesieve — серверные фильтры через Dovecot Pigeonhole. Правила сортировки живут на сервере и работают даже когда браузер закрыт, в отличие от клиентских. Требует managesieve_host = 'localhost:4190' и запущенного в Dovecot сервиса managesieve-login на 4190.
password — смена пароля прямо из webmail, пишет в вашу таблицу virtual_users. В config/password.inc.php:
Плейсхолдер %D заставляет плагин прогнать новый пароль через doveadm pw — поэтому и нужна строка password_dovecotpw, иначе %D молча не сработает. Схема хеша обязана совпадать с той, что ждёт Dovecot. В generic-сетапах это SHA512-CRYPT; если у вас, как в некоторых инсталляциях, всё ещё PLAIN — это повод мигрировать на нормальный scheme, а не подгонять плагин под слабость.
zipdownload — скачивание нескольких вложений одним архивом (тот самый php-zip).
newmail_notifier — уведомление о новых письмах без ручного обновления.
Отдельно про enigma (GPG в браузере): выглядит эффектно, но операционно дорог — управление ключами, их хранение и поддержка пользователей съедают больше времени, чем даёт ценности. Если нужен серьёзный E2E, он живёт в почтовом клиенте, а не в webmail.
Защита веб-почты: три слоя против брутфорса
Webmail — это форма логина, открытая всему интернету. Оборачивать её надо как публичный API: rate limit на входе, бан по факту провалов, троттлинг на самой аутентификации.
Слой 1 — Nginx срезает объём ещё до PHP-FPM, чтобы боты не жгли ваши воркеры. Тонкость: location матчится по пути, а не по query-string, а форма логина — это POST /?_task=login. Поэтому ключ для лимита собираем через map по $request_uri и применяем limit_req внутри основного php-локейшена:
Пустой ключ limit_req_zone игнорирует, так что лимит бьёт только по самому логину, а не по всем запросам. В location ~ \.php$ добавляем строку:
nginx
limit_req zone=rc_login burst=3 nodelay;
Слой 2 — fail2ban банит тех, кто прошёл rate limit, но упорно перебирает пароли. Чтобы попытки вообще попадали в лог, включаем в config.inc.php строку $config['log_logins'] = true; — тогда Roundcube пишет каждую в logs/userlogins. Фильтр /etc/fail2ban/filter.d/roundcube.conf:
ini
[Definition]
failregex = Failed login for .* from <HOST>
Слой 3 — session hardening в Roundcube плюс сама Dovecot-аутентификация. В Roundcube включаем $config['ip_check'] = true; (привязка сессии к исходному IP) и держим короткий session_lifetime; куки при HTTPS on получают Secure/HttpOnly автоматически — проверьте это в браузере. Встроенного счётчика неудачных логинов у Roundcube в core нет, и выдумывать его не будем: последний рубеж — сам Dovecot с auth_failure_delay (по умолчанию 2 с), который замедляет каждую неудачную проверку и делает перебор дорогим независимо от web-слоя.
Про CSP честно: Elastic skin активно использует inline-стили, поэтому строгий style-src 'self' его ломает — придётся оставить style-src 'self' 'unsafe-inline'. Это не «дыра», а компромисс темы; закрыть его полностью без правки core не выйдет, и врать про идеальный CSP не будем. Для админского доступа к webmail поверх всего можно повесить basic-auth или клиентский сертификат на location /.
Эксплуатация и чек-лист
Когда «не работает», смотрят в четыре места по порядку: logs/errors.log Roundcube, PHP-FPM slowlog, Nginx error_log и dovecot -a. Обновляют Roundcube через bin/installto.sh с обязательным бэкапом БД, никогда не редактируя core — все правки только в config/ и плагинах, иначе следующий апдейт затрёт изменения.
Перед выкаткой в прод пройдитесь по списку:
installer/ удалён или закрыт через deny all
des_key заменён на случайный, enable_installer = false
openssl s_client к 993 и 587 показывает валидный сертификат
imap_host/smtp_host через ssl:///tls://, а не plaintext
smtp_user='%u', smtp_pass='%p' — без сервисных аккаунтов
OPcache включён, validate_timestamps=0, reload FPM в процедуре деплоя
отдельный PHP-FPM пул, max_children посчитан от памяти
client_max_body_size синхронен с post_max_size
rate limit на логине активен через map (проверить 429)
log_logins = true, fail2ban ловит Failed login и реально банит (fail2ban-client status roundcube)
бэкап БД roundcube в cron
права: вебрут root:root, запись только на temp/ и logs/
Roundcube надёжен ровно настолько, насколько надёжен слой под ним. Соберёте Nginx, PHP-FPM и TLS-связку с Dovecot/Postfix правильно — получите webmail, который не течёт, не тормозит и не сдаётся первому же боту с password-листом.