Roundcube в mailcow: Access denied для базы, где искать
АйТи Фреш
Linux, Docker и DevOps

Roundcube рядом с SOGo не пускает в mailcow и показывает Access denied для базы: что проверить

Автор: , директор ООО «АйТи-Фреш» · · ~13 мин чтения
Неподходящий ключ с плейсхолдером застрял в замке базы данных — метафора ошибки Access denied в Roundcube из-за нераскрытой переменной
В конфиге остался не пароль, а сама переменная — MySQL закономерно отвечает Access denied.

Установщик Roundcube поверх mailcow на последнем шаге пишет SQLSTATE[HY000] [1045] Access denied, хотя база roundcubemail и пользователь roundcube созданы точно по инструкции. Причина почти всегда одна: в config.inc.php вместо реального пароля остаётся буквальная строка ${DBROUNDCUBE} — переменная, которую нужно было подставить руками. Показываю, где именно это происходит и какой второй похожий баг ждёт сразу после.

Кейс: тренажёрный зал ставит Roundcube и упирается в Access denied

Тренажёрный зал «Мускул-хаус» — 49 рабочих мест, включая стойку регистрации, тренерский состав и бухгалтерию, которая ведёт учёт абонементов. Основной почтовый клиент — SOGo, встроенный в корпоративную почту на mailcow, но часть сотрудников привыкла к более лёгкому интерфейсу и попросила добавить Roundcube как альтернативный веб-клиент — мы решили поставить его по официальной документации mailcow, благо она описывает установку прямо внутри существующего docker-compose.

После создания базы данных roundcubemail, пользователя MySQL roundcube и конфигурационного файла config.inc.php, установщик Roundcube на финальном шаге проверки конфигурации выдал: «DSN (write): NOT OK (SQLSTATE[HY000] [1045] Access denied for user 'roundcube'@'172.22.1.7' (using password: YES))». Пароль явно передавался — ошибка так и написана «using password: YES», — но подключение всё равно отклонялось.

Первая мысль — перепроверить права пользователя roundcube на базу roundcubemail командой SHOW GRANTS, но права оказались корректными, в точности как в документации. Проблема была не в MySQL и не в правах — она была на одну строчку выше, в самом конфиге.

Где на самом деле теряется пароль: буквальная переменная в конфиге

Официальная инструкция mailcow по установке Roundcube начинается с генерации пароля в переменную шелла: DBROUNDCUBE=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 2> /dev/null | head -c 28) — случайная 28-символьная строка, которая затем используется сразу в нескольких местах: при создании пользователя MySQL (CREATE USER 'roundcube'@'%' IDENTIFIED BY '${DBROUNDCUBE}') и в строке DSN внутри data/web/rc/config/config.inc.php ($config['db_dsnw'] = 'mysql://roundcube:${DBROUNDCUBE}@mysql/roundcubemail'). Конфиг инструкция пишет heredoc-ом cat <<EOCONFIG >data/web/rc/config/config.inc.php, то есть подставляет значение сам шелл в момент выполнения команды. А ещё раньше, в разделе подготовки, выполняется source mailcow.conf — оттуда в шелл приходят DBROOT и IPV4_NETWORK, которые понадобятся дальше.

Ключевая деталь, которую легко упустить: переменная DBROUNDCUBE существует только в текущей сессии шелла, в которой её объявили. Если между генерацией пароля и редактированием config.inc.php администратор открыл новый терминал, переключился на screen/tmux сессию, разорвал SSH-соединение или просто выполнял шаги не подряд одной командой, а по очереди вручную — переменная DBROUNDCUBE в новой сессии окажется пустой или неопределённой — документация прямо предупреждает, что при продолжении в новом шелле её нужно задать вручную. Если в такой сессии выполнить heredoc из инструкции, в DSN попадёт пустой пароль, и MySQL ответит уже «using password: NO». При этом если конфиг редактируется не подстановкой через shell (envsubst или heredoc с переменной), а прямым копипастом текста из документации в редактор, строка ${DBROUNDCUBE} попадает в файл буквально, как есть — без единой подстановки, потому что PHP не умеет разворачивать переменные окружения шелла внутри строкового литерала.

