Как правильно отключить IPv6 в mailcow
АйТи Фреш
Linux, Docker и DevOps

Как правильно отключить IPv6 в mailcow, чтобы не сломать Docker и не открыть SMTP-релей

Автор: , директор ООО «АйТи-Фреш» · · ~13 мин чтения
Иллюстрация: неверно настроенный IPv6 в Docker обходит проверку доверенной сети mailcow и открывает риск релея
Рассинхрон IPv6 между mailcow.conf и Docker daemon — самый тихий путь к открытому релею

С релиза mailcow 2025-09 IPv6 отключается одной переменной ENABLE_IPV6=false, но старые инструкции через docker-compose.override.yml и рассинхрон с настройками Docker-демона всё ещё ломают апдейты и открывают риск релея через IPv6. Разбираю правильный порядок на актуальной версии, два известных открытых бага и кейс с 42 рабочими местами.

Симптом: контейнер netfilter-mailcow падает в перезапуск после отключения IPv6

Классическая история для клиента, у которого почта на mailcow стоит на хосте без поддержки IPv6 (типично для многих VPS): администратор отключает IPv6 на уровне ядра (параметр загрузки ipv6.disable=1, после которого семейство адресов IPv6 в системе просто отсутствует) — и контейнер netfilter-mailcow начинает уходить в бесконечный рестарт с ошибкой вида «Address family not supported by protocol». Почта при этом продолжает ходить, потому что Postfix и Dovecot к сбою netfilter не чувствительны, — и это самое опасное: контейнер защиты от перебора паролей лежит, а внешне всё выглядит рабочим.

Второй частый сценарий — обратный: администратор давно отключил IPv6 по старой инструкции, всё работало годами, а после планового update.sh стек mailcow вообще не поднимается с ошибкой вида services must be a mapping в docker-compose.override.yml. Оба симптома — следствие одной и той же причины: до релиза 2025-09 в mailcow не было единого штатного переключателя IPv6, и версия используемого метода отключения должна точно совпадать с версией самого mailcow.

Если вы вообще впервые разворачиваете mailcow и ещё не решили, отключать ли IPv6 сразу на этапе установки, порядок первого запуска я подробно разбирал в пошаговом регламенте развёртывания mailcow — там же объясняю, почему решение про IPv6 разумнее принимать один раз на старте, а не переключать туда-сюда на живой инсталляции.

Актуальный способ — ENABLE_IPV6=false в mailcow.conf (релиз 2025-09+)

Начиная с релиза 2025-09 (вышел 10.09.2025, с последующими ревизиями 2025-09b и 2025-09c) mailcow получил переработанный IPv6-контроллер, и документация по отключению IPv6 теперь описывает единственный параметр в mailcow.conf: ENABLE_IPV6=false. По умолчанию, если хост поддерживает IPv6, переменная равна true. Изменение значения не применяется на лету — нужно пересоздать сеть контейнеров: docker compose down, затем docker compose up -d.

Для этого же релиза разработчики отдельно предупредили: mailcow теперь требует установленный на хосте jq, потому что обновлённый IPv6-контроллер использует его для правки /etc/docker/daemon.json при синхронизации настроек IPv6 между mailcow и самим Docker-демоном. Если jq на хосте нет, апдейт до 2025-09 и работу нового контроллера стоит проверить заранее — команда which jq или apt install jq для Debian/Ubuntu снимает вопрос.

Отдельно стоит проговорить: сама по себе переменная ENABLE_IPV6 управляет только тем, слушают ли контейнеры mailcow IPv6-сокеты и настраивает ли мейлкоу IPv6 NAT внутри своей docker-сети. Она не убирает IPv6-адрес с самого хоста и не трогает системные настройки ядра — если задача именно «полностью убрать IPv6 из системы», ENABLE_IPV6=false нужно рассматривать как настройку уровня mailcow, а отключение на уровне ОС (sysctl net.ipv6.conf.all.disable_ipv6 и аналоги) делать отдельным, независимым шагом.

Сравнение старого и нового способа отключения IPv6 в mailcow до и после релиза 2025-09
Старая схема через override.yml ломается при обновлении — новая держится на одной переменной

Как было раньше: override.yml, ipv6nat-mailcow и почему это ломалось

До 2025-09 единого параметра не существовало, и официальная инструкция сводилась к ручной правке docker-compose.override.yml — туда добавлялся псевдо-сервис, отключающий контейнер ipv6nat-mailcow, примерно так: services: ipv6nat-mailcow: image: bash:latest restart: "no" entrypoint: ["echo", "ipv6nat disabled"]. Это работало, но было хрупко: файл override.yml — не то, что mailcow ожидает трогать сам, и в issue #6295 (открыт 05.02.2025, помечен confirmed и закрыт 11.09.2025 — на следующий день после выхода 2025-09 с новым IPv6-контроллером) описан ровно такой случай — update.sh на версии mailcow 2025-01 стирал содержимое override.yml до пустого services:, оставляя невалидный YAML и падение всего стека с ошибкой services must be a mapping.

