SSD Nodes Learn 8GB RAM — $66/год
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-08-01

wg-easy: WireGuard с веб-интерфейсом в Docker

Настройте wg-easy 15 через Docker Compose: порты, NET_ADMIN и sysctl, ошибка «Cannot find device wg0» и QR-коды для подключения телефонов.

Что вы создаете

wg-easy — это WireGuard с веб-интерфейсом, работающий в одном контейнере Docker. Он управляет интерфейсом WireGuard и предоставляет веб-интерфейс для создания клиентов. Для каждого созданного клиента формируются файл конфигурации и QR-код. Поэтому телефон подключается к VPN после наведения камеры на экран.

Сам туннель использует обычный WireGuard. Пакеты обрабатывает модуль ядра, поэтому пропускная способность такая же, как в конфигурации, созданной вручную. Вы получаете управление жизненным циклом клиентов: добавление, отключение и удаление узлов без редактирования файла конфигурации по SSH. При этом вы отказываетесь от прямого управления этим файлом. Это рассматривается в разделе настройка WireGuard вручную на VPS.

Вам потребуется KVM VPS с общедоступным IPv4-адресом, Docker Engine с Compose plugin и доступом root. Виртуализация контейнеров с общим ядром хоста, например OpenVZ или LXC, обычно не позволяет загрузить модуль WireGuard. Поэтому контейнер не сможет поднять интерфейс.

Версия 15 перенесла настройки из переменных среды

Большинство найденных руководств написано для wg-easy 14. В них WG_HOST задается как адрес сервера, а PASSWORD_HASH — как bcrypt-хеш пароля администратора. Оба значения передаются через переменные среды. Версия 15 полностью переработана. В официальных примечаниях по миграции прямо указано, что v15 не использует те же переменные среды, что и v14, а большинство настроек перенесено в панель администратора веб-интерфейса.

Поэтому WG_HOST и PASSWORD_HASH больше ни на что не влияют. Если скопировать старый compose-файл, контейнер запустится, проигнорирует эти строки, а затем предложит создать учетную запись администратора в браузере. Это не ошибка. Так теперь выполняется первоначальная настройка.

По состоянию на July 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::/64

etc_wireguard — это именованный том, в котором хранятся ключ сервера и все созданные вами клиенты. Создайте резервную копию этого тома. Иначе пересборка удалит всех ваших участников. Если вы хотите видеть эти файлы в файловой системе хоста, замените его на bind mount. Перед этим ознакомьтесь с материалом о различиях между bind mount и именованными томами, поскольку права доступа работают по-разному.

Почему требуются 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 помечает собственные исходящие пакеты, чтобы они не маршрутизировались обратно в туннель. Строгая фильтрация обратного пути видит пакет, исходный адрес которого не соответствует ожидаемому маршруту, и отбрасывает его. Этот параметр 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. В документации upstream указано, что запуск start для контейнера, созданного с другими настройками, приводит к некорректному состоянию сети. Если после перезагрузки вам нужно автоматически восстановить стек, restart: unless-stopped уже обеспечивает это. В разделе поведение сервисов compose при загрузке объясняется, что именно гарантирует эта политика, а что нет.

Веб-интерфейс прослушивает TCP-порт 51821. При первом посещении открывается страница настройки. На ней создайте учетную запись администратора и подтвердите адрес хоста, по которому клиенты будут подключаться к серверу. Этот адрес попадает в строку Endpoint каждого клиентского файла конфигурации. Поэтому укажите публичный IP-адрес или DNS-имя VPS. Если адрес указан неправильно, QR-код, который вы передадите телефону, будет вести к недоступному адресу, и рукопожатие не завершится.

Есть еще один важный момент, связанный с этим портом: wg-easy 15 отказывается использовать обычный HTTP, если не задан параметр INSECURE=true. Подключение по HTTPS с недоверенным сертификатом или завершение TLS на reverse proxy перед ним допустимы. Подключение по 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-коду и наведите камеру на код на экране. Туннель появится под заданным вами именем. Включите его. В строке клиента в веб-интерфейсе начнут отображаться счётчики переданных данных и время последнего рукопожатия.

Если после включения клиента рукопожатие не отображается, клиент вообще не достигает сервера. Причина связана с UDP 51820: либо порт блокирует межсетевой экран провайдера, либо проблема связана с адресом конечной точки, встроенным в конфигурацию. Если рукопожатие отображается, но интернет не работает, проверьте переадресацию трафика или DNS.

На компьютере скачайте файл .conf и импортируйте его в клиент WireGuard вместо ручного ввода параметров. Закрытый ключ в этом файле создаётся один раз и отображается один раз. Обращайтесь с этим файлом так же, как с закрытым ключом SSH.

Когда UI перестает подходить

wg-easy подходит, пока вашими пирами являются люди и телефоны. UI быстрее, чем редактирование конфигурационных файлов. Отозвать доступ у потерянного телефона можно одним нажатием.

Ограничения проявятся, когда потребуется функция, которую UI не поддерживает. Маршрутизация между сайтами, при которой AllowedIPs пира охватывает целую удаленную подсеть, а не один адрес, обычно становится первым таким ограничением. Следующий уровень — split tunnel с правилами маршрутизации для отдельных пиров или конфигурация, созданная инструментом подготовки. На этом этапе ручная настройка не сложнее. Она просто выполняется другим способом. В руководстве по обычной настройке WireGuard показано, как создать тот же туннель с помощью wg0.conf. Если вы хотите полностью отказаться от запуска плоскости управления, в материале WireGuard и Tailscale: сравнение рассмотрен управляемый вариант.

Если незнакомой частью была синтаксическая запись 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 в любом случае остается обязательным.

Клиент подключается, но интернета нет. В чем проблема?

Установленное рукопожатие без трафика почти всегда указывает на проблему с переадресацией. Убедитесь, что net.ipv4.ip_forward=1 и net.ipv4.conf.all.src_valid_mark=1 по-прежнему присутствуют в compose-файле. При ручном редактировании копии их часто удаляют. Если переадресация включена, проверьте DNS-сервер, который получил клиент. Если туннель направляет весь трафик через VPN, но указывает DNS-сервер, который больше недоступен через этот туннель, в браузере это выглядит как полностью неработающее подключение.

Как создать резервную копию клиентов?

Все данные находятся в именованном томе etc_wireguard, в файле wg0.json. В веб-интерфейсе также есть кнопка резервного копирования, которая экспортирует те же данные. Перед любым обновлением скопируйте этот файл за пределы сервера. Восстановление выполняется загрузкой файла на этапе настройки нового контейнера.

Можно ли запустить wg-easy за обратным прокси?

Да. Разместите прокси перед TCP 51821, завершайте TLS на прокси и задайте INSECURE=true для контейнера, чтобы он принимал обычное HTTP-подключение от прокси. UDP 51820 оставьте опубликованным напрямую, поскольку VPN-трафик использует UDP и не проходит через HTTP-прокси.