Ровно эту ситуацию описал пользователь CodeShell на форуме mailcow community в декабре 2024 года: DSN из ошибки в точности совпадал с шаблоном из документации — mysql://roundcube:${DBROUNDCUBE}@mysql/roundcubemail, то есть буквенно-символьная последовательность ${DBROUNDCUBE} так и осталась в файле вместо самого пароля. Автор проверил, что может зайти под пользователем roundcube и паролем DBROUNDCUBE вручную внутри контейнера MySQL — то есть база и права были настроены верно, — но PHP-процесс Roundcube пытался подключиться буквально со строкой ${DBROUNDCUBE} вместо пароля, а MySQL закономерно отклонял такой логин.

Быстрая проверка: `grep DBROUNDCUBE data/web/rc/config/config.inc.php`. Если grep находит строку — значит, подстановка не сработала и в файле остался буквальный плейсхолдер вместо пароля. Замените его на реальное значение вручную и повторите тест конфигурации в установщике.
Схема прохождения пароля DBROUNDCUBE от генерации в шелле до config.inc.php с двумя возможными исходами
Между генерацией пароля и записью в config.inc.php переменная легко теряется, если файл не подставлен подстановкой шелла.

Второй похожий баг, который ждёт сразу после первого

Даже после того, как DSN с базой данных исправлен и установщик Roundcube проходит успешно, часть инсталляций упирается в следующую стену — уже не при входе на страницу логина, а при попытке реально авторизоваться письмом и паролем: ошибка подключения к IMAP. Причина та же по природе, но в другом файле: инструкция mailcow для внутреннего сетевого доступа PHP-контейнера Roundcube к Dovecot требует добавить в data/conf/dovecot/extra.conf блоки с разрешением на plaintext-аутентификацию для внутренних сетей контейнеров — remote ${IPV4_NETWORK}.0/24 { disable_plaintext_auth = no } и такой же для ${IPV6_NETWORK}. По умолчанию без TLS так может входить только SOGo.

Тот же пользователь CodeShell зафиксировал именно эту вторую проблему следующим комментарием на форуме: соединение между Roundcube и Dovecot не работало, «потому что в extra.conf у plaintext-соединения был ${IPV4_NETWORK}, и это вызывало ошибки — похоже, php-fpm неправильно получает переменные из mailcow.conf». То есть тот же класс ошибки, что и с DBROUNDCUBE: в конфигурационный файл Dovecot попадает буквальный текст ${IPV4_NETWORK} вместо реального значения подсети внутренней Docker-сети mailcow, потому что dovecot читает extra.conf как обычный текстовый файл, а не как шаблон с переменными окружения.

На практике это значит, что перед перезапуском dovecot-mailcow нужно вручную подставить в extra.conf настоящее значение подсети — посмотреть его можно в mailcow.conf по переменной IPV4_NETWORK (по умолчанию mailcow использует подсеть вида 172.22.1.0/24, но при установке она могла быть переопределена, особенно если на сервере уже была другая Docker-сеть с тем же диапазоном). После правки — docker compose restart dovecot-mailcow, и только тогда Roundcube сможет достучаться до почтовых ящиков через IMAP.

Чек-лист из четырёх команд для диагностики ошибок Roundcube при установке поверх mailcow
Четыре команды проверяют оба известных источника проблемы — базу данных и IMAP-доступ — за одну итерацию.

Как исправить у себя: пошагово, не трогая работающий SOGo

Первый шаг — открыть data/web/rc/config/config.inc.php и найти строку db_dsnw. Если в ней виден буквальный ${DBROUNDCUBE} — заменить его на реальный пароль. Пароль можно достать двумя способами: если сессия шелла с переменной ещё жива, вывести её echo $DBROUNDCUBE; если сессия давно закрыта — быстрее сбросить пароль пользователю MySQL заново и тут же прописать его в оба места одной командой, не полагаясь на переменные шелла между шагами.

Второй шаг — сброс пароля, если переменная безвозвратно потеряна: из каталога mailcow выполнить source mailcow.conf, затем docker compose exec mysql-mailcow mysql -uroot -p${DBROOT} -e "ALTER USER 'roundcube'@'%' IDENTIFIED BY 'НОВЫЙ_ПАРОЛЬ';". Здесь DBROOT — пароль root MySQL из mailcow.conf; без source переменная в новой сессии тоже будет пустой, и команда упадёт уже с Access denied для root. После смены пароля прописываем то же самое значение в config.inc.php вручную, без переменных, — так исключается сам класс ошибки с непойманной интерполяцией.

Третий шаг — проверка IMAP-связки: открыть data/conf/dovecot/extra.conf, найти блоки remote ${IPV4_NETWORK}.0/24 и remote ${IPV6_NETWORK}, заменить плейсхолдеры на реальные значения подсетей из mailcow.conf (переменные IPV4_NETWORK и IPV6_NETWORK), сохранить и выполнить docker compose restart dovecot-mailcow. После этого стоит один раз войти в Roundcube под тестовым ящиком и убедиться, что письма реально подгружаются, а не только страница логина открывается без ошибок.

Четвёртый, необязательный, но полезный шаг — отключить установщик после того, как всё заработало: документация mailcow требует сделать и то и другое: удалить каталог data/web/rc/installer и выставить $config['enable_installer'] = false; в config.inc.php (в инструкции для этого есть готовая команда sed). Работающий установщик, доступный по адресу вида https://mail.example.com/rc/installer/, — не просто мусор, а потенциальная точка входа для постороннего, который сможет увидеть структуру вашей конфигурации или попытаться пересоздать её под себя, если случайно наткнётся на этот URL.

Сравнение состояния Roundcube до и после исправления плейсхолдеров DBROUNDCUBE и IPV4_NETWORK у клиента Мускул-хаус
Обе переменные оказались нераскрытыми одновременно — типичный след установки по шагам с перерывами между сессиями шелла.

Что сделали у клиента и почему я не советую городить plaintext шире, чем нужно

У «Мускул-хаус» оказались сразу обе проблемы: и буквальный ${DBROUNDCUBE} в config.inc.php, и нетронутый ${IPV4_NETWORK} в extra.conf — типичная ситуация, когда установка делалась по шагам инструкции не одной непрерывной сессией, а с перерывами между настройкой базы и правкой конфигов. Мы вручную подставили реальный пароль и реальную подсеть и перезапустили dovecot-mailcow в обеденное окно. SOGo работал всё время, кроме нескольких секунд рестарта Dovecot, — config.inc.php Roundcube его не касается, а в extra.conf мы добавили только remote-блоки для внутренних сетей, не трогая остальные настройки Dovecot.

Отдельно стоит сказать про сам смысл disable_plaintext_auth = no в extra.conf: это разрешение работает только для указанных внутренних подсетей Docker (${IPV4_NETWORK}.0/24 и IPv6-сеть mailcow), а не глобально для всего Dovecot — то есть внешние подключения к почтовому серверу через открытый интернет по-прежнему обязаны идти через TLS. Ошибка новичка — расширить этот блок на более широкий диапазон адресов, «чтобы уж точно заработало», вместо того чтобы разобраться, почему конкретная переменная не подставилась. Я советую держать этот блок ровно таким, каким его описывает официальная документация, и разбираться с причиной ошибки, а не расширять зону доверенных сетей на глазок.

После этого случая я проверяю оба файла — config.inc.php и extra.conf — сразу после установки любой дополнительной надстройки поверх mailcow, будь то Roundcube или что-то ещё: grep -r '\${' data/web/rc/config/ data/conf/dovecot/extra.conf одной командой находит все нераскрытые плейсхолдеры разом, вместо того чтобы ловить их по одному через разные симптомы (сначала Access denied для базы, потом обрыв IMAP). Тот же принцип «искать буквальные плейсхолдеры, а не гадать по симптомам» я применяю при любой установке mailcow с нуля — большинство постустановочных проблем сводится именно к переменным, которые не подставились там, где их ждали.

Стоит ли вообще ставить Roundcube рядом с SOGo

Прежде чем повторять эту установку у себя, стоит честно оценить, зачем вообще нужен второй веб-клиент почты рядом со встроенным SOGo. Roundcube даёт более лёгкий и привычный многим пользователям интерфейс, плагины вроде managesieve для фильтров и archive для архивации писем в один клик — но он не является частью штатного mailcow: ставится как сторонняя надстройка (раздел third_party документации) — файлы кладутся в data/web/rc и исполняются в php-fpm-mailcow — и не получает обновлений вместе с остальным стеком при ./update.sh.

