VPN AmneziaWG
16.03.2026 #Dev #Life

VPN AmneziaWG с веб-панелью на Debian

Мир обожает что-нибудь блокировать. Твоя задача - чтобы люди могли работать. Погнали.

Если тебе нужен VPN с человеческим лицом, где видно клиентов, можно выдать конфиг по QR и при этом не париться с ключами вручную, то связка AmneziaWG и amnezia-wg-easy твой бро.

AmneziaWG - это форк WireGuard (какой же кайф давать ссылки на себя же) с обфускацией трафика (удобно там, где обычный WG режут по форме).
В статье - пошаговая установка на Debian 13 (выбрал по ресурсам и доступности)

Шаг 0: что должно быть под рукой

  • VPS на Debian 12/13 (достаточно 1 GB RAM, 1 CPU, это же Дебиан ♥️).
  • Домен, направленный на IP сервера (или готовность заходить по IP).
  • Доступ по SSH под root (или sudo).

Дальше все команды от root. Интерфейс у меня был ens3, IP - свой; у тебя могут быть другие имена интерфейсов подставляй свои.

Шаг 1: подготовка системы и установка Docker

Docker нам нужен, потому что amnezia-wg-easy это образ с панелью и логикой управления WireGuard/AmneziaWG в одном флаконе. Крутить это голыми руками без контейнера примерно так же увлекательно как вручную собирать жигули в гараже.

1.1. Обновление пакетов

apt update && apt upgrade -y

Стандартная гигиена: актуальные патчи и пакеты. На триальном VPS иногда висит старый kernel после upgrade сделай перезагрузку.

1.2. Зависимости и репозиторий Docker

apt install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(. /etc/os-release && echo "${VERSION_CODENAME:-$VERSION}") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
apt update

Добавляем официальный репозиторий Docker под твою кодовую базу Debian (bookworm, trixie и т.д.). Так мы получаем нормальные версии docker-ce, а не из старых репозиториев дистрибутива.

1.3. Установка Docker

apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

1.4. Запуск и проверка

systemctl enable --now docker
docker --version
docker run --rm hello-world

Если hello-world отработал и в логе видно Hello from Docker! - движок поднят.

Шаг 2: включение IP forwarding

VPN работает так: пакеты приходят на сервер от клиента, сервер должен отправить их дальше в интернет от своего имени и вернуть ответ обратно в туннель. Для этого ядро должно разрешать пересылку пакетов между интерфейсами, то есть IP forwarding. Без него клиент поднимется, но в интернет не выйдет.

echo 'net.ipv4.ip_forward = 1' > /etc/sysctl.d/99-vpn.conf
sysctl --system

Проверка:

cat /proc/sys/net/ipv4/ip_forward

Должна быть 1. После перезагрузки параметр подхватится из /etc/sysctl.d/99-vpn.conf.

Шаг 3: порты и фаервол

Нужно, чтобы снаружи были доступны:

  • UDP 51820  сам AmneziaWG (подключения клиентов).
  • TCP 443 веб-панель (мы поднимем её на 443, чтобы не открывать лишний порт у провайдера).

Если пользуешься iptables вручную задрот, то добавляем правила и сохраняем их (иначе после перезагрузки всё слетит):

iptables -I INPUT -p udp --dport 51820 -j ACCEPT
iptables -I INPUT -p tcp --dport 443 -j ACCEPT
apt install -y iptables-persistent
netfilter-persistent save

Если у тебя уже есть свой фаервол (ufw, nftables и т.д.), открой эти порты в нём. У части хостингов есть ещё внешний фаервол в панели, туда тоже стоит добавить 51820/udp и 443/tcp. Иначе будешь полчаса искать причину, почему с сервера curl отвечает, а с ноутбука хуй нет.

Шаг 4: модуль ядра AmneziaWG