Причём override.yml был лишь одним из шести шагов старой инструкции: ещё правили enable_ipv6: false в сети docker-compose.yml, do-ip6: no в конфиге unbound, inet_protocols = ipv4 в data/conf/postfix/extra.cf, listen-адреса Dovecot и php-fpm и DISABLE_IPv6=y для nginx. Если у вас в инфраструктуре IPv6 отключался по такой старой схеме и вы обновляетесь на 2025-09 или новее, override.yml с этим блоком нужно убрать перед обновлением и заменить на ENABLE_IPV6=false, а остальные ручные правки пересмотреть — оставлять оба способа одновременно смысла нет, а комбинация старого override.yml с новым контроллером — самый частый источник конфликтов, которые я вижу у клиентов, «унаследовавших» инфраструктуру от предыдущего администратора.

Кстати, сам update.sh и его капризы к docker-compose.override.yml — известная история для mailcow не только применительно к IPv6: похожий по духу разбор конфликта override-файлов и версий Docker Compose я делал в статье про почему update.sh отвергает Docker Compose v5. Логика та же: чем больше ручных правок накопилось в override.yml за годы эксплуатации, тем выше шанс, что очередной апдейт заденет именно их.

Открытый баг: netfilter-mailcow не учитывает отключённый IPv6

Даже на актуальной версии есть нюанс, о котором стоит знать заранее. В issue #6700 (открыт 30.08.2025, ещё до выхода 2025-09, помечен как enhancement и на момент написания статьи остаётся открытым) описан сбой именно контейнера netfilter-mailcow: трассировка падает в функции clearIPv6Table() при попытке создать iptc.Table6(iptc.Table6.FILTER) на системе, где IPv6 недоступен на уровне ядра, с той же ошибкой «Address family not supported by protocol». Автор issue предлагает патч — проверять переменную DISABLE_IPV6 из mailcow.conf при старте и полностью пропускать инициализацию IPv6-таблиц, SNAT6-поток и очистку IPv6-цепочек, если она включена. Это предложение автора багрепорта, а не подтверждённое разработчиками исправление — на момент проверки issue остаётся открытым без смёрженного патча. Я заглянул в текущий код netfilter: функция clear() по-прежнему вызывает clearIPv6Table() без всякой проверки, так что на хосте с ipv6.disable=1 сбой воспроизводится и после ENABLE_IPV6=false. А вот если IPv6 выключен только через sysctl disable_ipv6, семейство адресов в ядре остаётся, ip6tables работает, и этого падения быть не должно.

Практический вывод: если после ENABLE_IPV6=false и docker compose up -d контейнер netfilter-mailcow всё равно уходит в рестарт с этой ошибкой — вы не единственный, кто на это наткнулся, дело не в ошибке настройки на вашей стороне, а в известном пограничном случае контейнера netfilter-mailcow. Сообщать об этом стоит в тот же issue #6700 со своей версией mailcow и выводом docker compose logs netfilter-mailcow, а не искать проблему в собственном конфиге часами.

Дерево решений при ошибке Address family not supported в netfilter-mailcow после отключения IPv6
Если сеть пересоздана, а ошибка осталась — это открытый баг платформы, а не ваш конфиг

Почему рассинхрон с Docker может превратить почту в открытый релей

Это часть, из-за которой я всегда советую не отключать IPv6 «на глаз» только внутри mailcow.conf, забыв про сам Docker. Документация mailcow прямо предупреждает: если Docker daemon настроен неправильно, он может транслировать внешний IPv6-адрес во внутренний IPv4-адрес контейнера через NAT — и с точки зрения Postfix внутри mailcow такой трафик будет выглядеть так, будто он пришёл из доверенной внутренней docker-подсети, где relay для локальных клиентов разрешён по умолчанию. Итог — сервер начинает пересылать письма для случайных внешних адресатов, которых сам администратор никогда не разрешал как relay-клиентов.

Поэтому после ENABLE_IPV6=false стоит проверить сам /etc/docker/daemon.json — там не должно оставаться включённого "ipv6": true или настроенного fixed-cidr-v6, если вы действительно уходите с IPv6 полностью; и наоборот, если IPv6 нужен, но сеть настроена неаккуратно, стоит проверять именно эту связку, а не только конфиг mailcow. Отдельно учтите исторический баг Docker версий 25.0.0–25.0.2: там одного "ipv6": false в daemon.json было недостаточно из-за ошибки в самом движке выделения IPv6-адресов, официально это исправлено в Docker 25.0.3 — если у вас Docker старше, сначала обновите его, а потом разбирайтесь с mailcow.

Общие принципы фильтрации трафика Docker на уровне iptables/nftables и типичные ошибки с доверенными подсетями я разбирал отдельно в статье про перенос фильтрации Docker на nftables — она пригодится, если проблема с релеем окажется не в IPv6, а в более широкой настройке доверенных сетей Docker на вашем хосте.

Кейс «Дебютная роль»: 42 рабочих места, VPS без IPv6 у хостера

У клиента — театральной школы «Дебютная роль», 42 рабочих места — почта на mailcow крутится на VPS, у которого хостер в принципе не выдаёт IPv6-адрес. Прежний администратор в своё время отключил IPv6 по инструкции 2024 года через override.yml с блоком ipv6nat-mailcow и надолго застрял на ветке 2025-01. Когда школа наконец решила обновиться, первый же запуск update.sh оборвался той самой ошибкой services must be a mapping из issue #6295: override.yml оказался стёрт до пустого services.