Это значит, что обновлять Roundcube — отдельная ручная задача: документация описывает её так: скачать complete.tar.gz нужного релиза внутрь php-fpm-mailcow, выполнить bin/installto.sh /web/rc, затем при необходимости composer update --no-dev -o и перезапустить php-fpm-mailcow. Плюс время от времени composer audit на уязвимые зависимости. Для компании с активным администратором, который готов взять на себя это сопровождение, Roundcube — разумный бонус к SOGo. Для остальных я обычно рекомендую для начала показать сотрудникам сам SOGo — за последние пару лет его интерфейс заметно доработали, и часто разница с Roundcube по факту сводится к привычке, а не к реальному функциональному разрыву, который стоил бы содержания второго клиента с отдельным циклом обновлений.

Отдельно я предупреждаю клиентов, которые параллельно держат резервное копирование всего сервера: сторонние файлы вроде data/web/rc и правки в data/conf/dovecot/extra.conf не входят в штатный бэкап mailcow скриптом backup_and_restore.sh (база roundcubemail при этом попадает в копию MySQL вместе с остальными базами), — при восстановлении из архива можно неожиданно получить SOGo без Roundcube и снова столкнуться с той же историей про буквальные плейсхолдеры, потому что конфиг Roundcube восстановился не полностью или из старой копии. Если решаете оставить Roundcube в проде — включите его каталоги в регламент бэкапа явным пунктом, а не полагайтесь, что штатный скрипт mailcow заберёт их автоматически заодно со всем остальным.

Частые вопросы

Access denied означает, что пароль неверный или права не настроены?

Чаще всего ни то ни другое — база и права созданы верно, а в config.inc.php остаётся буквальная строка ${DBROUNDCUBE} вместо реального пароля, потому что переменная шелла не была подставлена при редактировании файла. Проверяется командой grep DBROUNDCUBE config.inc.php.

Почему SQLSTATE 1045 пишет using password: YES, если пароль якобы не передаётся?

Пароль передаётся — просто это буквальный текст ${DBROUNDCUBE}, а не сгенерированное значение. MySQL честно сообщает, что пароль был указан (YES), но именно эта строка не совпадает с паролем пользователя roundcube в базе.

Нужно ли открывать plaintext-аутентификацию Dovecot для всего сервера ради Roundcube?

Нет — блоки disable_plaintext_auth = no в extra.conf ограничены внутренними подсетями Docker (${IPV4_NETWORK}.0/24 и IPv6-сеть mailcow), внешние подключения по-прежнему обязаны использовать TLS. Расширять диапазон не нужно.

Как узнать реальное значение IPV4_NETWORK для своей установки?

Выполните grep IPV4_NETWORK mailcow.conf — там указана подсеть, которую использует внутренняя Docker-сеть mailcow. По умолчанию mailcow предлагает 172.22.1, но значение могло быть изменено при установке.

Получит ли Roundcube обновления автоматически вместе с mailcow?

Нет. Roundcube ставится как сторонняя надстройка (third_party): файлы лежат в data/web/rc и работают в php-fpm-mailcow, но штатный ./update.sh их не обновляет. Обновление — вручную через bin/installto.sh по разделу Upgrading Roundcube документации.

Столкнулись с похожей задачей? Обращайтесь — решим

Если у вас происходит что-то из описанного в этой статье — или любая другая проблема с ИТ-инфраструктурой, — обращайтесь в любое время. Мои специалисты и я лично разберём ситуацию, найдём настоящую причину и доведём до решения.

Возьмёмся и за разовую задачу, и за постоянное обслуживание. Первичная консультация — бесплатно и без обязательств.

📞 +7 903 729-62-41 ✈ Telegram @ITfresh_Boss

С уважением, Семёнов Евгений Сергеевич, директор «АйТи Фреш» — IT-аутсорсинг для компаний до 50 рабочих мест, 15+ лет практики

Источники

© ООО «АйТи-Фреш» · Москва · Все статьи