AmneziaWG не просто WireGuard: у него свой модуль ядра с обфускацией. Образ amnezia-wg-easy из коробки ожидает, что на хосте этот модуль уже есть, а сам контейнер мы запустим с --network host, чтобы он пользовался интерфейсом wg0 на хосте. Поэтому сначала ставим модуль и утилиты на сам сервер.

4.1. Зависимости и репозиторий Amnezia

apt install -y linux-headers-$(uname -r) curl gnupg
install -d -m 755 /etc/apt/keyrings
curl -fsSL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x57290828" | gpg --dearmor --output /etc/apt/keyrings/amneziawg-keyring.gpg
echo 'Types: deb
URIs: https://ppa.launchpadcontent.net/amnezia/ppa/ubuntu
Suites: focal
Components: main
Signed-By: /etc/apt/keyrings/amneziawg-keyring.gpg' > /etc/apt/sources.list.d/amneziawg.sources
apt update

да, это Ubuntu PPA, и это нормально, официального Debian-пакета пока нет

4.2. Установка пакетов AmneziaWG

apt install -y amneziawg amneziawg-tools

Подтянутся amneziawg-dkms (модуль ядра), amneziawg-tools (утилиты). DKMS соберёт модуль под твоё текущее ядро. После установки модуль уже загружен. Проверка:

lsmod | grep amneziawg

Должна быть строка с amneziawg.

Шаг 5: запуск amnezia-wg-easy (панель на 443)

Используем образ eyrafir/amnezia-wg-easy: в нём и панель, и AmneziaWG. Запуск с --network host нужен, чтобы контейнер создавал интерфейс wg0 на хосте и использовал наш только что установленный модуль ядра. Панель поднимаем на порту 443, чтобы не плодить лишние порты и не упираться в фаервол хостера.

5.1. Каталог для данных и пароль

mkdir -p /root/.amnezia-wg-easy
# Пароль для входа в веб-панель - задай свой, сложный
AMNEZIA_PASSWORD="$(openssl rand -base64 24)"
echo "$AMNEZIA_PASSWORD" > /root/.amnezia-wg-easy-password.txt
chmod 600 /root/.amnezia-wg-easy-password.txt
echo "Пароль панели сохранён в /root/.amnezia-wg-easy-password.txt"

5.2. Запуск контейнера

docker run -d \
  --name amnezia-wg-easy \
  --network host \
  -e WG_HOST=ТВОЙ_ДОМЕН_ИЛИ_IP \
  -e PASSWORD="$(cat /root/.amnezia-wg-easy-password.txt)" \
  -e WG_PORT=51820 \
  -e PORT=443 \
  -v /root/.amnezia-wg-easy:/etc/wireguard \
  --cap-add=NET_ADMIN \
  --restart unless-stopped \
  eyrafir/amnezia-wg-easy:latest

Замени ТВОЙ_ДОМЕН_ИЛИ_IP на домен (например na.butilky.ru) или IP сервера. В конфигах клиентов будет указан именно он.

Проверка:

docker ps
ss -tlnp | grep 443
ss -ulnp | grep 51820

Должен быть контейнер amnezia-wg-easy в статусе Up, на 443, что-то вроде node, на 51820/udp процесс из контейнера.

5.3. NAT для VPN-подсети

Трафик клиентов идёт в подсеть 10.8.0.0/24. Чтобы он уходил в интернет, на хосте нужен NAT (MASQUERADE) в сторону твоего внешнего интерфейса. У меня это ens3; у тебя может быть eth0 или другое имя. Узнать так:

ip route get 8.8.8.8 | grep -oP 'dev \K\S+'

Подставь свой интерфейс:

iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o ens3 -j MASQUERADE
netfilter-persistent save

Без этого шага клиенты подключаются, но интернет пропадает, т.к. пакеты до сервера доходят, а дальше не уходят. Со стороны выглядит так, будто VPN сломал сеть. На самом деле сеть просто не знает, куда отправить ответы. Мы её просветим.

Шаг 6: nftables

