Да, я знаю. «Опять статья про WireGuard». Но это не очередное «how-to» на 5 строк. Это — история боли, паранойи и, наконец, автоматизации. Это история о том, как мы перестали «обслуживать» VPN и заставили его работать на нас.
Привет, Хабр! Меня зовут... впрочем, неважно. Важно то, что я 10 лет занимаюсь тем, что «делаю, чтобы работало». И последние лет 8 из них я, как и вы, страдал.
Мое утро начиналось не с кофе, а с тикета в Jira:
«У меня отвалился VPN, не могу подключиться» (протух сертификат)
«Почему так медленно открывается...» (привет, TCP-over-TCP)
«Я не могу выпустить сертификат для нового сотрудника» (опять сломали
easy-rsa)
Мы все через это проходили. OpenVPN — это как старый-добрый ВАЗ-2106. Он едет, он легенда, и у каждого админа в гараже есть ящик запчастей для него (PKI, скрипты отзыва, кастомные push route). Но в 2025 году ездить на этом каждый день — больно.
В какой-то момент мы просто сели и посчитали, сколько человеко-часов уходит на обслуживание VPN-инфраструктуры. И прослезились. Пришло время перемен.
Наша "Боль", или "Классика", которая устала
Проблема была не в том, что OpenVPN плохой. Он просто... сложный. И громоздкий.
Управление PKI (Инфраструктура открытых ключей): Это главный монстр. Управление центром сертификации (CA), выпуск ключей для каждого пользователя, отзыв скомпрометированных ключей (CRL), отслеживание сроков действия... Это отдельная, полноценная работа.
Производительность: OpenVPN работает в userspace. Это оверхед. На шифрование тратится много CPU. При плохом канале (особенно мобильном) постоянные переподключения превращали жизнь «удаленщиков» в ад.
Кодовая база: Сотни тысяч строк кода. С точки зрения «здоровой паранойи» (а я фанат ИБ), это гигантская поверхность для атаки. Чем больше кода — тем больше багов.
Конфигурация: Гибкость 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) — нет.
-
Лечение: Две причины:
Вы забыли
net.ipv4.ip_forward=1иsudo sysctl -p.Ваши
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.Конфиг
/etc/wireguard/wg0.confпревратился в Ansible-шаблон (wg0.conf.j2).-
Информация о "пирах" (клиентах) хранится в
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" -
Ansible-шаблон просто пробегается по этому списку циклом:
Django
... # --- ПИРЫ --- {% for peer in wireguard_peers %} [Peer] # {{ peer.name }} PublicKey = {{ peer.public_key }} AllowedIPs = {{ peer.ip }} {% endfor %} -
Процесс для нового сотрудника:
Он сам генерирует у себя пару ключей.
Создает Merge Request в наш репозиторий, добавляя свой
public_keyи имя в YAML.Старший инженер ревьюит MR, жмет "Merge".
CI/CD-пайплайн запускает Ansible-playbook, который раскатывает новый конфиг на VPN-сервер и бесшовно перезагружает его (
wg syncconf wg0).
ВСЁ. Мы получили GitOps-управляемый VPN. Админы больше не занимаются ключами, они просто аппрувят MR.
Заключение: Что мы получили?
Скорость: Подключение к VPN занимает 0.1 секунды. Не 10-30 секунд, как с OpenVPN.
Надежность: Связь не рвется при смене сети (с Wi-Fi на LTE).
Безопасность: Минимальная кодовая база и простая криптография.
Прозрачность: Вся конфигурация VPN-клиентов лежит в Git. Мы видим, кто и когда получил доступ.
Свободное время: Самое главное. У нас больше нет тикетов «VPN не работает». Мы занимаемся делом, а не «обслуживанием».
Так что да, если вы еще сидите на OpenVPN и страдаете — хватит это терпеть. Путь займет один вечер, а сэкономит сотни часов в будущем.
Комментарии (15)

