Как запустить wg-easy в Docker: настройка WireGuard
Разверните WireGuard с веб-интерфейсом через Docker Compose. Узнайте, как правильно настроить параметры NET_ADMIN, sysctls и порты для корректной работы VPN на вашем сервере.
Что вы создаете
wg-easy — это WireGuard с веб-интерфейсом, работающий в одном Docker контейнере. Он управляет интерфейсом WireGuard и предоставляет браузерный UI для создания клиентов. Для каждого созданного клиента генерируется файл конфигурации и QR-код, поэтому телефон подключается к VPN после сканирования кода камерой.
Сам туннель является стандартным WireGuard. Пакеты обрабатываются модулем ядра, поэтому пропускная способность не отличается от ручной настройки. Вы получаете удобный жизненный цикл клиентов: добавление, отключение и удаление пиров без редактирования конфигурационных файлов по SSH. Вы теряете прямой контроль над конфигурацией, что подробно описано в ручной настройке WireGuard на VPS.
Вам потребуется KVM VPS с публичным IPv4-адресом, Docker Engine с плагином Compose и права root. Контейнерная виртуализация, использующая общее ядро хоста, например OpenVZ или LXC, обычно не позволяет загрузить модуль WireGuard, из-за чего контейнер не сможет поднять интерфейс.
В версии 15 настройки вынесены из переменных окружения
Большинство руководств, которые вы найдете, написаны для wg-easy 14, где WG_HOST задавался как адрес сервера, а PASSWORD_HASH — как bcrypt-хеш пароля администратора, причем оба параметра передавались через переменные окружения. Версия 15 представляет собой переписанный продукт. В официальных примечаниях к миграции прямо указано, что v15 не использует те же переменные окружения, что и v14, и что большинство из них перенесено в панель администратора в веб-интерфейсе.
Таким образом, WG_HOST и PASSWORD_HASH больше не выполняют никаких функций. Если вы скопируете старый файл compose, контейнер запустится, проигнорирует эти строки, а затем предложит создать учетную запись администратора в браузере. Это не ошибка. Таков новый процесс настройки.
По состоянию на июль 2026 года основным тегом для фиксации версии является 15. Фиксируйте мажорную версию вместо использования latest, так как мажорное обновление изменяет формат конфигурации на диске и не предполагает корректного отката.
Файл compose
Создайте каталог для стека и запишите в него официальный файл compose. Это исходный файл от разработчика, он не требует изменений.
sudo mkdir -p /etc/docker/containers/wg-easy
sudo curl -o /etc/docker/containers/wg-easy/docker-compose.yml \
https://raw.githubusercontent.com/wg-easy/wg-easy/master/docker-compose.ymlСодержимое выглядит следующим образом:
volumes:
etc_wireguard:
services:
wg-easy:
image: ghcr.io/wg-easy/wg-easy:15
container_name: wg-easy
networks:
wg:
ipv4_address: 10.42.42.42
ipv6_address: fdcc:ad94:bacf:61a3::2a
volumes:
- etc_wireguard:/etc/wireguard
- /lib/modules:/lib/modules:ro
ports:
- "51820:51820/udp"
- "51821:51821/tcp"
restart: unless-stopped
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv6.conf.all.disable_ipv6=0
- net.ipv6.conf.all.forwarding=1
- net.ipv6.conf.default.forwarding=1
networks:
wg:
driver: bridge
enable_ipv6: true
ipam:
driver: default
config:
- subnet: 10.42.42.0/24
- subnet: fdcc:ad94:bacf:61a3::/64etc_wireguard — это именованный том, в котором хранятся ключ сервера и все созданные вами клиенты. Создавайте резервные копии этого тома, иначе при пересборке все пиры будут удалены. Если вы предпочитаете видеть эти файлы в файловой системе хоста, замените его на bind mount. Перед этим ознакомьтесь с разницей между bind mounts и именованными томами, так как права доступа в них работают по-разному.
Зачем нужны NET_ADMIN, SYS_MODULE и sysctl
По умолчанию контейнеру запрещено взаимодействовать со стеком сети, и каждая из этих строк снимает конкретное ограничение.
NET_ADMIN позволяет контейнеру создавать интерфейс wg0, назначать ему адрес и прописывать маршруты. Без этого параметра контейнер запускается, но завершается при попытке поднять интерфейс, так как ip link add wg0 type wireguard возвращает Operation not permitted.
SYS_MODULE в сочетании с монтированием /lib/modules в режиме только для чтения позволяет контейнеру загрузить модуль ядра WireGuard, если он ещё не загружен на хосте. Модуль находится в ядре хоста, а не внутри образа, поэтому директория хоста должна быть доступна. В современных ядрах модуль обычно встроен, что можно проверить командой sudo modprobe wireguard && echo ok на хосте.
net.ipv4.ip_forward=1 заставляет ядро пересылать пакеты, которые не адресованы самому устройству. Без этого параметра клиент подключается, рукопожатие проходит успешно, но все пакеты в интернет отбрасываются, из-за чего ping 1.1.1.1 выдает тайм-аут, хотя VPN выглядит подключенным.
net.ipv4.conf.all.src_valid_mark=1 — это параметр, который часто вызывает вопросы. WireGuard помечает свои исходящие пакеты, чтобы они не направлялись обратно в туннель. Строгая фильтрация обратного пути (reverse path filtering) обнаруживает пакет, чей адрес источника не совпадает с ожидаемым маршрутом, и отбрасывает его. Этот sysctl указывает ядру принимать помеченные пакеты, что предотвращает самоблокировку полноценного туннеля.
Запуск и создание учетной записи администратора
cd /etc/docker/containers/wg-easy
sudo docker compose up -d
sudo docker compose logs -fИспользуйте docker compose up и docker compose down, а не start и stop. Разработчики предупреждают, что start для контейнера, созданного с другими параметрами, приводит к несогласованному состоянию сети. Если вы хотите, чтобы стек автоматически запускался после перезагрузки, restart: unless-stopped уже обеспечивает это, а в поведении сервисов compose при загрузке разъясняется, что именно гарантирует эта политика.
Веб-интерфейс ожидает подключений на порту TCP 51821. При первом посещении отображается страница настройки, где необходимо создать учетную запись администратора и подтвердить адрес хоста, который клиенты будут использовать для связи с сервером. Этот адрес хоста попадает в строку Endpoint каждой клиентской конфигурации, поэтому он должен соответствовать публичному IP-адресу или DNS-имени вашего VPS. Если адрес указан неверно, QR-код, который вы передадите на мобильное устройство, будет указывать на недоступный узел, и «рукопожатие» (handshake) не завершится.
Еще один важный момент относительно этого порта: wg-easy 15 отклоняет обычные HTTP-запросы, если не задана переменная INSECURE=true. Доступ по HTTPS с использованием недоверенного сертификата или терминация TLS на обратном прокси-сервере перед приложением являются допустимыми вариантами. Доступ по http:// с настройками по умолчанию невозможен.
Не открывайте порт веб-интерфейса в интернет
Файл compose публикует порт 51821 на всех интерфейсах. Это страница входа в систему, которая может маршрутизировать ваш трафик, поэтому она не должна быть доступна извне. Публикация порта в Docker добавляет правила в цепочку DOCKER, которая обрабатывается раньше, чем ufw, поэтому правило запрета в ufw не закроет этот порт. Эту особенность важно понимать, и в статье почему опубликованные Docker порты игнорируют ufw она разобрана полностью.
Простое решение — привязать веб-интерфейс к интерфейсу loopback и подключаться к нему через SSH-туннель:
ports:
- "51820:51820/udp"
- "127.0.0.1:51821:51821/tcp"
environment:
- INSECURE=trueЗатем с вашего ноутбука выполните:
ssh -L 51821:127.0.0.1:51821 youruser@your.server.addressОткройте http://127.0.0.1:51821 в браузере на вашем ноутбуке. Трафик шифруется через SSH, порт не отвечает никому другому, а INSECURE=true здесь безопасен, так как передача по обычному HTTP не покидает интерфейс loopback.
Откройте UDP 51820 и проверьте оба межсетевых экрана
Для работы WireGuard необходимо, чтобы порт UDP 51820 был доступен из интернета. Docker публикует его, но многие провайдеры используют внешний сетевой экран перед VPS, о котором Docker не знает. Откройте порт в обоих местах. Если вы управляете межсетевым экраном хоста с помощью ufw, базовые правила ufw для VPS будут более простым решением, чем ручная настройка nftables.
Убедитесь, что контейнер действительно ожидает соединений:
sudo ss -ulnp | grep 51820Вы должны увидеть слушающий UDP-сокет. Если в этой строке ничего нет, значит, контейнер не смог поднять интерфейс, а sudo docker compose logs wg-easy укажет причину.
Создание клиента и сканирование на телефоне
В интерфейсе создайте клиента и присвойте ему имя, по которому вы сможете его опознать, например, название устройства. wg-easy выделит следующий свободный адрес в туннеле и сгенерирует пару ключей. В строке каждого клиента доступен QR-код и файл .conf для скачивания.
Установите официальное приложение WireGuard на телефон, выберите добавление туннеля через QR-код и наведите камеру на код на экране. Туннель появится под заданным вами именем. Включите его, и в строке клиента в интерфейсе начнут отображаться счетчики передачи данных и время последнего рукопожатия (handshake). Как только телефон подключен к туннелю, он получает доступ к сервисам, которые вы не публиковали в Интернете. Так телефон может загружать данные на собственный фотосервер из любого места, при этом сервер не будет иметь ни одного открытого вовне порта. Тот же метод работает для медиаконтента: библиотека Jellyfin, стилизованная под видеопрокат 90-х, — приятный ресурс для просмотра из номера отеля, остающийся таким же приватным, как и в вашей локальной сети. Оповещения работают в обратном направлении через тот же туннель: собственный сервер ntfy может отправить сообщение на телефон в момент сбоя резервного копирования, не отвечая на запросы из публичного Интернета.
Если после включения клиента рукопожатие не происходит, значит, он не может связаться с сервером. Проблема кроется в UDP 51820: либо в файрволе провайдера, либо в адресе конечной точки, указанном в конфигурации. Если рукопожатие есть, но интернет не работает, проблема в пересылке пакетов (forwarding) или в DNS.
На компьютере скачайте файл .conf и импортируйте его в клиент WireGuard, вместо того чтобы вводить данные вручную. Приватный ключ в этом файле генерируется и отображается только один раз. Обращайтесь с этим файлом так же, как с приватным ключом SSH.
Когда пора отказываться от графического интерфейса
wg-easy — подходящий инструмент, пока вашими пирами являются люди и их телефоны. Интерфейс работает быстрее, чем редактирование конфигурационных файлов, а отзыв доступа для потерянного телефона выполняется в один клик.
Вы столкнетесь с ограничениями, когда вам потребуется функциональность, не предусмотренная интерфейсом. Маршрутизация между сайтами (site-to-site), где AllowedIPs пира охватывает целую удаленную подсеть, а не один адрес, обычно становится первым препятствием. Раздельное туннелирование (split tunnels) с правилами маршрутизации для каждого пира или конфигурация, созданная вашим инструментом автоматизации, — следующие шаги. На этом этапе настройка вручную не становится сложнее, она просто меняется, и базовое руководство по WireGuard показывает тот же туннель, построенный с помощью wg0.conf. Если вы хотите полностью отказаться от управления плоскостью контроля, сравнение WireGuard и Tailscale описывает управляемый вариант. Является ли это выгодным обменом, зависит от того, к чему именно имеет доступ координационный сервер, поэтому стоит изучить модель доверия Tailscale, прежде чем доверять ему свою сеть. Стоимость обычно является следующим вопросом, и того, что включено в бесплатный тариф Tailscale, достаточно, чтобы домохозяйство или небольшая команда не платили за него ничего. После этого порога оплата рассчитывается по количеству пользователей, а не устройств, что отличается от модели оплаты VPS, за которую вы уже платите, поэтому стоимость Tailscale после выхода за рамки бесплатного тарифа — это цифра, которую нужно проверить перед миграцией команды. Полноценный туннель, который вы только что создали, имеет там прямой аналог, поскольку объявление VPS в качестве exit node в Tailscale дает вам тот же маршрут выхода через сервер, который одобряется в консоли администратора, а не прописывается в конфигурации каждого клиента. Ограничение подсетей также имеет решение, так как объявление целой частной сети с VPS делает эту сеть доступной для каждого устройства в tailnet без редактирования AllowedIPs для каждого пира, что и заставило вас отказаться от графического интерфейса. Если вам нужна панель управления и автоматическая mesh-маршрутизация, но вы не хотите использовать чужой координационный сервер, запуск собственного сервера NetBird на VPS позволит сохранить контроль над плоскостью управления на вашем оборудовании, ценой настройки DNS и TLS, которые не требовались в wg-easy.
Если синтаксис compose, приведенный выше, оказался для вас незнакомым, а не сам WireGuard, основы Docker Compose на VPS объясняют формат файла и повседневные команды.
FAQ
Почему wg-easy игнорирует мои переменные WG_HOST и PASSWORD_HASH?
Эти переменные относились к wg-easy версии 14. Версия 15 была полностью переписана, и разработчики перенесли почти всю конфигурацию в панель администратора в веб-интерфейсе. Контейнер игнорирует эти переменные, поэтому он запускается в стандартном режиме и при первом посещении предлагает создать учётную запись администратора. Укажите адрес хоста для клиентов на этой странице настройки.
Нужен ли мне SYS_MODULE, если в ядре уже есть WireGuard?
Нет. SYS_MODULE и монтирование /lib/modules нужны для того, чтобы контейнер мог загрузить модуль, если он отсутствует на хосте. На хосте, где команда sudo modprobe wireguard выполняется успешно, эта возможность не используется. Удаление этой привилегии — разумный шаг для повышения безопасности, при этом NET_ADMIN всё равно требуется в любом случае.
Клиент подключается, но интернета нет. В чём проблема?
Отсутствие трафика при успешном рукопожатии почти всегда означает проблемы с пересылкой пакетов (forwarding). Убедитесь, что net.ipv4.ip_forward=1 и net.ipv4.conf.all.src_valid_mark=1 присутствуют в файле compose, так как при ручном редактировании они часто теряются. Если пересылка включена, проверьте DNS-сервер, который получил клиент. Туннель, который направляет весь трафик через VPN, но указывает на недоступный DNS-сервер, в браузере выглядит как неработающее соединение.
Как создать резервную копию клиентов?
Все данные хранятся в именованном томе etc_wireguard, в файле wg0.json. В интерфейсе также есть кнопка резервного копирования, которая экспортирует те же данные. Скопируйте этот файл за пределы сервера перед любым обновлением. Восстановление выполняется путём загрузки файла на этапе настройки нового контейнера.
Можно ли запустить wg-easy за reverse proxy?
Да. Разместите прокси перед TCP-портом 51821, настройте там TLS termination и установите INSECURE=true для контейнера, чтобы он принимал обычные HTTP-запросы от прокси. UDP-порт 51820 оставьте опубликованным напрямую, так как VPN-трафик использует протокол UDP и не проходит через HTTP-прокси.