nginx mailcow не стартует на [::]:7080: решение
АйТи Фреш
Linux, Docker и DevOps

После обновления mailcow nginx не стартует на [::]:7080: какой переключатель IPv6 ему нужен

Автор: , директор ООО «АйТи-Фреш» · · ~13 мин чтения
Контейнер nginx mailcow не запускается из-за конфликта переменных ENABLE_IPV6 и DISABLE_IPv6 после обновления
Один непереключённый флаг IPv6 в шаблоне — и веб-почта у всех сотрудников встаёт колом.

Если после обновления mailcow контейнер nginx не поднимается, а в логе — socket() [::]:7080 failed, веб-почта легла не из-за IPv6, а из-за того, что шаблон nginx проверяет не ту переменную. Разбираю, что изменилось в конфигурации IPv6 с релиза 2025-09 и что прописать в mailcow.conf, чтобы вернуть доступ.

Симптом: после update.sh веб-почта и админка недоступны у всех сразу

Ко мне обратилось маркетинговое агентство «Маркетинговая мастерская» — 49 рабочих мест, своя почта на mailcow для переписки с клиентами и рассылок, обслуживаем корпоративную почту на mailcow у них уже второй год. В воскресенье вечером сработал плановый апдейт: update.sh отработал без ошибок, но при попытке зайти в веб-почту утром понедельника — таймаут. SSH жив, контейнер mysql-mailcow и postfix-mailcow в docker compose ps числятся как running, а вот nginx-mailcow — в статусе Restarting по кругу.

Смотрю docker compose logs nginx-mailcow --tail 50: в логе по кругу одна и та же пара строк — [emerg] 1#1: socket() [::]:7080 failed (97: Address family not supported by protocol) и её дубль с префиксом nginx: [emerg]. Сервер — обычный VPS без выданного провайдером IPv6, IPv6 на нём никогда не настраивался и не использовался, поэтому картина выглядит абсурдно: почему nginx вдруг решил слушать IPv6-сокет там, где его физически нет и раньше он туда не пытался?

Порт 7080 — не магия mailcow, а значение HTTP_PORT из mailcow.conf этой конкретной установки. У агентства mailcow стоит за обратным прокси на том же хосте, поэтому когда-то по инструкции Reverse Proxy его убрали со стандартных портов: HTTP_PORT=7080, HTTPS_PORT=7443, HTTP_BIND=127.0.0.1. В шаблоне nginx каждая директива listen {{ HTTP_PORT }} имеет парную listen [::]:{{ HTTP_PORT }}, обёрнутую в условие по IPv6. Если это условие срабатывает неверно, nginx не может создать IPv6-сокет, и nginx -t валит весь конфиг — падают не только IPv6-функции, а вообще все веб-сервисы mailcow разом, включая обычный HTTPS на IPv4. Поэтому авария выглядит как полный отказ, а не как частичная деградация одного протокола. Если у вас mailcow на стандартных портах, в той же ошибке будет [::]:80 или [::]:443.

Что реально изменилось в mailcow с релиза 2025-09

До сентября 2025 года отключение IPv6 в mailcow было многоступенчатой ручной работой: по старой инструкции правили enable_ipv6: false прямо в сетевой секции docker-compose.yml, глушили контейнер ipv6nat-mailcow через docker-compose.override.yml, ставили do-ip6: no в data/conf/unbound/unbound.conf и дописывали inet_protocols = ipv4 в data/conf/postfix/extra.cf. С релиза 2025-09 (10 сентября 2025 года) разработчики свели это к одному переключателю ENABLE_IPV6 в mailcow.conf — по официальной инструкции по отключению IPv6 достаточно одной строки и пересоздания стека через docker compose down / docker compose up -d. По умолчанию переменная равна true, если система поддерживает IPv6.

