Да, я знаю. «Опять статья про WireGuard». Но это не очередное «how-to» на 5 строк. Это — история боли, паранойи и, наконец, автоматизации. Это история о том, как мы перестали «обслуживать» VPN и заставили его работать на нас.

Привет, Хабр! Меня зовут... впрочем, неважно. Важно то, что я 10 лет занимаюсь тем, что «делаю, чтобы работало». И последние лет 8 из них я, как и вы, страдал.

Мое утро начиналось не с кофе, а с тикета в Jira:

  • «У меня отвалился VPN, не могу подключиться» (протух сертификат)

  • «Почему так медленно открывается...» (привет, TCP-over-TCP)

  • «Я не могу выпустить сертификат для нового сотрудника» (опять сломали easy-rsa)

Мы все через это проходили. OpenVPN — это как старый-добрый ВАЗ-2106. Он едет, он легенда, и у каждого админа в гараже есть ящик запчастей для него (PKI, скрипты отзыва, кастомные push route). Но в 2025 году ездить на этом каждый день — больно.

В какой-то момент мы просто сели и посчитали, сколько человеко-часов уходит на обслуживание VPN-инфраструктуры. И прослезились. Пришло время перемен.

Наша "Боль", или "Классика", которая устала

Проблема была не в том, что OpenVPN плохой. Он просто... сложный. И громоздкий.

  1. Управление PKI (Инфраструктура открытых ключей): Это главный монстр. Управление центром сертификации (CA), выпуск ключей для каждого пользователя, отзыв скомпрометированных ключей (CRL), отслеживание сроков действия... Это отдельная, полноценная работа.

  2. Производительность: OpenVPN работает в userspace. Это оверхед. На шифрование тратится много CPU. При плохом канале (особенно мобильном) постоянные переподключения превращали жизнь «удаленщиков» в ад.

  3. Кодовая база: Сотни тысяч строк кода. С точки зрения «здоровой паранойи» (а я фанат ИБ), это гигантская поверхность для атаки. Чем больше кода — тем больше багов.

  4. Конфигурация: Гибкость OpenVPN — это и его проклятие. Файл конфига на 200 строк с push, pull, tls-auth и кастомными скриптами — это нормально. Но поддерживать это? Увольте.

Поиски "Серебряной пули"

Мы, инженеры, ленивы (в хорошем смысле). Нам нужен инструмент, который «один раз настроил и забыл».

  • Вариант А: Остаться на OpenVPN, но автоматизировать?

    • Почему нет: Это как пытаться прикрутить турбонаддув и ABS к тому самому ВАЗ-2106. Можно, но зачем? Мы автоматизируем не инструмент, а решение проблемы. Проблема сложности PKI никуда не денется.

  • Вариант Б: IPsec (StrongSwan/Libreswan)

    • Почему нет: Кто хоть раз дебажил IPsec «в поле», тот в цирке не смеется. Конфиги еще более зубодробительные, а несовместимость реализаций между вендорами — классика жанра.

  • Вариант В: Коммерческие "Zero Trust" решения (Tailscale, Zscaler, etc.)

    • Почему нет: Tailscale — прекрасен. Но он построен на... WireGuard. И он требует доверия к стороннему «control plane». Как админ-параноик, я хочу, чтобы ключи от моего «прода» лежали у меня в сейфе, а не в облаке у «дяди». Мы хотим свой VPN, полностью подконтрольный.

  • Вариант Г: WireGuard

    • Что это? Современный, быстрый, минималистичный VPN.

    • Ключевая фишка 1: Он работает в ядре Linux. Это делает его дьявольски быстрым.

    • Ключевая фишка 2: Никакого PKI. Вообще. Вся аутентификация построена на обмене простыми публичными ключами (как в SSH).

    • Ключевая фишка 3 (Паранойя ликует): Кодовая база — всего несколько тысяч строк. Аудировать ее можно «за выходные». Атаковать тут почти нечего.

Решено. Берем.

Решение "из окопов": Наш бастион на WireGuard

