Что здесь собирается
Один инстанс с WireGuard, выдающий каждому клиенту приватный IPv4-адрес, маршрутизируемый IPv6-адрес из вашего собственного /48 и резолвер, который живёт внутри туннеля, так что DNS-запросы никогда не идут другим путём, чем остальной трафик. Межсетевой экран по умолчанию запрещает всё в обоих направлениях. Пересылка включена только для туннельного интерфейса и только исходящая.
Проверено на R-4 в AMS-01 с Debian 13. WireGuard работает с одним потоком и на стороне ядра, так что больше ядер вам ничего не даст, пока вы не гоните несколько гигабит через него. Гораздо важнее площадка: выбирайте ту, что ближе к вам, потому что каждая добавленная здесь миллисекунда добавляется к каждому пакету.
Перед началом
- Развёрнутый инстанс и root по SSH.
- Включённый аддон IPv6 /48 в панели. Включённого /64 хватит на одного клиента, но выдача подсетей каждому клиенту из одного /64 означает проксирование neighbor discovery, а это хобби, а не конфигурация.
- Имя хоста, указывающее на инстанс. Всё ниже использует
vpn.example.com. - Каждый адрес
2001:db8:здесь — документация. Замените префикс на тот, что показан на странице вашего инстанса.
1. Пакеты и настройки ядра
apt update && apt full-upgrade -y
apt install -y wireguard-tools nftables unbound qrencode
ip -br linkЭта последняя команда выводит имя вашего внешнего интерфейса. На наших KVM-образах это ens3; если у вас другое, подставьте его везде ниже. Теперь ядро:
cat > /etc/sysctl.d/99-wireguard.conf <<EOF
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
net.ipv6.conf.ens3.accept_ra = 2
net.ipv4.conf.all.rp_filter = 1
EOF
sysctl --systemСтрока accept_ra = 2 — та, что все забывают. Включение IPv6-пересылки заставляет ядро перестать принимать Router Advertisements, маршрут по умолчанию исчезает в течение десяти минут, и машина теряет связь по v6, оставаясь доступной по v4. Установка в два заставляет продолжать принимать объявления на маршрутизирующем хосте.
2. Ключи
install -d -m 700 /etc/wireguard
umask 077
wg genkey | tee /etc/wireguard/server.key | wg pubkey > /etc/wireguard/server.pub
wg genkey | tee /etc/wireguard/laptop.key | wg pubkey > /etc/wireguard/laptop.pub
wg genpsk > /etc/wireguard/laptop.pskPre-shared key в этой сборке не опционален. Он ничего не стоит, это одна строка в каждом конфиге, и это разница между туннелем, который просто безопасен сегодня, и тем, что останется безопасным против противника, записывающего трафик сейчас, чтобы расшифровать позже.
3. Серверный интерфейс
Напишите /etc/wireguard/wg0.conf, вставив содержимое ключей, где отмечено:
[Interface]
Address = 10.7.0.1/24, 2001:db8:1a2b:7::1/64
ListenPort = 51820
PrivateKey = <contents of /etc/wireguard/server.key>
MTU = 1420
[Peer]
# laptop
PublicKey = <contents of /etc/wireguard/laptop.pub>
PresharedKey = <contents of /etc/wireguard/laptop.psk>
AllowedIPs = 10.7.0.2/32, 2001:db8:1a2b:7::2/128На стороне сервера AllowedIPs — это таблица маршрутизации, а не список разрешений. Дайте каждому пиру ровно те адреса, которыми он владеет, и не шире, иначе два клиента будут спорить за один и тот же маршрут, и проигравший просто перестанет получать пакеты.
chmod 600 /etc/wireguard/wg0.conf4. Резолвер внутри туннеля
Указывать клиентам на публичный резолвер — значит свести на нет большую часть смысла. Unbound уже установлен; дайте ему /etc/unbound/unbound.conf.d/tunnel.conf:
server:
interface: 10.7.0.1
interface: 2001:db8:1a2b:7::1
access-control: 10.7.0.0/24 allow
access-control: 2001:db8:1a2b:7::/64 allow
do-ip6: yes
prefetch: yes
hide-identity: yes
hide-version: yes
qname-minimisation: yesUnbound не запустится, пока туннель не существует, потому что эти адреса ещё не подняты. Правильно организуйте последовательность, а не боритесь с ним:
systemctl edit unbound[Unit]
[email protected]
[email protected]5. Межсетевой экран
Замените /etc/nftables.conf целиком:
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
ct state invalid drop
iif lo accept
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
tcp dport 22 accept
udp dport 51820 accept
iifname "wg0" udp dport 53 accept
iifname "wg0" tcp dport 53 accept
}
chain forward {
type filter hook forward priority filter; policy drop;
ct state established,related accept
iifname "wg0" oifname "ens3" accept
iifname "wg0" oifname "wg0" drop
}
chain output {
type filter hook output priority filter; policy accept;
}
}
table ip nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
ip saddr 10.7.0.0/24 oifname "ens3" masquerade
}
}Обратите внимание на то, чего нет: нет таблицы NAT для IPv6. Клиенты выходят наружу со своим адресом из вашего /48, что и есть причина брать /48 в первую очередь. Строка iifname "wg0" oifname "wg0" drop не даёт пирам достучаться друг до друга, что вам и нужно, если вы не строите mesh намеренно.
systemctl enable --now nftables
nft list ruleset | head -206. Запуск
systemctl enable --now wg-quick@wg0
systemctl restart unbound
wg show wg07. Клиент
[Interface]
PrivateKey = <contents of /etc/wireguard/laptop.key>
Address = 10.7.0.2/24, 2001:db8:1a2b:7::2/64
DNS = 10.7.0.1
MTU = 1420
[Peer]
PublicKey = <contents of /etc/wireguard/server.pub>
PresharedKey = <contents of /etc/wireguard/laptop.psk>
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25Для телефона превратите это в QR-код на сервере и сфотографируйте терминал:
qrencode -t ansiutf8 < /root/laptop.confНа клиенте Debian или Ubuntu для строки DNS = нужен установленный openresolv, иначе wg-quick печатает предупреждение и молча оставляет ваш старый резолвер на месте. Это самая частая причина того, что туннель работает идеально, а каждый запрос утекает.
Проверка
Четыре проверки по порядку. Любая неудачная укажет, к какому шагу вернуться.
# on the server, after connecting the client
wg show wg0 latest-handshakes
wg show wg0 transferОтметка времени рукопожатия в пределах последних двух минут и ненулевые счётчики в обоих направлениях означают, что и криптография, и маршрутизация в порядке.
# on the client
ping -c3 10.7.0.1
ping -c3 2001:db8:1a2b:7::1
dig @10.7.0.1 paragonvps.com AAAA +shortТретья команда доказывает, что туннельный резолвер отвечает. Если он не отвечает, значит, unbound запустился раньше, чем появился wg0; перезапустите его.
Последняя проверка — единственная, которая доказывает, что ваш трафик выходит туда, куда вы думаете, и ей нужна вторая машина. Подойдёт любой другой инстанс во флоте, в любом городе:
# on the second instance
nc -lv 9101
# on the client, tunnel up
curl -m5 -4 http://second.example.com:9101/
curl -m5 -6 http://second.example.com:9101/Слушатель печатает адрес источника каждого соединения. Ваш запрос по v4 должен прийти с WireGuard-сервера, потому что он маскарадится. Запрос по v6 должен прийти с собственного адреса клиента в вашем /48, нетронутым, что и означает routed — то, чего proxied не делает.
Если что-то не так
| Симптом | Причина | Исправление |
|---|---|---|
| Рукопожатие удаётся, трафик не идёт | Пересылка выключена или forward chain сбрасывает | sysctl net.ipv4.ip_forward, затем перечитайте forward chain |
| Мелкие запросы в порядке, большие загрузки зависают | MTU и path discovery | Снизьте в обоих конфигах до MTU = 1380 и повторите |
| IPv6 отваливается через десять минут после загрузки | Пересылка убила Router Advertisements | accept_ra = 2 на внешнем интерфейсе |
| DNS резолвится вне туннеля | Клиент проигнорировал DNS = | Установите openresolv на клиенте |
| Всё пропало после перезагрузки | Модули не включены | systemctl enable wg-quick@wg0 nftables unbound |
Дальше разумные шаги — второй endpoint в другом регионе и мониторинг, который скажет вам, когда один из них перестанет отвечать. Оба описаны в руководствах; на странице сети объясняется, что край делает с вашим трафиком до того, как он дойдёт до вас.