На многих современных Debian по умолчанию фаерволом управляет nftables (через iptables-nft). Цепочка FORWARD в nft имеет политику drop, а правила для интерфейса wg0 контейнер добавляет в iptables-legacy. В итоге пакеты из туннеля приходят на хост, ядро смотрит в nft, а там для wg0 нет разрешения и всё режется. Клиент видит, что сеть пропала, потому что весь трафик ушёл в VPN, а форвард на сервере его дропает.

Лечится добавлением правил в nftables:

nft add rule ip filter FORWARD iifname "wg0" accept
nft add rule ip filter FORWARD oifname "wg0" accept

Чтобы после перезагрузки правила не терялись, заводим сервис, который выполнит их после старта Docker:

Файл /etc/systemd/system/wg0-nft-forward.service:

[Unit]
Description=Allow nftables FORWARD for WireGuard wg0
After=docker.service
PartOf=docker.service

[Service]
Type=oneshot
ExecStartPre=/bin/sleep 2
ExecStart=/usr/sbin/nft add rule ip filter FORWARD iifname "wg0" accept
ExecStart=/usr/sbin/nft add rule ip filter FORWARD oifname "wg0" accept
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable wg0-nft-forward.service

После следующей перезагрузки правила подставятся автоматически. На клиенте после этого включение VPN не должно «убивать» интернет.

Основную работу ты сделал(а😘)

Пример конфига сервера (wg0.conf)

Файл лежит в /root/.amnezia-wg-easy/wg0.conf. Его правит сама панель; ниже пример структуры (ключи и прешейр - твои, не копируй!).

# Note: Do not edit this file directly.
# Your changes will be overwritten!

[Interface]
PrivateKey = <серверный приватный ключ>
Address = 10.8.0.1/24
ListenPort = 51820
PostUp =  iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE; ...
PostDown =  iptables -t nat -D POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE; ...
Jc = 9
Jmin = 50
Jmax = 1000
S1 = 81
S2 = 149
H1 = 1985136747
H2 = 1102862225
H3 = 173874812
H4 = 513709483

# Client: ly-laptop (uuid)
[Peer]
PublicKey = <публичный ключ клиента>
PresharedKey = <preshared key>
AllowedIPs = 10.8.0.2/32

Параметры Jc, Jmin, Jmax, S1, S2, H1–H4 — это как раз обфускация AmneziaWG. Их трогать не нужно, если не копируешь конфиг с нуля вручную.

Клиенты на устройствах

