Ты завёл себе домашний сервер. Развернул пару удобных сервисов: фотохостинг, облако, панель управления, мониторинг. Всё крутится, всё работает, доступ из интернета есть, какая красота скажешь ты.
Но вот только одна мелочь… про защиту-то ты и забыл.
Пока сервер никому не интересен, всё кажется в порядке и ни о чём не беспокоишься. Но стоит оставить хоть одну админку открытой наружу, как на неё придут боты, сканеры и «коллекционеры чужих логинов». Через неделю в логах можно увидеть десятки попыток авторизации, а через месяц вполне реальный взлом.
Так что, если ты хочешь спокойно использовать свой сервер без страха, что кто-то вскроет админку, то пора задуматься о защите. Не о громоздких корпоративных решениях, а о простом и понятном инструменте, который создаёт надёжный частный контур вокруг твоих сервисов.
Именно это и будет делать VPN. Он не просто будет шифровать трафик, а еще определять, кто вообще имеет право попасть к твоему серверу.
Тут все без магии и долгой настройки. Я сам считал, что это не так то просто и нужно обладать определенными навыками на уровне сетевого инженера. На деле же все делается за один вечер, если следовать уже рабочей схеме и не собирать грабли.
Эта статья как раз поможет тебе пройти весь путь без лишней боли.
Ее я уже испытал за тебя, высиживая часами, матерясь на монитор и пытаясь понять, почему «оно не работает, всё же сделано правильно».
Я продумал, как сделать настройку проще и понятнее, исправил собственные ошибки и теперь делюсь готовым, проверенным решением, которое тебе останется только повторить и докрутить под свои сервисы.
📌 Важное уточнение: В этой статье я исхожу из того, что у тебя есть статический IP-адрес (или белый IP от провайдера) и на него настроен домен (например,
home-server.ru). Это стандартная и самая распространённая конфигурация для домашнего сервера с внешним доступом.Если у тебя другая ситуация: DDNS, проброс через NAT провайдера, Cloudflare Tunnel или что-то ещё - тебе нужно будет адаптировать решение под свой случай. Если возникнут сложности, напиши в комментариях или в мой Telegram-канал - разберу твой кейс и, возможно, дополню статью или выпущу отдельный материал.
Содержание
1 Механизм работы
1.1 Схема разделения доступов
1.2 VPN - закрытый вход в частную сеть
1.3 Reverse-proxy - единая точка входа
1.4 Локальный DNS для VPN-клиентов
2 Настройка роутера
3 Настраиваем VPN
3.1 Подготовка: установка
Docker3.2 Запускаем
WireGuardвDocker3.3 Важный нюанс: проблема с одинаковыми подсетями
3.4 Добавление VPN-клиентов
4 Настраиваем DNS-резолвер (
dnsmasq)4.1 Как это работает
4.2 Проверка работы
5 Настраиваем Reverse-proxy
5.1 Вариант A - «Всё в
Docker» (рекомендую)5.2 Вариант B - «
NginxвDocker→ сервис на хосте»5.3 Вариант C - «Всё на хосте (без
Docker)»
6 Устанавливаем VPN-конфиг на устройства
6.1 Где найти конфиги клиентов
6.2 Пример клиентского конфига
6.3 Установка приложения
WireGuard6.4 Импорт конфига на ПК (Windows/macOS/Linux)
6.5 Импорт конфига на мобильные (iOS/Android)
6.6 Проверка подключения
7 Набор быстрых проверок (self-audit)
7.1 Проверка VPN-сервера
7.2 Проверка DNS-резолвера
7.3 Проверка
Nginx7.4 Сквозной тест с клиента
8 Что делать, если не работает
8.1 VPN не подключается
8.2 DNS не резолвит домены
8.3
Nginxотдаёт ошибки8.4 Чеклист диагностики
8.5 Нужна помощь?
9 Итоги
9.1 Что мы построили
9.2 Преимущества решения
9.3 Когда это решение подходит
9.4 Обратная связь
10 Финал
1 Механизм работы
Прежде чем «натягивать» защиту и переходить к конфигам, разберём общую логику.
Публичные сервисы (например, фотогалерея) остаются доступными из интернета по HTTPS - чтобы ими могли пользоваться те, у кого нет конфигурации VPN. При желании их можно в один шаг «завести» за VPN.
Административные панели и всё чувствительное - доступны только через VPN.
Проще говоря:
всё, что нужно друзьям и обычным пользователям - снаружи;
всё, что нужно только тебе - внутри VPN.
Такой подход даёт гибкость в разделении: какие сервисы остаются публичными, а какие - физически недоступны без VPN.
1.1 схема разделения доступов
🌍 ПУБЛИЧНАЯ ЗОНА (Интернет) ──────────────────────────────────────────────────────────────────────── HTTPS :443 UDP :51820 ──────────────────────────────────────────────────────────────────────── │ │ ▼ ▼ ┌────────────────────┐ ┌────────────────────┐ │ РОУТЕР │ │ РОУТЕР │ │ проброс 80, 443 │ │ проброс 51820 (VPN)│ └────────┬───────────┘ └──────────┬─────────┘ │ │ │ ▼ обычный клиент (145.168.21.43) ┌──────────────────────────────────┐ │ │ │ │ │ - WireGuard (10.8.0.0/24) │ ▼ │ - dnsmasq (локальный DNS) │ ┌───────────────────────────────┐ │ │ │ │ │ ▸ резолвит: │ │ - nginx │ │ photos.home-server.ru │ │ │ │ home-server.ru │ │ - photos.home-server.ru │ └────────────────┬─────────────────┘ │ Immich прямой доступ │ │ │ - home-server.ru → │ │ │ Proxmox │ ▼ │ только для (10.8.0.0/24) │ ◀───── VPN-клиент (10.8.0.x) └───────────────┬───────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ - Immich публичный фото-сервис │ │ - Proxmox доступен только из VPN │ └─────────────────────────────────────────────────────┘
Главная идея состоит в том, что для сервисов, которые мы хотим защитить, в nginx будет фильтрация доступа к сервисам по IP клиента: если IP подходит под маску VPN-клиента = пропускаем, не подходит - отвечаем 403.
Чтобы собрать эту схему, нужны три компонента: VPN, reverse-proxy и локал��ный DNS-резолвер.
1.2 VPN - закрытый вход в частную сеть
VPN (в моём случае - WireGuard) создаёт защищённый туннель между твоим устройством и домашней сетью.
Подключившиеся клиенты получают адреса из отдельной подсети - например, 10.8.0.0/24. То есть при подключении по VPN образуется своя «виртуальная» сеть со своими IP.
Только эти IP-адреса имеют право обращаться к приватным сервисам (это контролируется на уровне NGINX и/или firewall).
1.3 Reverse-proxy - единая точка входа
Reverse-proxy (в моём случае - NGINX) принимает входящие запросы на 80/443 и решает, что с ними делать:
отдаёт публичные сервисы «как обычно», без дополнительных фильтров;
ограничивает доступ к приватным доменам (
home-server.ru) только для клиентов из VPN-подсети (allow 10.8.0.0/24; deny all;)
Иными словами, это диспетчер трафика: знает, что куда идёт, и не пропускает лишнего.
1.4 Локальный DNS для VPN-клиентов
В моём случае это dnsmasq. Он:
слушает
wg0(интерфейс VPN) и127.0.0.1,отвечает только клиентам VPN по адресу
10.8.0.1,выдаёт LAN-IP для внутренних доменов, чтобы маршрут до них шёл через туннель, а не в обход.
Пример записей:
home-server.ru → 192.168.31.90 (NGINX) photos.home-server.ru → 192.168.31.90 (NGINX)
На клиентах в конфиге
WireGuardпропишемDNS = 10.8.0.1- тогда резолвинг внутренних имён всегда будет идти через VPN-DNS.
Теперь ты понимаешь, как всё устроено. Дальше идёт практика - настройка каждого компонента по очереди. Не переживай, я старался все пояснять, чтобы даже неопытный пользователь справился с установкой.
2 Настройка роутера
Показывать я буду на примере своей веб панели роутера, она может отличаться от того, что стоит у тебя, но суть ты поймешь.
Детали
Нужно, чтобы IP твоего сервера был постоянным, мы будем использовать его тут и в дальнейшем при настройке конфигураций на сервере.
У меня это выглядит следующим образом:

Здесь я задал постоянный IP - 192.168.31.90
Теперь пробрасываем порты на зарезервированный IP:
51820/UDP - для
WireGuard(VPN-подключения)80/TCP и 443/TCP - для
Nginx(веб-доступ к сервисам)

3 Настраиваем vpn
В этом разделе мы поднимем VPN-сервер на WireGuard в Docker контейнере.
Он создаст частную подсеть 10.8.0.0/24, через которую будут доступны все приватные сервисы.
Дополнительную информацию по сервису ты можешь получить на официальном сайте.
3.1 Подготовка: установка Docker
Для работы WireGuard в контейнере нам нужен Docker и Docker Compose. Если у тебя уже установлен Docker, можешь пропустить этот раздел.
Обновляем систему
sudo apt update sudo apt upgrade -yУстанавливаем зависимости
sudo apt install -y apt-transport-https ca-certificates curl software-properties-common gnupgДобавляем GPG-ключ
Dockercurl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpgДобавляем официальный репозиторий
Dockerecho "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/nullОбновляем индекс пакетов
sudo apt updateУстанавливаем
DockerEngine +DockerCompose pluginsudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginЗапускаем и включаем
Dockersudo systemctl enable docker sudo systemctl start dockerДобавляем пользователя в группу docker Чтобы не писать
sudoперед каждой командойDocker:sudo usermod -aG docker $USERПрименяем изменения групп
newgrp dockerИли перелогинься в систему, чтобы изменения вступили в силу.
3.2 Запускаем WireGuard в Docker
Создаем для удобства директорию
vpn:
mkdir ~/vpn cd ~/vpn
Создаём файл
docker-compose.vpn.yml:
cat << EOF > docker-compose.vpn.yml version: '3.8' services: vpn: image: linuxserver/wireguard container_name: vpn network_mode: "host" cap_add: - NET_ADMIN - SYS_MODULE environment: - PUID=0 - PGID=0 - TZ=Europe/Moscow - SERVERURL=home-server.ru - SERVERPORT=51820 - PEERS=mackbook,ipad - PEERDNS=auto - INTERNAL_SUBNET=10.8.0.0 - ALLOWEDIPS=10.8.0.0/24,192.168.31.90/32 - LOG_CONFS=false volumes: - ./wireguard/config:/config restart: unless-stopped EOF
Разберём конфигурацию подробнее для того, чтобы ты смог сконфигурировать сервис под свой случай.
Параметры, которые тебе с большей вероятностью нужно будет заменить:
Переменные окружения:
environment: - PUID=0 - PGID=0 - TZ=Europe/Moscow - SERVERURL=home-server.ru - SERVERPORT=51820 - PEERS=mackbook,ipad - PEERDNS=auto - INTERNAL_SUBNET=10.8.0.0 - ALLOWEDIPS=10.8.0.0/24,192.168.31.90/32 - LOG_CONFS=false
PUID/PGID
UID и GID пользователя внутри контейнера, от имени которого будут созданы файлы в/config.
В примере используется 0 (root) - просто и универсально. При желании можно завести отдельного пользователя.TZ
Часовой пояс. Нужен для того, чтобы время в логах совпадало с твоим локальным.SERVERURL
Внешний адрес, по которому к тебе будут подключаться клиенты:домен (
home-server.ru),или белый IP сервера. Этот параметр попадает в готовые клиентские конфиги как
Endpoint =home-server.ru:51820.
SERVERPORT
ПортWireGuardна стороне сервера. Здесь51820, но можно выбрать любой свободный UDP-порт.
Этот же порт мы уже пробросили на роутере (см. главу 2).PEERS
Количество клиентов, для которых контейнер сам сгенерирует конфиги, или список имён через запятую. Контейнер сделает автогенерациюwg0.conf+peerX.confи их QR‑кодов.
Формат:PEERS=1 - один клиентpeer1.PEERS=mackbook,ipad- два клиента с такими именами.
PEERDNS
Какой DNS‑сервер будет прописан в конфигурациях клиентов ( DNS = ... ).
Значения:auto (по умолчанию) - клиенты используют DNS самого контейнера, который в свою очередь форвардит запросы туда же, куда ходит основной хост.
Любой IP, например
1.1.1.1или8.8.8.8.
INTERNAL_SUBNET
Базовый адрес подсети VPN.
В примере:10.8.0.0, значит, клиенты будут получать адреса вида10.8.0.x.ALLOWEDIPS
Какие сети клиент будет маршрутизировать через туннель.
В примере:10.8.0.0/24- сама VPN-подсеть (чтобы клиенты видели друг друга и сервер),192.168.31.90/32- конкретный IP сервера с маской/32.
⚠️ Важно: Используем именно
/32(один конкретный хост), а не/24(всю подсеть).
Это гарантирует, что трафик к серверу всегда пойдёт через VPN-туннель, даже когда ты подключён из той же домашней сети - подробности в разделе 3.3 ниже.Весь остальной интернет-трафик (обычные сайты) будет идти напрямую через твоё текущее подключение (Wi-Fi, мобильный интернет), а не через VPN. Это называется split-tunnel режим - он экономит трафик домашнего интернета и даёт максимальную скорость.
LOG_CONFS
Включить или выключить вывод QR‑кодов конфигураций клиентов в лог контейнера.Значения: true или false (по умолчанию false). Я поставил false, конфиги мы потом возьмем из сгенерированных файлов.
Остальные параметры docker-compose.yml
Описание сервиса
services: vpn: image: linuxserver/wireguard container_name: vpnvpn- имя сервиса внутри compose-файла.image- готовый образ от LinuxServer.io сWireGuard+ удобной обвязкой.container_name- как контейнер будет называться вdocker ps, логах и командах.
Сеть и права
network_mode: "host" cap_add: - NET_ADMIN - SYS_MODULE sysctls: - net.ipv4.ip_forward=1network_mode: "host"Контейнер использует сеть хоста, а не отдельную сеть docker(docker-bridge). Это позволяетWireGuardсоздавать интерфейсwg0прямо на хосте и слушать порт51820/udpбез дополнительных порт-мапов.cap_add: NET_ADMINДаёт контейнеру право, чтобы:поднимать интерфейсы,
менять маршруты,
настраивать iptables. Все это нужно для корректной работы
wireguard
cap_add: SYS_MODULEРазрешает загружать модуль ядраwireguard, если он ещё не подгружен в систему.Тома
volumes: - ./wireguard/config:/config./wireguard/config:/config
В эту директорию на хосте будут сохраняться:ключи сервера
конфиги клиентов
базовый
wg0.conf
Это позволяет:
переживать перезапуски / обновления контейнера
удобно копировать
.confна устройства
Политика рестартов
restart: unless-stoppedЭто гарантирует, что контейнер:поднимется после перезагрузки сервера,
перезапустится после ошибок,
не будет стартовать только в одном случае - если его явно остановили (
docker stop vpn).
💡 Совет: Не пугайся количества параметров - в 90% случаев достаточно поменять только SERVERURL и PEERS. Остальное работает "из коробки".
Включаем пересылку пакетов (IP forwarding)
Чтобы VPN-сервер мог маршрутизировать трафик между клиентами и локальной сетью, нужно включить пересылку пакетов:
echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf sudo sysctl -p
Это заставляет сервер работать как маршрутизатор:
принимает пакеты от VPN-клиента (10.8.0.x),
пересылает их в локальную сеть (192.168.31.x),
и обратно - отвечает клиенту через туннель.
Запуск контейнера
Теперь всё готово для запуска VPN-сервера:
docker compose -f docker-compose.vpn.yml up -d
После запуска ты получаешь:
Контейнер
vpnпроверка:docker psДолжен увидеть контейнер
vpnв статусеUp.Интерфейс
wg0на хосте Проверка:ip a show wg0Должен увидеть интерфейс с адресом
10.8.0.1/24в состоянииUP.Конфигурация сервера и клиентов На хосте появилась директория:
ls ./wireguard/configВнутри лежат конфиги
WireGuard.Готовность добавлять клиентов Не нужно вручную генерировать ключи - всё делает скрипт внутри контейнера.
3.3 Важный нюанс: проблема с одинаковыми подсетями

При настройке VPN есть важная особенность, с которой ты можешь столкнуться, если находишься в той же домашней сети, что и сервер.
1 В чём проблема
Параметр AllowedIPs в WireGuard на стороне клиента задаёт маршруты - то есть говорит операционной системе: "вот эти адреса отправляй через VPN-туннель".
Казалось бы, можно указать:
AllowedIPs = 10.8.0.0/24, 192.168.31.0/24
И всё должно работать: VPN-подсеть + вся домашняя сеть. Но вот загвоздка.
Когда ты подключаешься к VPN находясь в той же домашней сети (например, сидишь дома, подключён к своему Wi-Fi с адресом 192.168.31.x), операционная система видит, что адрес 192.168.31.90 (сервер) находится в той же локальной сети, к которой ты уже подключён напрямую.
И тут происходит конфликт приоритетов в таблице маршрутизации:
С одной стороны,
WireGuardсоздаёт маршрут: "трафик к192.168.31.0/24отправляй через туннельwg0"С другой стороны, уже существует локальный маршрут: "трафик к
192.168.31.0/24отправляй черезWi-Fiинтерфейс напрямую"
Локальный маршрут имеет больший приоритет (меньшую метрику), потому что система считает прямое подключение к локальной сети более предпочтительным, чем туннель.
В итоге трафик идёт напрямую, в обход VPN.
2 Как это проявляется
Подключаешься к VPN из домашней сети
DNS через VPN резолвит
home-server.ruв LAN IP192.168.31.90Браузер отправляет запрос на
192.168.31.90Запрос идёт напрямую, минуя туннель
Nginxвидит твой реальный домашний IP (например,192.168.31.150), а не VPN IP (10.8.0.x)Получаешь
403Forbidden, потому чтоNginxразрешает только10.8.0.0/24
3 Почему так происходит
В таблице маршрутизации ОС правила имеют приоритет по длине префикса (маске подсети):
Более конкретный маршрут (
/32,/30) имеет больший приоритетМенее конкретный маршрут (
/24,/16) имеет меньший приоритет
Но когда маски одинаковые (/24 через Wi-Fi vs /24 через VPN), система смотрит на метрику маршрута - и локальное подключение обычно выигрывает.
4 Решение
Нужно добавить более специфичный маршрут - указать конкретный IP сервера с маской /32. В маршрутизации более конкретный маршрут всегда имеет приоритет над общим, независимо от метрики.
Поэтому вместо:
AllowedIPs = 10.8.0.0/24, 192.168.31.0/24
Используем:
AllowedIPs = 10.8.0.0/24, 192.168.31.90/32
Где 192.168.31.90 - это IP твоего сервера (на котором крутится Nginx иWireGuard).
Теперь:
Маршрут к
192.168.31.90/32(через VPN) более конкретен, чем192.168.31.0/24(через Wi-Fi)Трафик к конкретному IP сервера
192.168.31.90гарантированно пойдёт через туннельVPN-клиент всегда будет приходить с адреса
10.8.0.xNginxпропустит запрос
5 Когда эта проблема НЕ проявляется
Если ты подключаешься к VPN из другой сети (например, с мобильного интернета, из кафе, с работы), там ��одсеть другая (скажем 172.16.x.x), и конфликта нет - трафик нормально идёт через туннель даже с AllowedIPs = 10.8.0.0/24, 192.168.31.0/24.
Но лучше сразу использовать /32 - это универсально работает везде, независимо от того, откуда ты подключаешься.
📖 Из личного опыта: Я потратил часа 4 на понимание этой проблемы. Перепроверил все конфиги, отследил все хождения запросов, искал ошибку везде. В итоге нашёл информацию об этом нюансе в интернете - оказалось, всё дело в приоритетах маршрутизации. Теперь ты не потратишь эти 4 часа!
3.4 Добавление VPN-клиентов
Чтобы добавить больше узлов/клиентов, необходимо увеличить PEERS значение в нашем docker-compose.vpn.yml или добавить больше элементов в список и пересоздать контейнер.
docker compose -f docker-compose.vpn.yml down docker compose -f docker-compose.vpn.yml up -d
🎉 Отлично! VPN-сервер запущен и готов к работе. Но клиентам ещё нужно научиться обращаться к сервисам по доменам вместо IP. Для этого настроим DNS.
4 Настраиваем DNS-резолвер (dnsmasq)
Чтобы клиенты VPN могли обращаться к сервисам по доменам (home-server.ru, photos.home-server.ru и т.п.), добавим лёгкий DNS-сервер dnsmasq.
Установка
Устанавливаем
dnsmasqsudo apt install -y dnsmasqДобавляем сервис в автозапуск при старте системы:
sudo systemctl enable dnsmasqСоздаем файл
dnsmasq.confв директорииvpn:cd ~/vpn cat << EOF > dnsmasq.conf # слушаем VPN и локально listen-address=10.8.0.1 listen-address=127.0.0.1 # интерфейс VPN interface=wg0 bind-interfaces # внутренние домены → LAN IP reverse-proxy address=/home-server.ru/192.168.31.90 address=/photos.home-server.ru/192.168.31.90 EOFСоздаем символическую ссылку в
/etc/dnsmasq.d:sudo ln -s $HOME/vpn/dnsmasq.conf /etc/dnsmasq.d/vpn.confПерезапустим
dnsmasqsudo systemctl restart dnsmasq
Как это работает
Все клиенты VPN будут использовать DNS
10.8.0.1.При запросе
home-server.ru-dnsmasqвернёт LAN IP сервера192.168.31.90(где установленNginx).Nginxувидит клиента как10.8.0.xи разрешит доступ (поallow 10.8.0.0/24).
Проверка работы
Проверяем, что
dnsmasqзапущен и слушает10.8.0.1:53:sudo ss -tulpn | grep ':53'Проверяем, что домены резолвятся в нужный IP:
dig @10.8.0.1 home-server.ru dig @10.8.0.1 photos.home-server.ruВ секции ANSWER должен быть адрес
192.168.31.90
Хорошо, DNS настроен! Осталось настроить Nginx - и защита будет полной. Это последний технический шаг перед подключением устройств.
5 Настраиваем Reverse-proxy
В этом разделе я буду показывать всё на примере nginx - одного из самых популярных HTTP‑серверов, который часто используется в качестве reverse‑proxy.
Подробнее с возможностями и конфигурацией nginx можно ознакомиться в официальной документации.
Мы не будем подробно рассматривать настройку поддержки TLS в nginx, поск��льку это выходит за рамки текущей статьи. Тем не менее, настоятельно рекомендую добавить её. Для этого достаточно настроить Certbot, что несложно и хорошо документировано. То, как это настроить, хорошо поясняется на сайте проекта.
5.1 Вариант A - «Всё в Docker» (рекомендую)
Этот вариант подразумевает, что и Nginx, и сами сервисы живут в Docker и общаются друг с другом по внутренней сети Docker, без лишних пробросов портов и танцев с IP‑адресами.Nginx смотрит наружу (80/443), а внутри проксирует запросы в другие контейнеры по их именам внутри одной сети edge_net.
docker-compose.nginx.yml
version: '3.8' services: nginx: image: nginx:alpine container_name: nginx ports: - "80:80" - "443:443" networks: - edge_net volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro networks: edge_net: external: true
docker-compose.app.yml
version: '3.8' services: myapp: image: myapp:latest container_name: nginx expose: - "9000" # порт доступен только внутри сети edge_net networks: - edge_net networks: edge_net: external: true
конфигурация nginx.conf
events {} http { upstream myapp { server myapp:9000; } server { listen 80; server_name home-server.ru; # доступ только из VPN allow 10.8.0.0/24; deny all; location / { proxy_pass http://myapp; } } }
Плюсы подхода
- Всё изолировано внутри Docker: порты приложений не торчат на хост, наружу выходят только 80/443 с nginx.
- Упрощённый DNS: в proxy_pass и upstream используем имена сервисов ( myapp ), Docker сам резолвит их в IP.
Минусы подхода
- Требует «докеризовать» приложение.
- Отладка иногда чуть менее наглядна: нужно помнить про внутреннюю сеть Docker и её DNS, а не просто стучаться на localhost:port.
как добавить в текущую реализацию
Создаем docker сеть для nginx и сервисов:
docker network create edge_net
Создаем для удобства директорию nginx:
mkdir ~/nginx cd ~/nginx
Создаем файл nginx.conf:
cat << EOF > nginx.conf events {} http { upstream myapp { server myapp:9000; } server { listen 80; server_name home-server.ru; allow 10.8.0.0/24; deny all; location / { proxy_pass http://myapp; } } } EOF
Создаём файл docker-compose.nginx.yml:
cat << EOF > docker-compose.nginx.yml version: '3.8' services: nginx: image: nginx:alpine container_name: nginx ports: - "80:80" - "443:443" networks: - edge_net volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro networks: edge_net: external: true EOF
Не забудьте добавить свой docker сервис в сеть edge_net
Запускаем nginx:
docker compose -f docker-compose.nginx.yml up -d
5.2 Вариант B - «Nginx в Docker → сервис на хосте»
Этот вариант нужен, когда приложение крутится на хосте, у него свой порт (например, 127.0.0.1:9000 ), и ты не хочешь/не можешь его перенести в Docker.
Здесь мы просто запускаем nginx в контейнере и даём ему доступ к сети хоста.docker-compose.nginx.yml
version: '3.8' services: nginx: image: nginx:alpine container_name: nginx network_mode: host # контейнер использует сеть хоста volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro restart: unless-stopped
nginx.conf
events {} http { upstream myapp_host { server 127.0.0.1:9000; # существующий сервис на хосте } server { listen 80; server_name home-server.ru; # доступ только из VPN allow 10.8.0.0/24; deny all; location / { proxy_pass http://myapp\\\_host; } } }
Плюсы подхода
- Не нужно переносить существующий сервис в Docker - он остаётся как есть, ты добавляешь только тонкий Nginx‑слой поверх.
- Простая схема отладки: бекенд по‑прежнему доступен на 127.0.0.1:9000 , Nginx на 80/443 того же хоста.
- Хорошо подходит для «старых» или тяжёлых сервисов, где контейнеризация откладывается на потом.
Минусы подхода
- Если на хосте уже есть другой веб‑сервер на 80/443, придётся либо переносить его, либо разруливать конфликты портов.
- В отличие от варианта «всё в Docker», приложение всё ещё «торчит» на хосте (хотя только на 127.0.0.1 ), и часть сетевой логики остаётся вне Docker.
как добавить в текущую реализацию
Создаем для удобства директорию nginx:
mkdir ~/nginx cd ~/nginx
Создаем файл nginx.conf:
cat << EOF > nginx.conf events {} http { upstream myapp_host { server 127.0.0.1:9000; } server { listen 80; server_name home-server.ru; allow 10.8.0.0/24; deny all; location / { proxy_pass http://myapp\\\\_host; } } } EOF
Создаём файл docker-compose.nginx.yml:
cat << EOF > docker-compose.nginx.yml version: '3.8' services: nginx: image: nginx:alpine container_name: nginx network_mode: host volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro restart: unless-stopped EOF
Запускаем nginx:
docker compose -f docker-compose.nginx.yml up -d
5.3 Вариант C - «Всё на хосте (без Docker)»
Этот вариант для максимально простой схемы. Все компоненты, приложения и nginx установлены прямо на хосте, без контейнеров.
Плюсы подхода
- Минимум частей: ничего не нужно устанавливать и обновлять в контейнерах, только пакеты ОС.
- Хорош для небольшого домашнего сервера или одной‑двух служб, когда Docker избыточен.
Минусы подхода
- Нет изоляции приложений: все процессы делят одну систему (библиотеки, окружение, зависимости).
- Развёртывание и обновление сложнее повторять: нет декларативного описания в compose‑файле, всё завязано на состояние хоста.
- При росте числа сервисов конфигурация на голом хосте начинает быстро обрастать «зоопарком» юнитов, пакетов и ручных настроек.
как добавить в текущую реализацию
Установите nginx и включите его в автозапуск:
sudo apt update sudo apt install nginx -y sudo systemctl enable nginx
Отключаем дефолтный сайт Ubuntu, чтобы он не мешал нашему конфигу:
sudo rm /etc/nginx/sites-enabled/default
Создаем конфиг для нашего домена:
sudo tee /etc/nginx/sites-available/home-server.ru >/dev/null << 'EOF' server { listen 80; server_name home-server.ru; allow 10.8.0.0/24; deny all; location / { proxy_pass http://127.0.0.1:9000; } } EOF
Включаем сайт и проверяем конфигурацию:
sudo ln -s /etc/nginx/sites-available/home-server.ru /etc/nginx/sites-enabled/home-server.ru sudo nginx -t
Перезапускаем nginx, чтобы применить изменения:
sudo systemctl restart nginx
Вся инфраструктура готова! Теперь самое приятное - подключить свои устройства к VPN и проверить, что всё работает.
6 Устанавливаем VPN-конфиг на устройства
На этом этапе у нас уже есть:
работающий сервер
WireGuardв контейнереvpn;сгенерированные конфиги клиентов в
./wireguard/config.
Осталось забрать нужный .conf на устройство и импортировать его в официальное приложение WireGuard.
6.1 Где найти конфиги клиентов
Конфиги были автоматически сгенерированы при первом запуске контейнера WireGuard.
Заходим в директорию с конфигами:
cd ~/vpn/wireguard/config/ ls
В консоли ты увидишь директории с префиксом peer_ - это конфиги для VPN-клиентов. В моём случае это peer_mackbook и peer_ipad.
Заходим в директорию нужного клиента:
cd peer_mackbook ls
Здесь лежат два файла:
peer_mackbook.conf- конфигурационный файл для импортаpeer_mackbook.png- QR-код для быстрого импорта на мобильные устройства
6.2 Пример клиентского конфига
Выведем в консоль содержимое конфига:
cat peer_mackbook.conf
Пример того, что увидишь:
[Interface] Address = 10.8.0.2/32 PrivateKey = <приватный ключ клиента> DNS = 10.8.0.1 [Peer] PublicKey = <публичный ключ сервера> PresharedKey = <общий ключ> Endpoint = home-server.ru:51820 AllowedIPs = 10.8.0.0/24, 192.168.31.90/32 PersistentKeepalive = 25
Разбор параметров:
Секция [Interface] (настройки клиента):
Address = 10.8.0.2/32- IP-адрес клиента в VPN-сети (каждый клиент получает свой уникальный адрес)PrivateKey- приватный ключ клиента (генерируется автоматически, никому не показывай)DNS = 10.8.0.1- DNS-сервер для резолвинга внутренних доменов (нашdnsmasq) Секция[Peer](настройки сервера):PublicKey- публичный ключ сервераWireGuardPresharedKey- дополнительный общий ключ для усиления безопасности (опционально)Endpoint =home-server.ru:51820- адрес и порт сервера для подключенияAllowedIPs = 10.8.0.0/24, 192.168.31.90/32- какие сети маршрутизировать через VPN:10.8.0.0/24- VPN-подсеть (для связи между клиентами и сервером)192.168.31.90/32- IP сервера в локальной сети (почему/32- см. раздел 3.3)
PersistentKeepalive = 25- отправлять keepalive-пакеты каждые 25 секунд
6.3 Установка приложения WireGuard
Перейди на официальный сайт WireGuard и скачай приложение для своей платформы
6.4 Импорт конфига на ПК (Windows/macOS/Linux)
Скопируй файл конфига с сервера на свой компьютер:
Вариант A - через SCP:
scp user@192.168.31.90:~/vpn/wireguard/config/peer_ipad/peer_ipad.conf ~/Downloads/
Вариант B - вручную:
Выведи содержимое:
cat ~/vpn/wireguard/config/peer_ipad/peer_ipad.confСкопируй весь текст
Создай файл
peer_ipad.confна своём компьютереВставь туда скопированный текст
Передай файл с компьютера на мобильное устройство:
Для iOS/macOS: используй AirDrop
Для Android: через USB, Bluetooth или облако (Google Drive, Dropbox)
Универсально: через мессенджер (Telegram "Избранное", отправь файл самому себе)
Импортируй конфиг в приложение WireGuard:
Открой приложение
WireGuardна телефонеНажми "+" → "Create from file or archive"
Выбери файл
peer_ipad.confКонфиг импортируется в приложение
Активируй подключение:
Включи переключатель напротив туннеля
Статус должен измениться на "Active"
6.5 Импорт конфига на мобильные (iOS/Android)
Скопируй файл конфига с сервера на свой компьютер:
Вариант A - через SCP:
scp user@192.168.31.90:~/vpn/wireguard/config/peer_ipad/peer_ipad.conf ~/Downloads/
Вариант B - вручную:
Выведи содержимое:
cat ~/vpn/wireguard/config/peer_ipad/peer_ipad.confСкопируй весь текст
Создай файл
peer_ipad.confна своём компьютереВставь туда скопированный текст
Передай файл с компьютера на мобильное устройство:
Для iOS/macOS: используй AirDrop
Для Android: через USB, Bluetooth или облако (Google Drive, Dropbox)
Универсально: через мессенджер (Telegram "Избранное", отправь файл самому себе)
Импортируй конфиг в приложение WireGuard:
Открой приложение
WireGuardна телефонеНажми "+" → "Create from file or archive"
Выбери файл
peer_ipad.confКонфиг импортируется в приложение
Активируй подключение:
Включи переключатель напротив туннеля
Статус должен измениться на "Active"
6.6 Проверка подключения
После активации VPN проверь доступ к защищённому сервису.
Проверь доступ с включённым VPN:
Открой браузер и введи:
http://home-server.ru
Должен открыться твой сервис.
Если видишь
403Forbidden - что-то не так с маршрутизацией или конфигомNginx(см. главу 8).Если страница не загружается - проверь DNS и маршруты (см. главу 8).
Проверь, что блокировка работает БЕЗ VPN: Отключи VPN в приложенииWireGuard и попробуй снова открыть http://home-server.ru. Теперь должна быть ошибка подключения или 403 Forbidden - это значит, что защита работает, и без VPN доступа нет.
🎉 Поздравляю! VPN настроен и работает. Переходим к финальной проверке всей системы.
7 Набор быстрых проверок (self-audit)
Перед тем как считать настройку законченной, пройдём по ключевым точкам и убедимся, что всё работает корректно.
7.1 Проверка VPN-сервера
Убедимся, что контейнер WireGuard запущен и интерфейс поднят.
Проверяем статус контейнера:
docker ps | grep vpnОжидаем увидеть контейнер
vpnв статусеUp.Проверяем интерфейс
wg0на хосте:ip a show wg0Должен быть виден интерфейс с адресом
10.8.0.1/24в состоянииUP.Смотрим подключённых клиентов:
docker exec vpn wg showЕсли клиенты подключены, увидите их публичные ключи и последнее время handshake. Если клиенты ещё не подключались, список peers будет пустым - это нормально.
7.2 Проверка DNS-резолвера
Проверим, что dnsmasq слушает на VPN-интерфейсе и корректно резолвит внутренние домены.
Проверяем, что
dnsmasqзапущен и слушает порт 53:sudo ss -tulpn | grep ':53'Должны увидеть процесс
dnsmasq, слушающий на10.8.0.1:53и127.0.0.1:53.Тестируем резолвинг (с сервера):
dig @10.8.0.1 home-server.ru dig @10.8.0.1 photos.home-server.ruВ секции ANSWER должен быть адрес
192.168.31.90(LAN IP сервера). Если домены не резолвятся, проверьте записи в~/vpn/dnsmasq.confи перезапуститеdnsmasq:sudo systemctl restart dnsmasq
7.3 Проверка Nginx
Убедимся, что Nginx запущен и правильно фильтрует доступ по IP.
Для
Docker-варианта:docker ps | grep nginxОжидаем увидеть контейнер с именем
nginxв статусеUp.Для установки на хосте:
sudo systemctl status nginxОжидаем увидеть
active (running)в выводе команды.Тестируем блокировку доступа БЕЗ VPN:
curl -I http://home-server.ruОжидаем
403Forbidden - это значит, чтоNginxкорректно блокирует доступ к защищённым доменам.
7.4 Сквозной тест с клиента
Финальная проверка всей цепочки с клиентского устройства (компьютер, телефон, планшет).
Подключаемся к VPN:
Открываем приложение
WireGuardна твоём устройствеАктивируем подключение к серверу
Должен появиться статус "Connected" или "Активно"
Проверяем доступ к защищённому домену:
Открываем браузер
Вводим в адресной строке:
http://home-server.ruДолжна открыться целевая страница
Если всё работает - поздравляем, защита настроена!
Проверяем блокировку без VPN:
Отключаем VPN в приложении
WireGuardОбновляем страницу в браузере или пробуем зайти снова на
http://home-server.ruДолжны получить ошибку подключения (браузер не может подключиться к сайту) или
403
Это подтверждает, что без VPN доступ действительно закрыт.
8 Что делать, если не работает
Если что-то пошло не так, не паникуй. Большинство проблем решаются за несколько минут, если знать, куда смотреть.
Добро пожаловать в раздел отладки! Если ты здесь, значит что-то пошло не так. Не переживай - 9 из 10 проблем решаются проверкой логов и перечитыванием конфигов. Разберём самые частые случаи.
8.1 VPN не подключается
Симптомы:
Приложение
WireGuardпоказывает "Inactive" или "Handshake failed"Подключение зависает на "Activating..."
Сразу после активации происходит отключение
Что проверить:
1 Порт 51820/UDP открыт на роутере?
Проверь настройки проброса портов на роутере (см. главу 2). Убедись, что:
Порт 51820 UDP (не TCP!) пробросил на IP сервера
IP сервера совпадает с тем, что указан в пробросе
2 Контейнер WireGuard запущен?На сервере про��ерь статус:
docker ps | grep vpn
Должен увидеть контейнер vpn в статусе Up. Если его нет:
cd ~/vpn docker compose -f docker-compose.vpn.yml up -d
3 Интерфейс wg0 поднят?
Проверь на сервере:
ip a show wg0
Должен увидеть интерфейс с адресом 10.8.0.1/24 в состоянии UP.
Если интерфейса нет - перезапусти контейнер:
docker restart vpn
4 Firewall на сервере не блокирует порт?
Проверь, есть ли активный firewall:
sudo ufw status
Если активен - добавь правило для WireGuard:
sudo ufw allow 51820/udp sudo ufw reload
5 Смотрим логи WireGuard
Логи контейнера покажут, в чём проблема:
docker logs vpn --tail 50
8.2 DNS не резолвит домены
Симптомы:
VPN подключается, но
home-server.ruне открываетсяБраузер говорит "DNS_PROBE_FINISHED_NXDOMAIN"
pinghome-server.ruне находит хост
Что проверить:
1 DNS указан в клиентском конфиге?
Открой файл конфига на устройстве (в приложении WireGuard → Edit):
[Interface] DNS = 10.8.0.1
Параметр DNS должен быть равен 10.8.0.1. Если его нет - добавь вручную и сохрани.
2 dnsmasq запущен на сервере?
Проверь статус:
sudo systemctl status dnsmasq
Должен быть active (running). Если нет:
sudo systemctl start dnsmasq sudo systemctl enable dnsmasq
3 dnsmasq слушает на 10.8.0.1?
Проверь:
sudo ss -tulpn | grep ':53'
Должен увидеть dnsmasq на адресах 10.8.0.1:53 и 127.0.0.1:53.
Если не слушает на 10.8.0.1 - проверь конфиг ~/vpn/dnsmasq.conf:
cat ~/vpn/dnsmasq.conf
Должны быть строки:
listen-address=10.8.0.1 listen-address=127.0.0.1 interface=wg0
Если конфиг неправильный, исправь и перезапусти:
sudo systemctl restart dnsmasq
4 Домены прописаны в dnsmasq?
Проверь:
grep 'address=' ~/vpn/dnsmasq.conf
Должен увидеть:
address=/home-server.ru/192.168.31.90 address=/photos.home-server.ru/192.168.31.90
Если нет - добавь вручную (см. главу 4).
5 Тестируем резолвинг с сервера
На сервере выполни:
dig @10.8.0.1 home-server.ru
В секции ANSWER должен быть IP 192.168.31.90. Если нет - проблема в конфиге dnsmasq.
6 Тестируем резолвинг с клиента
На клиенте (с активным VPN):
macOS/Linux:
nslookup home-server.ru
Windows (PowerShell):
nslookup home-server.ru
Должен вернуть 192.168.31.90. Если возвращает другой IP или ошибку - DNS клиента не использует VPN.
8.3 Nginx отдаёт ошибки
1 Forbidden
Причина: Nginx видит IP клиента, который не входит в разрешённую подсеть 10.8.0.0/24.
Что проверить:
Клиент действительно получает IP из VPN?
На клиенте с активным VPN:ip addr show wg0 # macOS/LinuxДолжен увидеть адрес типа
10.8.0.2/32.Трафик идёт через туннель?
Проблема с маршрутизацией (см. раздел 3.3). ПроверьAllowedIPsв клиентском конфиге:AllowedIPs = 10.8.0.0/24, 192.168.31.90/32IP сервера должен быть с маской
/32.Nginxправильно настроен?
Проверь конфигNginx:# Для Docker-варианта docker exec nginx cat /etc/nginx/nginx.conf# Для варианта на хосте sudo nginx -T | grep -A 10 "server_name home-server.ru"Должен увидеть:
allow 10.8.0.0/24; deny all;Смотрим логи
Nginx:# Docker-вариант docker logs nginx --tail 50 # Хост-вариант sudo tail -f /var/log/nginx/error.logИщи строки с IP клиента и причиной отказа.
2 Bad Gateway
Причина: Nginx не может достучаться до бэкенд-сервиса.
Что проверить:
Сервис запущен?
ДляDocker:docker ps | grep <имя_сервиса>Для процесса на хосте:
sudo systemctl status <имя_сервиса>Порт бэкенда открыт?
Проверь, слушает ли сервис на нужном порту:sudo ss -tulpn | grep <порт>Nginxправильно настроен?
Проверьproxy_passв конфиге:proxy_pass http://127.0.0.1:9000; # или имя контейнераДля
Docker-сети должно быть имя контейнера (http://myapp:9000).
3 Gateway Timeout
Причина: Сервис отвечает слишком долго (более 60 секунд).
Решение: Увеличь таймаут вNginx:
proxy_read_timeout 300; proxy_connect_timeout 300;
8.4 Чеклист диагностики
Если не знаешь, с чего начать - проходи по этому списку сверху вниз:
Проверка | Команда | Что должно быть |
|---|---|---|
Контейнер VPN запущен |
|
|
Интерфейс |
|
|
Порт 51820 открыт на роутере | Проверь настройки роутера | Проброс на IP сервера |
|
|
|
|
|
|
Домены прописаны |
|
|
DNS резолвится |
|
|
|
|
|
|
|
|
Клиент получил IP | В приложении |
|
DNS клиента настроен | Конфиг клиента |
|
8.5 Нужна помощь?
Если ты прошёл весь чеклист, но проблема не решилась:
Перечитай раздел, в котором застрял - возможно, пропустил важную деталь
Собери информацию для диагностики:
Вывод
docker psВывод
docker logs vpn --tail 100Вывод
sudo systemctl status dnsmasqСкриншот настроек VPN-клиента
Напиши в комментариях с описанием проблемы и собранной информацией
Загляни в мой Telegram-канал - там я разберу твой кейс и по возможности помогу с проблемой.
Помни: 90% проблем решаются внимательным чтением логов и проверкой конфигов. Не спеши, всё получится!
9 Итоги
9.1 Что мы построили

🎉 Поздравляю! Ты прошёл путь от открытого сервера до защищённой инфраструктуры с VPN-доступом.
Вот что теперь у тебя есть:
Защищённый туннель
WireGuard- современный VPN-сервер с автоматической генерацией конфигов клиентовВнутренний DNS-резолвер
dnsmasq- позволяет обращаться к сервисам по понятным доменам вместо IPReverse-proxy
Nginx- единая точка входа, которая фильтрует трафик и разделяет доступГибкая схема доступа - публичные сервисы остаются доступными всем, приватные защищены VPN
Всё это работает вместе как единая система: VPN создаёт частную сеть, DNS резолвит внутренние домены, а Nginx пропускает только авторизованных клиентов.
9.2 Преимущества решения
Безопасность:
Административные панели физически недоступны без VPN - никакие боты и сканеры их не достанут
Современное шифрование
WireGuard(ChaCha20, Poly1305) защищает трафик от перехватаКлючи генерируются автоматически, минимум ручной работы = меньше ошибок
Простота:
Вся настройка делается за один вечер, даже если ты не сетевой инженер
Docker-контейнеры упрощают развёртывание и обновлениеКонфиги клиентов генерируются автоматически - просто скопируй и импортируй
Гибкость:
Публичные сервисы (фотогалерея) остаются доступными всем
Приватные сервисы (
Proxmox, мониторинг) защищены VPNЛегко добавлять новых клиентов и сервисы
9.3 Когда это решение подходит
✅ Используй эту схему, если:
У тебя есть статический IP или белый IP от провайдера
Нужен защищённый доступ к домашним сервисам (
Proxmox, NAS, камеры и т.п.)Хочешь разделить публичные и приватные сервисы
Количество клиентов - до 10-20 устройств (семья, друзья)
Готов потратить пару часов на настройку ради спокойствия
❌ НЕ подходит, если:
Нет статического IP и не хочешь настраивать DDNS (хотя это решаемо)
Нужна корпоративная VPN для большой компании (используй коммерческие решения)
Требуется двухфакторная аутентификация (
WireGuardработает только по ключам)
9.4 Обратная связь
Поделись результатом:
Напиши в комментариях, получилось ли настроить защиту с первого раза
Поделись статьёй с теми, кто тоже держит домашний сервер - они скажут спасибо
Больше материалов и то, чем занимаюсь я, публикую в своём Telegram-канале. Подписывайся, если хочешь держать руку на пульсе!
Если эта статья была полезна, жду твоего фидбека и комментариев.
Возможно я дополню статью твоим кейсом и упоминанием или найду идеи на новые статьи. Буду рад любой форме обратной связи!
10 Финал
Если ты дочитал до конца, значит, тот самый «ненадёжно открытый сервер из вступления» у тебя превратился в аккуратно закрытый контур с VPN. Поблагодари себя за проделанную работу и спи спокойно - за сервисы теперь можно не переживать.
*хотя абсолютной безопасности не существует.
