После обновления mailcow nginx не стартует на [::]:7080: какой переключатель 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, заглавными целиком.
Подтверждение из трекера и что сказали разработчики
Тот же симптом описан в 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, и сервер может превратиться в открытый релей.
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 действительно поднялся правильно
После 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 минут, а не разбираю аварию по телефону вечером выходного, пока клиент уже пишет в трёх чатах, что «почта не работает». Это не техническое решение, а организационное, но именно оно чаще всего экономит нервы обеим сторонам.
- Перед update.sh — снимок diff текущего mailcow.conf и docker-compose.override.yml, если он есть
- После update.sh — сверка docker compose ps на все контейнеры в running (healthy), не только на код возврата скрипта
- Отдельная проверка grep -i ipv6 mailcow.conf на крупных релизах, где меняется схема конфигурации (как в 2025-09)
- Тестовый заход в веб-почту и Admin UI с внешнего IP сразу после обновления, а не по докладу сотрудников
- Чтение release notes на mailcow.email перед обновлением прод-инстанса, а не только changelog в самом update.sh
Частые вопросы
Почему на сервере без 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/.
Источники
- GitHub issue #6737 — Disable IPv6 does not disable ipv6 in mailcow-nginx — Проверил текст ошибки socket() [::]:7080 failed (97: Address family not supported by protocol), версию 2025-09a, ссылку на PR #6736 и ответ разработчика 12.09.2025 о выпуске 2025-09b. https://github.com/mailcow/mailcow-dockerized/issues/6737
- mailcow docs — Disable IPv6 — Проверил схему с 2025-09 (ENABLE_IPV6=false, docker compose down/up -d), старую схему до 2025-09 (enable_ipv6 в docker-compose.yml, ipv6nat-mailcow в override, do-ip6, extra.cf) и предупреждение об открытом релее. https://docs.mailcow.email/post_installation/firststeps-disable_ipv6/
- mailcow Mootember 2025-09 release notes — Проверил даты: 2025-09 — 10.09.2025 (требование jq), 2025-09b — 12.09.2025 (PR #6736), 2025-09c — 07.10.2025 (PR #6762 «do not invert ENABLE_IPV6»). https://mailcow.email/posts/2025/release-2025-09/
- generate_config.sh (master, GitHub) — Сверил строку ENABLE_IPV6=${IPV6_BOOL} в шаблоне mailcow.conf и подключение ipv6_controller.sh. https://github.com/mailcow/mailcow-dockerized/blob/master/generate_config.sh
- data/conf/nginx/templates/nginx.conf.j2 (master, GitHub) — Сверил актуальный шаблон: условие {% if ENABLE_IPV6 %} перед директивами listen [::]:{{ HTTP_PORT }} / {{ HTTPS_PORT }}; в docker-compose.yml для nginx-mailcow — ENABLE_IPV6=${ENABLE_IPV6:-true}. https://github.com/mailcow/mailcow-dockerized/blob/master/data/conf/nginx/templates/nginx.conf.j2



