cloud-init: автонастройка VPN при создании сервера
Обычный сценарий: вы создаёте VPS, ждёте загрузки, заходите по SSH, ставите WireGuard или OpenVPN, генерируете ключи, правите конфиг, включаете форвардинг — и только через 10-15 минут ручной работы сервер готов раздавать VPN. Если серверы нужны регулярно (тестовые окружения, серверы под конкретную поездку, замена заблокированного IP), эта рутина утомляет и допускает ошибки от невнимательности. cloud-init убирает этот шаг: вы описываете конфигурацию один раз в user-data, и VPN поднимается сам в первые секунды жизни сервера — ещё до того, как вы успеете открыть терминал.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Что такое cloud-init и что происходит при первом старте VM
cloud-init — это сервис, который есть почти во всех облачных образах Ubuntu, Debian, AlmaLinux и других дистрибутивов. При создании сервера через панель управления или API провайдер передаёт в VM текстовый блок — user-data. cloud-init читает его на самом первом старте и выполняет описанные там действия: устанавливает пакеты, создаёт файлы, запускает команды.
Важная деталь: user-data выполняется один раз — при первой загрузке инстанса, определяемой по уникальному instance-id. Перезагрузка сервера cloud-init заново не запускает. Этим он отличается от systemd-юнита или крон-задачи: не про постоянную работу, а про начальную инициализацию — ровно то, что нужно для разового поднятия VPN при создании сервера.
Формат user-data определяется первой строкой файла. Самый частый и удобный для VPN-задач — #cloud-config, YAML-подобный декларативный формат с модулями packages, write_files, runcmd. Альтернатива — обычный shell-скрипт с шебангом #!/bin/bash, который просто выполняется как есть. Для сложных сценариев их можно объединить через multi-part MIME, но для одного VPN-сервера хватает одного #cloud-config.
Как передать user-data серверу
У большинства провайдеров VPS есть поле User Data прямо в форме создания сервера в панели — обычно на шаге выбора образа или в разделе «Advanced options». Туда вставляется содержимое файла целиком.
Через CLI или API это тоже одна опция:
# пример для облака с cloud-init API (синтаксис условный,
# у каждого провайдера свои названия параметров)
curl -X POST https://api.example-cloud.com/v1/servers \
-H "Authorization: Bearer $API_TOKEN" \
-d image=ubuntu-24-04 \
-d size=2vcpu-4gb \
-d region=fra1 \
--data-urlencode "user_data@cloud-init.yaml"
Если вы уже используете Terraform для создания VPS, user-data передаётся через параметр ресурса (обычно называется user_data) с функцией templatefile(), которая подставляет в шаблон реальные значения — например, публичный ключ клиента — перед отправкой:
resource "cloud_server" "vpn" {
name = "vpn-01"
image = "ubuntu-24-04"
size = "1vcpu-1gb"
region = "fra1"
user_data = templatefile("${path.module}/cloud-init-wg.yaml.tpl", {
server_private_key = var.wg_server_private_key
client_public_key = var.wg_client_public_key
})
}
Так весь процесс — от terraform apply до готового поднятого туннеля — укладывается в одну команду без единого ручного захода на сервер.
Арендуйте сервер под свои задачи!
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверГотовый user-data для WireGuard
Ключи WireGuard лучше сгенерировать заранее на своей машине (wg genkey, wg pubkey), а не пытаться делать это внутри cloud-init — так проще контролировать, какой ключ куда попадает, и не завязываться на порядок выполнения модулей. Вот рабочий #cloud-config, который ставит WireGuard и поднимает интерфейс сразу после установки пакетов:
#cloud-config
package_update: true
packages:
- wireguard
- wireguard-tools
write_files:
- path: /etc/wireguard/wg0.conf
permissions: '0600'
owner: root:root
content: |
[Interface]
PrivateKey = SERVER_PRIVATE_KEY_HERE
Address = 10.10.0.1/24
ListenPort = 51820
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
PublicKey = CLIENT_PUBLIC_KEY_HERE
AllowedIPs = 10.10.0.2/32
runcmd:
- sed -i 's/#net.ipv4.ip_forward=1/net.ipv4.ip_forward=1/' /etc/sysctl.conf
- sysctl -p
- systemctl enable wg-quick@wg0
- systemctl start wg-quick@wg0
Пара нюансов, которые часто ломают этот сценарий на практике:
- Имя внешнего интерфейса в
PostUp/PostDown(eth0) нужно проверить под конкретный образ — у части провайдеров этоens3илиenp1s0. Если ошибиться,wg-quickне упадёт, но NAT работать не будет, а трафик из туннеля наружу не пойдёт. - Модуль
runcmdвыполняется послеwrite_filesиpackages, порядок в файле cloud-config гарантирован — можно не бояться, что сервис попытается стартовать раньше, чем появится конфиг. - Если вместо
sedвы предпочитаете системный способ, вариант чище — положитьnet.ipv4.ip_forward=1отдельным файлом в/etc/sysctl.d/99-wireguard.confчерез тот жеwrite_files.
Это тот же сервер, который получился бы после ручной настройки по шагам из статьи про установку WireGuard на Ubuntu 24.04 — просто без вашего участия и без риска что-то забыть на середине.
Вариант для OpenVPN: осторожнее с PKI
С OpenVPN автоматизация первого старта сложнее, потому что классическая схема требует полноценного центра сертификации (easy-rsa): генерация CA, серверного и клиентских сертификатов. Генерировать CA прямо в cloud-init — плохая идея: приватный ключ CA окажется в user-data в открытом виде, а user-data часто остаётся доступным через API/метаданные сервера ещё долго после создания.
Практичнее два подхода. Первый — сгенерировать PKI заранее (например, локально или на отдельном защищённом сервере) и передать через cloud-init уже готовые серверный сертификат, ключ и dh.pem:
#cloud-config
package_update: true
packages:
- openvpn
write_files:
- path: /etc/openvpn/server.conf
content: |
port 1194
proto udp
dev tun
ca /etc/openvpn/ca.crt
cert /etc/openvpn/server.crt
key /etc/openvpn/server.key
dh /etc/openvpn/dh.pem
server 10.20.0.0 255.255.255.0
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 1.1.1.1"
keepalive 10 120
persist-key
persist-tun
- path: /etc/openvpn/ca.crt
content: |
-----BEGIN CERTIFICATE-----
...ваш CA-сертификат...
-----END CERTIFICATE-----
- path: /etc/openvpn/server.key
permissions: '0600'
content: |
-----BEGIN PRIVATE KEY-----
...приватный ключ сервера...
-----END PRIVATE KEY-----
runcmd:
- sed -i 's/#net.ipv4.ip_forward=1/net.ipv4.ip_forward=1/' /etc/sysctl.conf
- sysctl -p
- iptables -t nat -A POSTROUTING -s 10.20.0.0/24 -o eth0 -j MASQUERADE
- systemctl enable openvpn@server
- systemctl start openvpn@server
Блок server.crt в примере опущен для краткости — в реальном файле он идёт тем же write_files-блоком, что и ca.crt. Второй подход — вообще не класть сертификаты в user-data, а скачать их при загрузке с защищённого эндпоинта по одноразовому токену (об этом ниже). Для полноценной установки с нуля, если автоматизация не нужна с первой секунды, разумнее ориентироваться на пошаговую установку OpenVPN на Ubuntu 24.04 и переносить в cloud-init только те шаги, которые действительно повторяются от сервера к серверу.
Ключи и секреты: чего не стоит класть в user-data напрямую
user-data — не защищённое хранилище секретов. На большинстве провайдеров его содержимое доступно через API аккаунта, а изнутри самой VM — через сервис метаданных (curl http://169.254.169.254/...) без какой-либо авторизации, кроме факта, что запрос идёт с самого сервера. Для приватного ключа WireGuard одного личного VPN-сервера это приемлемый риск: утечка требует либо доступа к панели, либо root на самой VM, которые и так критичны. Но масштабировать подход на что-то чувствительнее — CA-ключ, сертификаты для десятков клиентов — не стоит.
Более безопасная схема — не embedding, а fetch-at-boot: user-data не содержит секрет, а только команду на его получение с одноразовым или короткоживущим токеном:
#cloud-config
packages:
- wireguard
runcmd:
- curl -sSf -H "Authorization: Bearer ${ONE_TIME_TOKEN}" \
https://secrets.example.internal/vpn/wg0.conf \
-o /etc/wireguard/wg0.conf
- chmod 600 /etc/wireguard/wg0.conf
- systemctl enable --now wg-quick@wg0
Токен в этом случае живёт ровно до первого использования (сервер секретов его отзывает), а сам приватный ключ никогда не сохраняется в логах API-запроса на создание сервера. Для одного домашнего или рабочего VPN-сервера это избыточно, но если вы автоматизируете создание серверов регулярно и через общий CI/CD — это уже разумная гигиена, а не паранойя.
Проверка и отладка, если VPN не поднялся
Первое, что стоит сделать после создания сервера — не пытаться сразу подключиться клиентом, а проверить, что сам cloud-init отработал без ошибок:
cloud-init status --long
Статус done без ошибок в поле errors означает, что все модули завершились штатно. Если что-то пошло не так, детальный лог выполнения — в двух местах:
cat /var/log/cloud-init-output.log # stdout/stderr всех команд из runcmd
cat /var/log/cloud-init.log # подробный лог самого cloud-init построчно
Типичные причины, по которым VPN не поднимается, несмотря на «зелёный» статус cloud-init:
- Опечатка в имени внешнего интерфейса в
PostUp/PostDown— сервис стартует, но трафик не маршрутизируется. - IP-forwarding не применился, потому что
sysctl -pвыполнился раньше, чем изменения записались в файл (редко, но случается при нестандартном порядке модулей) — проверяется черезsysctl net.ipv4.ip_forward, должно быть1. - Провайдер режет исходящий UDP на нестандартный порт по умолчанию — стоит явно проверить правила firewall/security group на стороне панели, cloud-init их не трогает.
- В
write_filesконфиг записался с неверными правами доступа, и сервис отказался стартовать из-за слишком открытого файла с приватным ключом — WireGuard и OpenVPN оба чувствительны к правам600на файлы с ключами.
Для отладки шаблона на уже существующей машине можно принудительно прогнать модули заново без пересоздания сервера:
sudo cloud-init clean --logs
sudo cloud-init init
sudo cloud-init modules --mode=config
sudo cloud-init modules --mode=final
Это не полный аналог первого старта (часть вещей привязана к первой загрузке ядра), но для отладки write_files и runcmd достаточно — и заметно быстрее, чем пересоздавать VM на каждую правку YAML.
Арендуйте сервер под свои задачи!
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Все ли провайдеры VPS поддерживают user-data через cloud-init?
Практически все крупные площадки — да, для образов на базе Ubuntu, Debian, AlmaLinux, Rocky Linux. Поддержка обычно не заявлена явно как «фича VPN», а идёт как общая возможность облачных образов — ищите поле «User Data» или «Cloud-init» на шаге создания сервера или в разделе Advanced.
Что если нужен не Ubuntu/Debian, а другой дистрибутив?
cloud-init есть и в облачных образах CentOS/AlmaLinux/Rocky, но набор доступных модулей и путей к пакетным менеджерам отличается — например, вместо apt нужен dnf, и в некоторых сборках cloud-init предустановлен не по умолчанию. Перед переносом скрипта на другой дистрибутив стоит проверить cloud-init --version и доступность нужных модулей.
Безопасно ли класть приватный ключ WireGuard прямо в user-data, как в примере?
Для личного или рабочего VPN-сервера — приемлемый компромисс: риск ограничен доступом к панели провайдера и root-доступом на самой VM, которые и так критичны для безопасности. Для чувствительных сценариев (CA-ключи, десятки клиентов) лучше схема с получением секрета по токену при загрузке, описанная выше.
Можно ли использовать один и тот же user-data для нескольких серверов?
Технически да, но для WireGuard и OpenVPN так делать не стоит — одинаковый приватный ключ или общий CA без разделения по клиентским сертификатам ломает саму модель безопасности VPN. Используйте шаблонизацию (Terraform templatefile или подстановку переменных перед отправкой) даже для похожих серверов.
cloud-init выполнится повторно, если развернуть сервер из snapshot?
Зависит от того, как провайдер обрабатывает instance-id при клонировании: часто новый сервер получает новый ID и user-data выполнится заново, но иногда ID сохраняется, и cloud-init решает, что уже всё сделал. Стоит проверить один раз на конкретном провайдере, прежде чем полагаться на снапшоты как способ клонирования VPN-серверов.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →