Я начал снаружи, как поступил бы пользователь: ввёл имя в браузере телефона на мобильных данных и смотрел, как соединение висит, пока не истекло время. Имя разрешалось, значит DNS был в порядке. Страница проброса портов на домашнем роутере тоже оказалась тупиком, потому что её адрес WAN показывал 100.64.31.42, а это не адрес, на который провайдер маршрутизирует меня из интернета. Это число объясняет весь сбой: линия находится за carrier-grade NAT, поэтому публичный адрес на моей стороне туннеля принадлежит провайдеру. Дальше идёт путь, который я прошёл от той зависшей вкладки до сервиса, который отвечает.

Начни с клиента и двигайся внутрь

Прежде чем менять любое правило, докажи, где обрывается цепочка. На ноутбуке я выполнил dig +short files.example.net и получил адрес VPS, что исключило устаревший DNS на моей машине. Затем curl -v --connect-timeout 5 files.example.net:5022 истёк по времени вместо отказа. Эта разница важна: тайм-аут обычно означает, что пакеты отбрасываются по пути, а отказ означает, что кто-то ответил и сказал нет.

Следующим шагом внутрь был сам VPS. ss -ltnp не показал ничего слушающего на 5022, что и ожидалось до появления правила DNAT. На той же машине wg show (или ip -br a show tun0 для OpenVPN) подтвердил, что домашний peer поднят и имеет свежий handshake. Если peer не работает, никакое правило NAT не поможет, а зелёный свет на странице VPN домашнего роутера ничего не значит.

Подтверди, что домашняя линия действительно за CGNAT

Сравни адрес, который роутер сообщает на интерфейсе WAN, с тем, что видит внешний сервис. Всё в 100.64.0.0/10, то есть от 100.64.x.x до 100.127.x.x, — это диапазон общих адресов из RFC 6598, и из интернета он недоступен. К тому же выводу приходишь, когда роутер утверждает, что у него обычный приватный адрес, а публичная проверка возвращает что-то несвязанное. У линии CGNAT может быть и публичный адрес, действующий только до следующего lease, поэтому проброс порта как будто работает час, а затем тихо умирает.

Некоторые провайдеры действительно выдают настоящий публичный адрес, но блокируют входящие порты 80 и 443 на домашних тарифах. Если твой сервис только веб, а адрес WAN действительно публичный, спроси поддержку, прежде чем строить relay. Если нет, схема ниже применима одинаково к любому порту, и стоит прекратить эксперимент на стороне роутера: транслятор в сети провайдера завершает соединение раньше, чем оно доходит до твоего интерфейса WAN.

Направь имя на адрес VPS

DNS не пересылает трафик, поэтому A-запись сервиса должна содержать публичный адрес VPS. A-запись на 192.168.1.20 разрешается полезно только для клиентов, которые уже внутри VPN; из интернета она разрешается в адрес, который никто не может маршрутизировать. Для административного доступа к самому VPS я держу отдельное имя, чтобы никогда не путать, к какому хосту относится запись.

Если и VPS, и домашняя машина отдают HTTP, дай каждой своё имя и позволь VPS решать, кто отвечает. Свести их в одно имя и надеяться, что прокси выберет правильный backend, — это ровно то, как проводишь целый день, отлаживая не тот хост.

Один порт через DNAT, SSH остаётся на 22

Прямой вариант — NAT назначения на VPS. Включи forwarding для работающего ядра и держи в правиле адрес туннеля домашнего сервера, а не его адрес LAN:

sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A PREROUTING -p tcp -d 198.51.100.10 --dport 5022 -j DNAT --to-destination 10.8.0.2

Оставь SSH к самому VPS на 22 и проброси другой порт, например 5022, на домашнюю машину. Если пробросить 22 на выключенную машину, теряешь единственный путь, который мог бы её починить. Та же логика касается прокси: держи порт управления VPS вне правил relay.

Сделай правила постоянными, когда они заработают. В Debian или Ubuntu apt install iptables-persistent и затем netfilter-persistent save записывают их в /etc/iptables/rules.v4. На хосте с firewalld эквивалент — постоянный проброс порта в зоне, которому перед применением нужен firewall-cmd --reload. Правила, живущие только в памяти, исчезают при первом сервисном перезапуске провайдера, обычно в неудачный час.

Почини обратный путь или избеги необходимости в нём

DNAT переписывает только назначение. Домашний сервер видит соединение с исходного адреса клиента, и если его маршрут по умолчанию указывает на домашний роутер, ответ уходит через домашний WAN, где транслятор провайдера его отбрасывает. Эта асимметрия была вторым сбоем, с которым я столкнулся, после того как правило DNAT выглядело совершенно правильным.

Две правки работают. Дай домашней машине политический маршрут, чтобы трафик из туннеля возвращался через туннель:

ip rule add from 10.8.0.2 table 100
ip route add default dev wg0 table 100

Или позволь VPS переписать источник, чтобы у ответа был адрес, который он может достичь, и позволь conntrack развернуть его на выходе:

iptables -t nat -A POSTROUTING -d 10.8.0.2 -p tcp --dport 5022 -j SNAT --to-source 10.8.0.1

Вариант с SNAT скрывает настоящий адрес клиента от домашнего сервера, поэтому в логах приложения как peer виден 10.8.0.1. Передай исходный адрес на уровне приложения или согласись потерять логирование по клиентам, но реши это осознанно, а не обнаруживай во время инцидента.

Проверь весь путь снаружи

Проверь из сети, которая не является ни VPS, ни домом: телефон на мобильных данных или дешёвый shell в другом месте. nc -vz files.example.net 5022 должен сообщить об успешном соединении, а openssl s_client -connect files.example.net:443 -servername files.example.net должен показать сертификат для публичного имени. Затем проверь security group или облачный фаервол провайдера. Правило DNAT, которое никогда не видит пакет, обычно блокируется на уровень выше хоста.

Наконец, реши, что происходит, когда туннель падает. Правило DNAT продолжает принимать соединения и тихо направляет их в мёртвый туннель, что выглядит точно как сломанный сервис. Проверка состояния на VPS, которая отклоняет порт, пока peer не работает, стоит несколько строк и экономит час догадок. После этого перестань править правила и наблюдай сутки, прежде чем доверять.

Похожие статьи