Наша задача: поднять центральный VPN-сервер (бастион), через который админы и DevOps-инженеры получают доступ во внутреннюю сеть (скажем, 10.10.0.0/16), где крутятся наши GitLab, Jenkins и тестовые стенды.

Disclaimer: Я фанат Arch... шучу. Берем обычную Ubuntu 22.04 LTS / Debian 12.

Шаг 1: Установка и генерация ключей (Сервер)

WireGuard уже есть в ядре, нужны только утилиты.

Bash

# На сервере
sudo apt update
sudo apt install wireguard -y

# Идем в папку конфигов
cd /etc/wireguard/

# Генерируем ПАРУ ключей: приватный и публичный
wg genkey | tee server_private.key | wg pubkey > server_public.key

# Генерируем ключи для нашего первого клиента (например, моего ноута)
wg genkey | tee client_maksim_private.key | wg pubkey > client_maksim_public.key

Шаг 2: Конфиг сервера (/etc/wireguard/wg0.conf)

Это вся конфигурация сервера. ВСЯ.

Ini, TOML

[Interface]
# Приватный IP самого VPN-сервера
Address = 10.200.0.1/24
# Порт, который мы слушаем (UDP)
ListenPort = 51820
# Наш приватный ключ
PrivateKey = <содержимое файла server_private.key>

# Магия, которая "выпускает" клиентов в нашу сеть
# Включаем NAT при старте интерфейса
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# Выключаем NAT при остановке
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

# --- ПЕРВЫЙ КЛИЕНТ ---
[Peer]
# Публичный ключ клиента
PublicKey = <содержимое файла client_maksim_public.key>
# IP, который мы ему выдаем в VPN-сети
AllowedIPs = 10.200.0.10/32

Шаг 3: Включаем Сервер

Во-первых, разрешим ядру форвардить пакеты. Это самые частые грабли.

Bash

# Редактируем /etc/sysctl.conf
sudo nano /etc/sysctl.conf

Нам нужна строка (раскомментируйте или добавьте): net.ipv4.ip_forward=1

Применяем: sudo sysctl -p

Теперь запускаем наш VPN-интерфейс:

Bash

# Запускаем и добавляем в автозагрузку
sudo systemctl enable --now wg-quick@wg0

Проверяем: sudo wg show

Шаг 4: Конфиг клиента (Ноутбук админа)

Создаем файл client_maksim.conf на своем ноутбуке (или телефоне).

Ini, TOML

[Interface]
# IP, который нам "выдал" сервер
Address = 10.200.0.10/24
# Приватный ключ клиента
PrivateKey = <содержимое файла client_maksim_private.key>
# DNS (важно, чтобы работали внутренние имена)
DNS = 10.10.1.1 

[Peer]
# Публичный ключ СЕРВЕРА
PublicKey = <содержимое файла server_public.key>
# Внешний IP:Порт нашего сервера
Endpoint = 95.217.XXX.XXX:51820

# САМАЯ ВАЖНАЯ СТРОКА
# Что мы заворачиваем в туннель?
# Вариант "Параноик" (весь трафик):
AllowedIPs = 0.0.0.0/0, ::/0
# Вариант "Split-tunnel" (только наша сеть):
# AllowedIPs = 10.10.0.0/16, 10.200.0.1/24

Я, как параноик, предпочитаю 0.0.0.0/0. Так я уверен, что мой ноутбук из кофейни не светит в локальную сеть WiFi, а весь трафик (включая DNS-запросы) идет через наш защищенный бастион.

Клиент (в Windows/macOS/Linux) просто импортирует этот *.conf файл и нажимает "Connect". Все. Подключение — мгновенное.

Наши "Грабли" (Что пошло не так)

Звучит слишком просто? Так и есть. Но дьявол, как всегда, в деталях.

Грабли №1: "Не пингуется!"

  • Симптом: Клиент подключился (handshake есть), но не пингует даже сервер (10.200.0.1).

  • Лечение: 99% случаев — файрвол. Вы забыли открыть UDP-порт (в нашем примере 51820) на входе на сервере. sudo ufw allow 51820/udp