Компьютер (Windows / Mac / Linux)

  1. Скачайте файл конфигурации из панели (иконка скачивания у вашего клиента).
  2. Установите клиент Amnezia (https://amnezia.org) или другой с поддержкой AmneziaWG.
  3. В клиенте: Добавить туннель или Импорт и выберите скачанный файл.
  4. Включите подключение.

Телефон (Android / iPhone)

  1. Установите приложение Amnezia из App Store / Google Play.
  2. В панели нажмите иконку QR-кода у вашего клиента.
  3. В приложении выберите Добавить по QR-коду и отсканируйте код с экрана.
  4. Включите VPN в приложении.

Один конфиг или QR - один клиент. Не передавайте свой конфиг другим. Не будет работать.

Если вас забанили в гугле:

💩 Возможные ошибки и как починить

Панель не открывается (ERR_CONNECTION_REFUSED)

  • Проверь на сервере: ss -tlnp | grep 443 что-то должно слушать.
  • Проверь с сервера: curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:443/ — лучше 200.
  • Если на сервере всё ок, скорее всего режет фаервол хостера или свой ufw/nft: открой 443/tcp снаружи.

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

  • Включён ли IP forwarding: cat /proc/sys/net/ipv4/ip_forward → 1.
  • Есть ли NAT: iptables -t nat -L POSTROUTING -n -v  должна быть строка MASQUERADE для 10.8.0.0/24 в сторону твоего внешнего интерфейса. Интерфейс в PostUp контейнера может быть eth0; если у тебя ens3, то добавь правило вручную и сохрани (см. шаг 5.3).
  • Не режет ли nftables FORWARD для wg0, то см. шаг 6.

Контейнер постоянно перезапускается (Restarting)

Смотри логи: docker logs amnezia-wg-easy. Частая причина «Cannot find device wg0» или «Missing WireGuard (Amnezia VPN) kernel module»: модуль amneziawg не установлен или не загружен на хосте. Установи пакеты из шага 4 и проверь lsmod | grep amneziawg. После обновления ядра может понадобиться перезагрузка, чтобы поднялся новый модуль.

После перезагрузки VPN не поднимается

Проверь: systemctl is-active dockerdocker ps. Контейнер должен быть с --restart unless-stopped. Правила nft для wg0 добавляет сервис wg0-nft-forward.service и он должен быть включён (enable). NAT и INPUT-правила сохраняй через netfilter-persistent save.

Панель открывается по IP, но не по домену

Проверь DNS: getent hosts твой.домен или dig твой.домен должен резолвиться в IP сервера. В панели и в конфигах клиентов WG_HOST лучше указывать домен, тогда при смене IP достаточно поправить DNS.

У меня получилось и ты сможешь, я верю в тебя ❤️

Бонус 1: отчёт по трафику в Telegram

В панели amnezia-wg-easy видна только текущая скорость (KB/s), а не суммарный трафик. Зато на сервере wg show wg0 даёт по каждому пиру строку transfer: X received, Y sent.
Ниже скрипт, который раз в сутки собирает эти данные и шлёт тебе в Telegram список клиентов и объём трафика (суммарно с последнего перезапуска сервера/контейнера).

Что нужно

  1. Создать бота через @BotFather, получить токен.
  2. Написать боту с аккаунта, куда хочешь получать отчёты.
  3. Узнать chat_id: открыть в браузере https://api.telegram.org/bot<ТОКЕН>/getUpdates, в ответе найти "chat":"id": — это chat_id.

Скрипт /root/amnezia-wg-easy/telegram-stats.sh

#!/bin/bash
CONF_DIR="/root/.amnezia-wg-easy"
CONF_FILE="$CONF_DIR/wg0.conf"
ENV_FILE="$CONF_DIR/telegram.env"
CONTAINER_NAME="amnezia-wg-easy"

[[ -f "$ENV_FILE" ]] || { echo "Create $ENV_FILE with TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID"; exit 1; }
source "$ENV_FILE"
[[ -n "$TELEGRAM_BOT_TOKEN" && -n "$TELEGRAM_CHAT_ID" ]] || { echo "Set TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID"; exit 1; }

declare -A PEER_NAMES
current_name=""
while IFS= read -r line; do
  if [[ "$line" =~ ^#\ Client:\ (.+)$ ]]; then current_name="${BASH_REMATCH[1]}"
  elif [[ "$line" =~ ^PublicKey\ =\ (.+)$ ]]; then
    [[ -n "$current_name" ]] && PEER_NAMES["${BASH_REMATCH[1]}"]="$current_name"
    current_name=""
  fi
done < "$CONF_FILE"

RAW=$(docker exec "$CONTAINER_NAME" wg show wg0 2>/dev/null) || exit 1
NL=$'\n'
TEXT="*Трафик по братанам и сестрёнкам:  *${NL}${NL}"
n=1
current_key=""
while IFS= read -r line; do
  if [[ "$line" =~ ^peer:\ (.+)$ ]]; then current_key="${BASH_REMATCH[1]}"
  elif [[ "$line" =~ transfer:\ (.+)\ received,\ (.+)\ sent ]]; then
    rx="${BASH_REMATCH[1]}"; tx="${BASH_REMATCH[2]}"
    name="${PEER_NAMES[$current_key]:-$current_key}"
    name="${name%% (*}"
    TEXT+="${n}. ${name}   ↓ ${rx}  ↑ ${tx}${NL}"
    ((n++))
    current_key=""
  fi
done <<< "$RAW"

if [[ "$TEXT" == *"↓ "* ]]; then
  curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
    -d "chat_id=${TELEGRAM_CHAT_ID}" \
    -d "parse_mode=Markdown" \
    --data-urlencode "text=${TEXT}" \
    -d "disable_web_page_preview=1" >/dev/null
fi

Файл с токеном и chat_id

# /root/.amnezia-wg-easy/telegram.env (chmod 600)
TELEGRAM_BOT_TOKEN=123456:ABC-DEF...
TELEGRAM_CHAT_ID=123456789

Права и запуск по расписанию

chmod +x /root/amnezia-wg-easy/telegram-stats.sh
# Раз в сутки в 09:00
(crontab -l 2>/dev/null | grep -v telegram-stats; echo "0 9 * * * /root/amnezia-wg-easy/telegram-stats.sh >> /var/log/amnezia-telegram-stats.log 2>&1") | crontab -

Ручной запуск: /root/amnezia-wg-easy/telegram-stats.sh. В Telegram придёт сообщение списком: имя клиента (без UUID), принято/отдано в MiB. Удобно смотреть, кто сколько трафика забирает без захода на сервер и без лишней магии.

Как-то так будет

Бонус 2: алерт в Telegram при нехватке RAM

На VPS с парой гигабайт памяти бывает полезно знать, когда свободная RAM падает ниже порога, чтобы не гадать, почему всё вдруг тормозит. Такой скрипт почти не забирает ресурсы.

Что нужно

Тот же Telegram-бот (или другой).

Скрипт /root/ram_alert.sh

#!/bin/bash
# Проверка свободной RAM, уведомление в Telegram при нехватке

BOT_TOKEN="ТВОЙ_ТОКЕН_БОТА"
CHAT_ID="ТВОЙ_CHAT_ID"
MIN_FREE_MB=200
COOLDOWN_FILE="/tmp/ram_alert_cooldown"
COOLDOWN_SEC=3600

# Свободная память в МБ (MemAvailable = free + buffers/cache)
free_mb=$(awk '/MemAvailable/ {printf "%.0f", $2/1024}' /proc/meminfo)

if [ "$free_mb" -ge "$MIN_FREE_MB" ]; then
  exit 0
fi

# Не спамить: не чаще раза в COOLDOWN_SEC
if [ -f "$COOLDOWN_FILE" ]; then
  last=$(stat -c %Y "$COOLDOWN_FILE" 2>/dev/null)
  [ -n "$last" ] && [ $(($(date +%s) - last)) -lt "$COOLDOWN_SEC" ] && exit 0
fi
touch "$COOLDOWN_FILE"

msg="RAM low: ${free_mb} MB free (threshold: ${MIN_FREE_MB} MB)"
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
  -d "chat_id=${CHAT_ID}&text=${msg}" >/dev/null

MIN_FREE_MB - порог в мегабайтах: если свободной памяти меньше, уйдёт алерт. 
COOLDOWN_SEC - не слать повторное уведомление чаще чем раз в этот интервал (3600 = 1 час), чтобы не заспамить при постоянной нехватке RAM.

Права и cron

chmod +x /root/ram_alert.sh
# Проверка раз в 5 минут
crontab -e
# Строка:
*/5 * * * * /root/ram_alert.sh

Проверить, что бот до тебя доходит: отправить тестовое сообщение вручную:

curl -s -X POST "https://api.telegram.org/botТВОЙ_ТОКЕН/sendMessage" \
  -d "chat_id=ТВОЙ_CHAT_ID&text=RAM Alert Bot: все ок, скрипт настроен."

В ответе "ok":true и сообщение пришло в чат.

P.s. в чем сила?

Если что-то устарело или не взлетело - маякни в комментариях или в телеге. Технические статьи имеют свойство прокисать быстрее молока.

Заранее спасибо