Восстановили в четыре шага: вернули docker-compose.override.yml к валидному YAML без старого блока ipv6nat-mailcow, довели обновление до актуальной версии, прописали ENABLE_IPV6=false в mailcow.conf, выполнили docker compose down && docker compose up -d. После этого проверили /etc/docker/daemon.json — там оставался старый "ipv6": true от предыдущей настройки, что как раз создавало пограничный риск релея, хоть заметных инцидентов и не фиксировалось; убрали лишнюю строку и перезапустили Docker daemon целиком. Контейнер netfilter-mailcow на этой инсталляции багом из issue #6700 не страдал — но я предупредил клиента, что при следующих обновлениях это стоит проверять в первую очередь, а не считать почту здоровой только потому, что письма ходят.

Вся работа заняла около двух часов, из них добрая половина — на то, чтобы аккуратно восстановить override.yml без потери других правок, которые прежний администратор туда добавил за три года (там же оказался, например, кастомный healthcheck для одного из контейнеров, не имевший отношения к IPv6). Именно поэтому я не советую слепо удалять override.yml целиком при переходе на ENABLE_IPV6 — сначала стоит прочитать, что реально в нём накопилось, и убрать только блок, отвечающий за IPv6.

Пошаговый чек-лист корректного отключения IPv6 в mailcow без риска open relay и падения netfilter
Пять шагов вместо разбора инцидента: версия, jq, переменная, daemon.json, известный баг

Чек-лист отключения IPv6 в mailcow

Порядок, которого я придерживаюсь на любой инсталляции: сначала проверить версию mailcow — для 2025-09 и новее нужен только ENABLE_IPV6=false, для более старых версий придётся использовать override.yml-схему и мигрировать на новый способ отдельно при следующем крупном обновлении. Затем убедиться, что на хосте есть jq, — без него новый IPv6-контроллер не сможет синхронизировать /etc/docker/daemon.json. После правки mailcow.conf — обязательно docker compose down и docker compose up -d, а не простой restart, потому что сеть контейнеров должна быть пересоздана заново.

Дальше — руками сверить /etc/docker/daemon.json на предмет забытых "ipv6": true или fixed-cidr-v6, если вы отключаете IPv6 полностью, и проверить версию самого Docker — 25.0.3 или новее, если раньше сталкивались с проблемами выделения адресов. И последним шагом — посмотреть docker compose logs netfilter-mailcow на предмет уже известного бага из issue #6700: если контейнер уходит в рестарт именно с ошибкой Address family not supported, это не ваша ошибка конфигурации, а открытый баг платформы, который стоит зафиксировать в тикете, а не пытаться вылечить локально бесконечными правками mailcow.conf.

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

На какой версии mailcow работает ENABLE_IPV6=false?

Начиная с релиза 2025-09 (10.09.2025) и новее. На более старых версиях штатного параметра нет, использовалась ручная правка docker-compose.override.yml с отключением контейнера ipv6nat-mailcow.

После ENABLE_IPV6=false нужно ли что-то делать, кроме перезапуска контейнеров?

Да, обязательно `docker compose down` и `docker compose up -d` — сеть должна пересоздаться. Также стоит проверить /etc/docker/daemon.json на предмет забытых ipv6/fixed-cidr-v6 настроек и убедиться, что на хосте установлен jq — он нужен новому IPv6-контроллеру.

Почему netfilter-mailcow падает с ошибкой Address family not supported даже после ENABLE_IPV6=false?

Это известный открытый баг, описанный в GitHub issue #6700: контейнер netfilter-mailcow всё ещё пытается инициализировать IPv6-таблицы на хосте, где IPv6 недоступен на уровне ядра (ipv6.disable=1). При отключении только через sysctl ошибка не возникает. Подтверждённого исправления от разработчиков на момент проверки нет, только предложенный автором issue патч.

Может ли неправильно отключённый IPv6 сделать mailcow открытым релеем?

Да, если настройки Docker daemon рассинхронизированы с ENABLE_IPV6: внешний IPv6-трафик может транслироваться в доверенную внутреннюю IPv4-подсеть docker-сети, а для Postfix эта подсеть по умолчанию доверенная для relay.

У меня старая инструкция с docker-compose.override.yml и ipv6nat-mailcow — можно оставить как есть?

На версиях mailcow до 2025-09 — да, но при обновлении до 2025-09 и новее лучше убрать этот override и перейти на ENABLE_IPV6=false: иначе рискуете нарваться на известный баг из issue #6295, когда update.sh стирает override.yml до невалидного YAML.

Нужен ли на хосте IPv6-адрес от провайдера, чтобы включить ENABLE_IPV6?

Да, без реальной поддержки IPv6 на хосте включать эту опцию нет смысла — она управляет только тем, использует ли mailcow IPv6 внутри своей docker-сети и NAT, а не выдаёт адрес сама по себе.

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

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

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

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

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

Источники

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