Грабли №2: "Сервер пингуется, а 'внутрянка' (10.10.0.0/16) — нет"

  • Симптом: 10.200.0.1 пингуется, а 10.10.5.12 (Jenkins) — нет.

  • Лечение: Две причины:

    1. Вы забыли net.ipv4.ip_forward=1 и sudo sysctl -p.

    2. Ваши iptables правила для NAT (в PostUp) написаны с ошибкой или не для того интерфейса (eth0 в моем примере, у вас может быть ens3 или другой).

Грабли №3: "Все работает, но отваливается за NAT"

  • Симптом: Клиент (особенно мобильный) в сети с агрессивным NAT (некоторые отели, мобильные операторы) теряет соединение через пару минут простоя.

  • Лечение: Добавляем в клиентский конфиг ([Peer]) одну строку: PersistentKeepalive = 25 Это заставит клиента раз в 25 секунд слать "heartbeat", поддерживая NAT-трансляцию живой.

Грабли №4 (Самые главные): "Ручное управление ключами — это тот же PKI!"

  • Симптом: Мы это осознали, когда у нас стало 30+ инженеров. Добавлять каждого вручную (wg genkey, копировать public, вставлять в wg0.conf, перезапускать...) — это больно. Мы вернулись к тому, с чего начали.

  • Лечение (DevOps-way): Автоматизация. Мы не стали ставить Web-морды (хотя они есть, типа wg-easy), потому что "мы же админы". Мы завели репозиторий в GitLab.

    1. Конфиг /etc/wireguard/wg0.conf превратился в Ansible-шаблон (wg0.conf.j2).

    2. Информация о "пирах" (клиентах) хранится в group_vars в виде YAML-словаря:

      YAML

      wireguard_peers:
        - name: "maksim_laptop"
          public_key: "..."
          ip: "10.200.0.10/32"
        - name: "anna_phone"
          public_key: "..."
          ip: "10.200.0.11/32"
    3. Ansible-шаблон просто пробегается по этому списку циклом:

      Django

      ...
      # --- ПИРЫ ---
      {% for peer in wireguard_peers %}
      [Peer]
      # {{ peer.name }}
      PublicKey = {{ peer.public_key }}
      AllowedIPs = {{ peer.ip }}
      {% endfor %}
    4. Процесс для нового сотрудника:

      • Он сам генерирует у себя пару ключей.

      • Создает Merge Request в наш репозиторий, добавляя свой public_key и имя в YAML.

      • Старший инженер ревьюит MR, жмет "Merge".

      • CI/CD-пайплайн запускает Ansible-playbook, который раскатывает новый конфиг на VPN-сервер и бесшовно перезагружает его (wg syncconf wg0).

ВСЁ. Мы получили GitOps-управляемый VPN. Админы больше не занимаются ключами, они просто аппрувят MR.

Заключение: Что мы получили?

  1. Скорость: Подключение к VPN занимает 0.1 секунды. Не 10-30 секунд, как с OpenVPN.

  2. Надежность: Связь не рвется при смене сети (с Wi-Fi на LTE).

  3. Безопасность: Минимальная кодовая база и простая криптография.

  4. Прозрачность: Вся конфигурация VPN-клиентов лежит в Git. Мы видим, кто и когда получил доступ.

  5. Свободное время: Самое главное. У нас больше нет тикетов «VPN не работает». Мы занимаемся делом, а не «обслуживанием».

Так что да, если вы еще сидите на OpenVPN и страдаете — хватит это терпеть. Путь займет один вечер, а сэкономит сотни часов в будущем.