Проблема переходного периода была в самом шаблоне: в сборке 2025-09a файл data/conf/nginx/templates/nginx.conf.j2, из которого внутри контейнера рендерится итоговый конфиг nginx, всё ещё проверял старую переменную DISABLE_IPv6 вместо новой ENABLE_IPV6. Новый mailcow.conf старую переменную уже не содержит, шаблон воспринимал пустое значение как «IPv6 не отключён», подставлял listen [::]:<порт> — и nginx падал, пытаясь забиндиться на адресное семейство, которого на сервере нет вообще. Отдельная ловушка тех дней: автор issue заметил, что документация писала имя переменной как ENABLE_IPv6, а правильно — ENABLE_IPV6, заглавными целиком.

Временная шкала перехода mailcow с переменной DISABLE_IPv6 на ENABLE_IPV6 и связанной ошибки nginx
Ошибка живёт ровно в переходном окне между старой и новой схемой отключения IPv6.

Подтверждение из трекера и что сказали разработчики

Тот же симптом описан в issue #6737 репозитория mailcow-dockerized, открытом вечером 11 сентября 2025 года: после обновления до 2025-09a nginx-mailcow падает на системе без IPv6 или с отключённым IPv6, в логе — та же строка socket() [::]:7080 failed. Исправление уже лежало в PR #6736 («Disable IPv6 support in Nginx configuration»), и 12 сентября разработчик ответил, что в этот же день выйдет 2025-09b с этим фиксом — релиз действительно вышел 12 сентября. Позже, в 2025-09c от 7 октября 2025 года, отдельным PR #6762 («do not invert ENABLE_IPV6») поправили ещё и логику условия в шаблоне — то есть баг чинили в два захода, и 2025-09a, 2025-09b и 2025-09c могли вести себя по-разному даже с одинаковым конфигом.

Из этого следует практический вывод: если у вас mailcow новее 2025-09c, а проблема всё равно есть — дело не в старом баге шаблона. Первое, что проверяю, — есть ли в mailcow.conf строка ENABLE_IPV6 вообще. В docker-compose.yml для nginx и для сети стоит значение по умолчанию ${ENABLE_IPV6:-true}: если строки нет, стек считает, что IPv6 включён, и на хосте без IPv6 получаем ровно ту же ошибку. Второе — легаси-правки от старой схемы отключения, которые конфликтуют с новой логикой.

Ещё один нюанс того же релиза: начиная с 2025-09 mailcow требует установленный на хосте jq — через него переработанный IPv6-контроллер аккуратно правит /etc/docker/daemon.json, и без него обновление останавливается с просьбой поставить утилиту. Похожая по духу история с Nginx в Docker, который получает Address not available под нагрузкой — тоже конфликт между тем, что ожидает конфигурация контейнера, и тем, что реально доступно на хосте. Проверяю which jq перед любым апдейтом на крупный релиз, отдельно от списка предварительных требований в самой инструкции.

Как я это чиню: одна строка и пересоздание стека

Первым делом сверяю версию mailcow и смотрю, что реально записано в конфиге:

grep -i ipv6 /opt/mailcow-dockerized/mailcow.conf

Если в выводе видна только ENABLE_IPV6= (пусто) или переменной нет вовсе — добавляю или правлю явно:

sed -i '/^ENABLE_IPV6=/d' /opt/mailcow-dockerized/mailcow.conf
echo 'ENABLE_IPV6=false' >> /opt/mailcow-dockerized/mailcow.conf

Дальше обязательно пересоздаю стек, а не просто перезапускаю контейнер — простой restart не подтягивает новый рендер nginx.conf из j2-шаблона:

cd /opt/mailcow-dockerized
docker compose down
docker compose up -d

У «Маркетинговой мастерской» после этого nginx-mailcow поднялся с первого раза, docker compose ps показал running (healthy), веб-почта и Admin UI открылись через обратный прокси без дополнительных правок. Если хост IPv6 всё же поддерживает и вы хотите его использовать — ставите ENABLE_IPV6=true, но тогда, по прямому предупреждению документации, сначала убедитесь, что Docker Daemon настроен под реальную IPv6-связность хоста: при неправильной настройке Docker транслирует внешние IPv6-адреса во внутренние IPv4, и сервер может превратиться в открытый релей.