vesper-bot
05.11.2025 07:16Хмм. Про грабли:
Управление CA - хотя и неприятно, но можно сделать на вменяемом уровне. То же создание CRL и подсовывание его серверу вполне себе автоматизируется на уровне крона. Обновление сертификатов - удобство vs безопасность, либо у тебя потерянные SSH-ключи на wg, либо забытые сертификаты на easyrsa, что технически одно и то же. Но если серт рано или поздно истечёт, то ключ - нет.
Userspace vs kernel space - грабли валидные, может наткнёмся со временем, но оверхэд там для нас не критичен настолько, чтобы решало. (Вот будь у нас 40Гбит/с снаружи, могло бы влиять по идее)
Спорные грабли. Да, wireguard меньше в виде кода, но статические (и динамические) анализаторы решают проблему аудита в поисках багов довольно эффективно. Ну и никто не застрахован от бага в либах, который можно проэксплойтить даже через идеальный код использующего приложения.
Насколько у вас сложные правила для OpenVPN, что вам надо поддерживать кастомные скрипты OpenVPN на стороне клиентов? А также, насколько часто у вас меняется конфигурация локальной сети "за" сервером OpenVPN, что надо лазать в конфиг и править там push route или ещё какие-то параметры? Один раз заточил, и оно "просто" работает. Про частые проблемы:
"Не могу зайти в VPN" - ну, штатная ситуация, можно лечить скриптами автоматического перевыпуска сертификатов, условно раз в месяц, и превентивно рассылать новые конфиги. По сравнению со статическими SSH-ключами Wireguard - удобство vs безопасность, плюс защита от забывчивости в случае увольнения кого-то, если сразу не сделали отзыв. По мне, так или иначе доступ к VPN админить надо, ваше решение не хуже многих.
OpenVPN прекрасно умеет работать по UDP - что мешает/мешало перенастроить сервер (поднять второй инстанс, начать перевозить клиентов по инструкции, ещё как) на использование UDP в качестве транспортного протокола? Проблема разрыва соединений и переподключения в мобильном роуминге решается параметром auth-gen-token (время в секундах) в конфиге сервера или CCD клиента, если нельзя на весь сервер включить.
А что это у вас кто попало лазает в easyrsa кривыми руками? Он, кстати, по логике должен жить отдельно от OpenVPN, в идеале на нормально выключенной ВМ, и бэкапаться регулярно, как центр доверия системы VPN. И при нормальной безопасности разрешение на вход по VPN должно выдаваться администраторами, кривых рук там быть не должно (как есть - смотрите сами, кто там вам ломал ЦС, какими действиями, почему и зачем, и насколько сильно его за это бить - тоже). Про альтернативы:
Какая такая проблема сложности PKI? Сложно создать CRL и добросить его до сервера? Сложно мониторить истечение сертификатов? (openssl x509 -enddate) Сложно держать его несломанным? Грубо говоря, семь бед - один backup.
"Голый" IPsec?! Он хорош для перманентных подключений через белые IP, и неизменяемых настройках пробрасываемых сетей. Применять его для подключения бегающих клиентов как минимум нелогично. L2TP/IPsec - вариант, но наши "доблестные" РКП его режут нещадно. Да, не вариант, но то, что он вообще тут появился, не подходит под описываемый сценарий использования VPN. 3, 4 - да, инфраструктура VPN должна быть "своей" вместе с центром доверия - но задачи управления им с вас не снимаются.
Проблемы настройки WG vs OpenVPN:
Firewall - смешно. Вы же проверяете подключение до того, как выставить сервер в прод, да? (ДА?!)
IPv4 forwarding - вообще аксиома, VPN-сервер, любой, является роутером и форвардить IPv4 обязан. (IPv6 не забудьте, кстати, если настраивали, он отдельно включается)
Ну, keepalive есть и в OpenVPN, но да, нужен.
Добавление - ну ОК, а удаление?
Итого: Можно было обойтись перенастройкой OpenVPN с тем же эффектом (ну, минус оверхэд, от него не уйти).

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).
Спасибо за то, что заставили копнуть глубже!

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

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

darkgerion
05.11.2025 07:161) Года 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, который выносит крипту из юзерспейса, но пока производительность устраивает и так.
vesper-bot
05.11.2025 07:161 - получается, переизобрели easyrsa, но на другой кодовой базе.
2 - интересная идея, при работе со стандартным ЦС такой опции не предусмотрено.
3 - а на сколько, и почему?
4 - вот это поддерживать надо, и зависит от конфига. Не пойму пока что, что туда кидать, за исключением одного клиента, у которого за openvpn-клиентом подсеть, куда нужен доступ из сети за сервером - у меня там конфиг с iroute.
5 - классно)
6 - тоже хорошо. Однако easyrsa можно докрутить до того, что его ЦС будет работать именно с эллиптической криптографией (set_var EASYRSA_ALGO ec; set_var EASYRSA_CURVE secp384r1)
darkgerion
05.11.2025 07:161) скорее избавился от обертки на баш для дерганья 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

darkgerion
05.11.2025 07:16UPD. Про crl немного попутал. Он считывается при новом подключении или при пересогласовании уже имеющейся сессии, а там дефолт час. Т.е можно провернуть kill и с crl.pem для быстрого блока. Только я бы дергал апдейт не кроном, а какой-то тулзой, умеющей в inotify/fsevents типа lsyncd, если использовать easy-rsa на отдельной машине, а не крутить пайплайны
gotch
Зачем клиентам NAT?
xitriy87
Наверное потому что у них сервер не на шлюзе и про маршруты до vpn сети никто не знает.
AdminFuture Автор
Классический
MASQUERADE, чтобы не бегать к сетевикам. 'Прод' видит трафик от самого WG-сервера и ему же отвечает.gotch
А потом на серверах авторизации (контроллерах домена) неуспешные попытки аутентификации с ip WG, а не конкретных клиентов. Не комильфо.