Комментарии (15)


  1. gotch
    05.11.2025 07:16

    Зачем клиентам NAT?


    1. xitriy87
      05.11.2025 07:16

      Наверное потому что у них сервер не на шлюзе и про маршруты до vpn сети никто не знает.


    1. AdminFuture Автор
      05.11.2025 07:16

      Классический MASQUERADE, чтобы не бегать к сетевикам. 'Прод' видит трафик от самого WG-сервера и ему же отвечает.


      1. gotch
        05.11.2025 07:16

        А потом на серверах авторизации (контроллерах домена) неуспешные попытки аутентификации с ip WG, а не конкретных клиентов. Не комильфо.


  1. vesper-bot
    05.11.2025 07:16

    Хмм. Про грабли:

    1. Управление CA - хотя и неприятно, но можно сделать на вменяемом уровне. То же создание CRL и подсовывание его серверу вполне себе автоматизируется на уровне крона. Обновление сертификатов - удобство vs безопасность, либо у тебя потерянные SSH-ключи на wg, либо забытые сертификаты на easyrsa, что технически одно и то же. Но если серт рано или поздно истечёт, то ключ - нет.

    2. Userspace vs kernel space - грабли валидные, может наткнёмся со временем, но оверхэд там для нас не критичен настолько, чтобы решало. (Вот будь у нас 40Гбит/с снаружи, могло бы влиять по идее)

    3. Спорные грабли. Да, wireguard меньше в виде кода, но статические (и динамические) анализаторы решают проблему аудита в поисках багов довольно эффективно. Ну и никто не застрахован от бага в либах, который можно проэксплойтить даже через идеальный код использующего приложения.

    4. Насколько у вас сложные правила для OpenVPN, что вам надо поддерживать кастомные скрипты OpenVPN на стороне клиентов? А также, насколько часто у вас меняется конфигурация локальной сети "за" сервером OpenVPN, что надо лазать в конфиг и править там push route или ещё какие-то параметры? Один раз заточил, и оно "просто" работает. Про частые проблемы:

    5. "Не могу зайти в VPN" - ну, штатная ситуация, можно лечить скриптами автоматического перевыпуска сертификатов, условно раз в месяц, и превентивно рассылать новые конфиги. По сравнению со статическими SSH-ключами Wireguard - удобство vs безопасность, плюс защита от забывчивости в случае увольнения кого-то, если сразу не сделали отзыв. По мне, так или иначе доступ к VPN админить надо, ваше решение не хуже многих.

    6. OpenVPN прекрасно умеет работать по UDP - что мешает/мешало перенастроить сервер (поднять второй инстанс, начать перевозить клиентов по инструкции, ещё как) на использование UDP в качестве транспортного протокола? Проблема разрыва соединений и переподключения в мобильном роуминге решается параметром auth-gen-token (время в секундах) в конфиге сервера или CCD клиента, если нельзя на весь сервер включить.

    7. А что это у вас кто попало лазает в easyrsa кривыми руками? Он, кстати, по логике должен жить отдельно от OpenVPN, в идеале на нормально выключенной ВМ, и бэкапаться регулярно, как центр доверия системы VPN. И при нормальной безопасности разрешение на вход по VPN должно выдаваться администраторами, кривых рук там быть не должно (как есть - смотрите сами, кто там вам ломал ЦС, какими действиями, почему и зачем, и насколько сильно его за это бить - тоже). Про альтернативы:

    8. Какая такая проблема сложности PKI? Сложно создать CRL и добросить его до сервера? Сложно мониторить истечение сертификатов? (openssl x509 -enddate) Сложно держать его несломанным? Грубо говоря, семь бед - один backup.

    9. "Голый" IPsec?! Он хорош для перманентных подключений через белые IP, и неизменяемых настройках пробрасываемых сетей. Применять его для подключения бегающих клиентов как минимум нелогично. L2TP/IPsec - вариант, но наши "доблестные" РКП его режут нещадно. Да, не вариант, но то, что он вообще тут появился, не подходит под описываемый сценарий использования VPN. 3, 4 - да, инфраструктура VPN должна быть "своей" вместе с центром доверия - но задачи управления им с вас не снимаются.

    Проблемы настройки WG vs OpenVPN:

    1. Firewall - смешно. Вы же проверяете подключение до того, как выставить сервер в прод, да? (ДА?!)

    2. IPv4 forwarding - вообще аксиома, VPN-сервер, любой, является роутером и форвардить IPv4 обязан. (IPv6 не забудьте, кстати, если настраивали, он отдельно включается)

    3. Ну, keepalive есть и в OpenVPN, но да, нужен.

    4. Добавление - ну ОК, а удаление?

    Итого: Можно было обойтись перенастройкой OpenVPN с тем же эффектом (ну, минус оверхэд, от него не уйти).


    1. AdminFuture Автор
      05.11.2025 07:16

      О, вот это я понимаю — комментарий "по существу". Спасибо, коллега vesper-bot, приятно видеть такой детальный разбор!

      Давайте пройдемся по пунктам. Вы абсолютно правы во многих технических деталях, но, кажется, мы смотрим на них с разных "колоколен". Я в статье описывал переход не с идеально настроенного OpenVPN, а с типичного, исторически сложившегося.

      1. Про PKI, ключи и "обвязку"

      "Управление CA - хотя и неприятно, но можно сделать на вменяемом уровне... CRL... Обновление сертификатов..." "Сложно создать CRL? Сложно мониторить истечение?"

      Нет, не сложно. Но всё, что вы перечислили — cron для CRL, отдельная (в идеале offline) ВМ для CA, скрипты для мониторинга openssl x509 -enddate — это и есть та самая "обвязка". Это еще одна система, которую нужно поддерживать, бэкапить и мониторить.

      "если серт рано или поздно истечёт, то ключ [WG] - нет."

      Это самый сильный ваш аргумент, и это 100% правда. Это известный трейд-офф. Но мы решили эту проблему "административно" через GitOps:

      • Выдача ключа: Новый сотрудник создает MR, добавляя свой публичный ключ в YAML-файл.

      • Отзыв ключа (удаление): Увольнение? git rm строки из vars.yml -> MR -> раскатка Ansible. Всё. Наш Git-репозиторий — это и есть наш "центр доверия" и "журнал аудита". Он проще, чем PKI.

      2. Про "Руки", UDP и auth-gen-token

      "А что это у вас кто попало лазает в easyrsa кривыми руками?"

      Золотые слова! Это была именно организационная боль. easyrsa валялся на самом VPN-сервере, и в него ходили несколько админов. Да, это "неправильно". Но это была реальность. WG, с его простой моделью "ключ на клиенте, public-key на сервере", просто исключает эту проблему как класс.

      "OpenVPN прекрасно умеет работать по UDP... что мешало перенастроить?" "Проблема разрыва... решается параметром auth-gen-token"

      Ничего не мешало. Вы абсолютно правы. Можно было перенастроить OVPN на UDP. Можно было вкрутить auth-gen-token. Можно было вынести CA на отдельную ВМ и автоматизировать CRL.

      Но в сумме это всё — «прикручивание фич» к старому комбайну. Мы просто решили, что взять новый, более простой инструмент (где UDP и "бесшовность" — это база, а не опция) и построить вокруг него прозрачный GitOps-процесс — в 2025 году уже проще, дешевле и надежнее.

      3. Про "смешные" грабли

      "Firewall - смешно. Вы же проверяете... да?" "IPv4 forwarding - вообще аксиома..."

      Конечно! =) Вы правы, для опытного инженера это аксиомы. Но статья рассчитана и на "начинающих сисадминов". И эти два пункта — 90% всех проблем "почему мой WG не работает?" на StackOverflow. Я счел своим долгом включить их, чтобы сэкономить кому-то пару часов дебага.

      Итого:

      Вы абсолютно правы: можно было обойтись перенастройкой OpenVPN. Но статья не о том, что OVPN нельзя заставить работать хорошо. Она о том, что цена поддержки "хорошо настроенного OVPN" (со всей его PKI-обвязкой) оказалась для нас выше, чем цена поддержки "хорошо настроенного WireGuard" (который, по сути, один conf + один vars.yml в Git).

      Спасибо за то, что заставили копнуть глубже!


  1. kolabaister
    05.11.2025 07:16

    OpenVPN пока не блокируется у нас - а вот WG уже частенько. Не опасаетесь остаться без рабочей сети?


    1. darkgerion
      05.11.2025 07:16

      К слову, если использовать tcp, то в опенвпн есть —port-share args. Возможно даже работает, но это не точно.


  1. lazyest
    05.11.2025 07:16

    для tailscale есть опенсорсная замена контрол плейну, headscale


  1. darkgerion
    05.11.2025 07:16

    1) Года 4 назад отказался от easy-rsa и накрутил в ансибле нужные рычаги openssl и ямлы.
    2) CRL.pem выкинул и использую crl-verify dir, куда кладется файл с именем равным серийнику серта. Дало возможность без особых танцев проводить временную блокировку подключения.
    3) Подкрутил на сервере txqueuelen (в дефолте он сотка) и sndbuf/rcvbuf.
    4) Для пушей использую client-config-dir - каждому пользователю улетает только то, что надо именно ему.
    5) Поигрался с cipher - оставил только AEAD алгоритмы.
    6) Отказался от rsa в пользу ec. Соответственно dh заменил на ecdh. Да, серты пришлось постепенно перевыдавать.

    Возможно дойдут руки поиграться с их Data Channel Offload, который выносит крипту из юзерспейса, но пока производительность устраивает и так.


    1. vesper-bot
      05.11.2025 07:16

      1 - получается, переизобрели easyrsa, но на другой кодовой базе.
      2 - интересная идея, при работе со стандартным ЦС такой опции не предусмотрено.
      3 - а на сколько, и почему?
      4 - вот это поддерживать надо, и зависит от конфига. Не пойму пока что, что туда кидать, за исключением одного клиента, у которого за openvpn-клиентом подсеть, куда нужен доступ из сети за сервером - у меня там конфиг с iroute.
      5 - классно)
      6 - тоже хорошо. Однако easyrsa можно докрутить до того, что его ЦС будет работать именно с эллиптической криптографией (set_var EASYRSA_ALGO ec; set_var EASYRSA_CURVE secp384r1)


      1. darkgerion
        05.11.2025 07:16

        1) скорее избавился от обертки на баш для дерганья openssl и можно извращаться с передачей контента в openssl в стиле -in <aws s3 cp filename -) без сохранения чувствительных данных на диске

        2) это описано в официальном мане openvpn. Pem перечитывается раз в час, если правильно помню, а в dir смотрит при попытке подключения. Таким образом при наличии файла и посланного kill в менеджмент сокет получается выбить сессию с минимальным лагом и без необходимости рестарта самого сервиса.

        3) txqueuelen 1000, как и у eth. Буферы на сервере надо подбирать исходя из ресурсов машины и наличия тюна сетевухи. Клиенту пушил 0, чтобы использовались оптимальные значения его ОС. Почему - для лучшей утилизации канала.

        4) пушить туда можно фиксированный ip, маршруты, dns сервера, домены для резолва. Тоже есть в мане. Было актуально для split-dns, но тут есть нюансы в разных клиентах. В версии 2.7 обещают это переосмыслить, но пока все в бете.

        5) прожевывается легче, чем старый добрый aes-cbc/ctr.

        6) можно, но не очевидно. Дергать модуль shell с конкретными флагами openssl было более наглядно.

        Ну и для кучи - отключать сжатие или ставить compress stubv2 для совместимости со старыми клиентами (сами разрабы рекомендуют для секьюрности).

        В итоге синтетика с локальным спидтестом при использовании udp выдавала метров 250-300 сидя на вайфае, при провайдерском тарифе 500


        1. darkgerion
          05.11.2025 07:16

          UPD. Про crl немного попутал. Он считывается при новом подключении или при пересогласовании уже имеющейся сессии, а там дефолт час. Т.е можно провернуть kill и с crl.pem для быстрого блока. Только я бы дергал апдейт не кроном, а какой-то тулзой, умеющей в inotify/fsevents типа lsyncd, если использовать easy-rsa на отдельной машине, а не крутить пайплайны


  1. Gutt
    05.11.2025 07:16

    Только не пытайтесь это использовать в России. WG активно режется РКН.


    1. domix32
      05.11.2025 07:16

      как и OVPN, но у автора вроде и раньше работало и новое завелось, хотя он не уточнял свою локацию.