Сравнение старой ручной схемы отключения IPv6 в mailcow и новой схемы через переменную ENABLE_IPV6
То, что раньше требовало правки четырёх файлов, теперь укладывается в одну строку конфига.

Legacy-хвосты: когда одной строки в mailcow.conf недостаточно

Если апдейт делался с версии старше 2025-09 и до этого IPv6 когда-то отключали вручную по старой схеме — стоит проверить три места. Первое — docker-compose.override.yml: по старой инструкции там глушили сервис ipv6nat-mailcow, которого в актуальном docker-compose.yml уже нет, так что override теперь описывает лишний самостоятельный сервис. Второе — локальные правки enable_ipv6 прямо в docker-compose.yml: они мешают update.sh чисто подтянуть новую версию файла, где сеть уже управляется переменной ${ENABLE_IPV6:-true}. Третье — /etc/docker/daemon.json: документация просит, чтобы при отключённом IPv6 там не было "ipv6": true и fixed-cidr-v6.

Второй класс легаси-хвостов — правки в data/conf/unbound/unbound.conf и data/conf/postfix/extra.cf, которые раньше добавляли вручную по старой инструкции (do-ip6: no, smtp_address_preference = ipv4, inet_protocols = ipv4). Сами по себе они nginx не валят, но после перехода на единый переключатель превращаются в скрытые исключения: через год никто не вспомнит, почему Postfix не ходит по IPv6, когда его решат включить. Я в таких случаях убираю ручные правки и оставляю единственный источник правды — ENABLE_IPV6 в mailcow.conf, а если хвост нужен осознанно, записываю его в паспорт сервера.

У «Маркетинговой мастерской» как раз нашёлся такой хвост: два года назад кто-то до нас отключал IPv6 по старой инструкции и оставил docker-compose.override.yml с заглушкой для ipv6nat-mailcow, а в extra.cf — строку inet_protocols = ipv4. После перехода на новую схему override поднимал одинокий контейнер-заглушку без смысла, а в mailcow.conf после апдейта строки ENABLE_IPV6 не оказалось — и стек по умолчанию считал IPv6 включённым. Удалил заглушку из override, прописал ENABLE_IPV6=false, пересоздал стек — только тогда конфигурация внутри контейнера стала соответствовать тому, что записано в mailcow.conf. В целом это тот же класс проблем, что я разбирал в статье про честный разбор mailcow как почтового сервера — накопленные за годы ручные правки поверх автогенерируемых конфигов рано или поздно конфликтуют с новой логикой апдейтов.

Чек-лист из пяти пунктов для проверки nginx и IPv6 после обновления mailcow
Успешный вывод update.sh ничего не говорит о том, поднялся ли на самом деле каждый контейнер.

Как проверить, что nginx действительно поднялся правильно

После docker compose up -d смотрю не только на статус running, но и на то, что внутри контейнера слушают нужные сокеты:

docker compose exec nginx-mailcow netstat -tlnp | grep -E '80|443|7080'

Если хост без IPv6 и ENABLE_IPV6=false выставлен верно, в выводе должны быть только IPv4-сокеты (в нашем случае 0.0.0.0:7080 и 0.0.0.0:7443, на стандартной установке — :80 и :443), без :::7080 или [::]. Если в образе нет netstat, то же самое покажет ss -tlnp. Если строка с [::] всё ещё есть — значит, контейнер не пересоздался с новым значением, и стоит проверить, не запущен ли параллельно второй стек с другим mailcow.conf (бывает, если в системе несколько установок или неправильно указан --project-directory).

Отдельно проверяю логи watchdog: docker compose logs watchdog-mailcow --tail 30 — он первым заметит, если nginx после починки снова уйдёт в перезапуск, и это удобнее, чем ждать жалобы от сотрудников на недоступную веб-почту в разгар рабочего дня.

Если после пересоздания стека nginx-mailcow поднялся, но снаружи сайт всё равно недоступен — проверяю ещё и внешний firewall или обратный прокси перед mailcow, если он есть: смена IPv6/IPv4-биндинга внутри контейнера иногда требует и пересмотра правил на уровне хоста или облачного firewall, особенно если раньше туда прицельно пускали и IPv6-трафик тоже. Это уже не баг mailcow, а последствие того, что внешняя инфраструктура была настроена под старую схему.

Ещё один способ подстраховаться — держать под рукой заранее сохранённую рабочую копию mailcow.conf перед каждым обновлением. У меня это отдельный шаг в регламенте: cp mailcow.conf mailcow.conf.bak-$(date +%F) перед update.sh, чтобы при любой странности с конфигом можно было быстро сравнить diff-ом, что именно поменялось после апдейта, а не гадать по памяти, какая строка была раньше.

Что я закладываю в регламент обновлений после этого случая

Сообщение об успешном завершении update.sh — это не то же самое, что «всё работает». Скрипт репортит об успешном выполнении своих шагов, но не гарантирует, что все контейнеры стека дальше поднимутся и останутся живыми — именно поэтому в регламент я включил отдельный пункт с ручной сверкой статусов после каждого крупного обновления, а не только просмотр итогового кода выхода.

После этого случая я завёл на серверах клиентов на mailcow простой скрипт-обвязку вокруг update.sh, который после завершения сам ждёт минуту и проверяет healthcheck всех контейнеров, а если что-то не healthy — шлёт уведомление, вместо того чтобы сотрудники узнавали о падении веб-почты методом «не открывается» в рабочий понедельник.

Плановые обновления mailcow я теперь ставлю не в воскресенье вечером, а в будний день ближе к обеду: если что-то пойдёт не так, я на месте и могу починить за 15 минут, а не разбираю аварию по телефону вечером выходного, пока клиент уже пишет в трёх чатах, что «почта не работает». Это не техническое решение, а организационное, но именно оно чаще всего экономит нервы обеим сторонам.

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

Почему на сервере без IPv6 nginx вообще пытался слушать [::]:7080?

Из-за переходного бага в шаблоне nginx.conf.j2 в сборке 2025-09a: он ещё проверял старую переменную DISABLE_IPv6, которой в новом mailcow.conf уже не было, читал это как «IPv6 не отключён» и подставлял listen [::]:<HTTP_PORT>. 7080 — это HTTP_PORT конкретной установки за обратным прокси.

Достаточно ли просто перезапустить контейнер nginx-mailcow, не трогая остальные?

Нет. docker compose restart nginx-mailcow не пересобирает конфиг из j2-шаблона — нужно docker compose down && docker compose up -d (или как минимум пересоздание контейнера nginx), чтобы новое значение ENABLE_IPV6 попало в итоговый nginx.conf.

Что если мне как раз нужен IPv6 на этом сервере?

Ставите ENABLE_IPV6=true в mailcow.conf, но сначала убедитесь, что IPv6 на хосте и в Docker Daemon настроен под реальную связность — документация mailcow прямо предупреждает, что ошибка здесь может превратить сервер в открытый релей.

Обновление было ещё летом 2025 — актуален ли этот баг сейчас, в сентябре 2026?

Сам баг шаблона исправлен в 2025-09b (PR #6736, 12.09.2025), логику условия дополнительно поправили в 2025-09c (PR #6762, 07.10.2025). Но если в mailcow.conf нет строки ENABLE_IPV6, стек по умолчанию считает IPv6 включённым — поэтому проверка grep -i ipv6 mailcow.conf уместна при любом крупном обновлении.

Где посмотреть, какая версия mailcow сейчас установлена?

Версия видна в интерфейсе администратора mailcow, а в каталоге установки её покажет git describe --tags. Release notes по каждой сборке публикуются на mailcow.email/posts/.

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

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

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

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

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

Источники

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