mysurik.ru

SSH туннели: как я пробрасывал порты через NAT и не сходил с ума

SSH туннелирование через NAT

Зачем мне понадобился SSH туннель

У меня есть сервер дома за NAT провайдера. К нему нет прямого доступа из интернета — только через внутреннюю сеть. А мне иногда нужно зайти на домашний сервер с работы.

Пробросить порты на роутере нельзя — провайдер выдаёт серый IP. Решение: SSH туннель через промежуточный VPS.

Честно говоря, я не сразу до этого дошёл. Сначала пытался настроить DynDNS, открыть порты — провайдер их резал на входе. Потом пробовал ZeroTier — штука крутая, но на работе корпоративный фаервол режет UDP. А SSH работает всегда, потому что 22 порт обычно открыт.

Как это работает

У меня есть дешёвый VPS за границей за 3$ в месяц. Домашний сервер сам устанавливает SSH-соединение к VPS и пробрасывает порты. Я подключаюсь к VPS, а он перенаправляет меня на домашний сервер.

Команда на домашнем сервере:

ssh -R 2222:localhost:22 user@vps

Флаг -R говорит SSH, мол, слушай на удалённой стороне порт 2222 и всё, что туда придёт, гони на localhost:22 моего компа. Я иногда путаю -R и -L, и однажды пробросил наоборот — открыл доступ к VPS с домашнего сервера. Толку ноль.

Теперь, когда я подключаюсь к VPS на порт 2222, меня перенаправляет на SSH домашнего сервера. Я захожу с ноутбука:

ssh -p 2222 user@vps

И оказываюсь дома.

Тут хитрость: я на VPS создал отдельного пользователя tunnel с шеллом /usr/sbin/nologin. Ему только разрешён Reverse Port Forwarding через опцию PermitOpen в authorized_keys. Если злоумышленник как-то получит ключ — он даже shell запустить не сможет.

Что ещё я пробрасываю

Кроме SSH, я пробросил веб-морду Proxmox (порт 8006) и админку Home Assistant (8123). Теперь могу управлять домашними устройствами из любого места, даже если я в командировке.

Для веб-интерфейсов это работает так:

ssh -L 8080:localhost:8006 user@vps

Открываю в браузере http://localhost:8080 — и я в админке Proxmox.

Тут важный момент: -R и -L — это разные звери. -L ты открываешь порт на своей машине и ходишь через VPS внутрь. А -R наоборот — выставляешь свой порт наружу. Я для Proxmox использую -R, потому что хочу заходить с любого компа, а не только с того, где настроен туннель.

Автоматизация туннеля

SSH соединение может рваться. Чтобы оно переподнималось автоматически, я настроил systemd сервис на домашнем сервере, который держит туннель постоянно.

Создал /etc/systemd/system/ssh-tunnel.service:

[Unit]
Description=SSH Tunnel to VPS
After=network.target

[Service]
Type=simple
User=tunnel
ExecStart=/usr/bin/ssh -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o ExitOnForwardFailure=yes -N -R 2222:localhost:22 -R 8006:localhost:8006 -R 8123:localhost:8123 tunnel@vps
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

ServerAliveInterval=30 — каждые 30 секунд SSH шлёт keepalive. Если три пакета пропало — перезапуск. RestartSec=10 — ждать 10 секунд перед реконнектом. Работает месяцами без моего вмешательства.

Я раньше не знал про -N и -T. Флаг -N означает «не запускать shell», -T — «не выделять псевдотерминал». Без них SSH при подключении выделял лишние ресурсы, и через неделю соединение отваливалось с ошибкой.

Кстати, autossh — штука популярная. Она сама мониторит соединение и перезапускает. Но я предпочёл systemd — он родной, логи пишет в journald, и я вижу, когда и почему туннель падал:

journalctl -u ssh-tunnel.service --since yesterday

Один раз упал провайдер — дёрнули оптику на районе. Через 15 минут интернет появился, и systemd сам поднял туннель. Я узнал об этом только когда зашёл в логи через неделю.

SSH-ключи и мультиплексирование

Я сгенерировал отдельный ключ для туннеля — без пароля. Да, без пароля, потому что сервер сам переподключается, и никто не введёт пароль после ребута. Хранится ключ в /etc/ssh/tunnel_key с правами 600.

На VPS в authorized_keys этого пользователя я добавил ограничения:

command="/bin/false",no-agent-forwarding,no-X11-forwarding,no-pty,permitlisten="localhost:2222,localhost:8006,localhost:8123" ssh-ed25519 AAA... tunnel-key

Разрешил только слушать определённые порты. Никаких shell, никакого перенаправления агента. Даже если кто-то стырит ключ — сделать с ним ничего не сможет.

Ещё я включил мультиплексирование SSH — ControlMaster auto. Это позволяет не плодить десятки TCP-соединений для каждого проброшенного порта. Одно соединение — все туннели внутри него.

В ~/.ssh/config прописал:

Host vps-tunnel
HostName vps
User tunnel
IdentityFile /etc/ssh/tunnel_key
ControlMaster auto
ControlPath ~/.ssh/controlmasters/%r@%h:%p
ControlPersist 10m

Теперь systemd сервис использует Host vps-tunnel. Если я захочу открыть ещё один туннель вручную — он подцепится к существующему соединению, не создавая нового. Экономия ресурсов и меньше нагрузки на VPS.

Проблема с NAT и TCP keepalive

C домашним NAT всё сложно. Провайдер режет неактивные TCP-соединения через 5 минут. Я это выяснил, когда туннель стабильно отваливался каждые 5 минут ровно.

Решение — ClientAliveInterval и ServerAliveInterval на клиенте и сервере. В /etc/ssh/sshd_config на VPS я выставил:

ClientAliveInterval 15
ClientAliveCountMax 3
TCPKeepAlive yes

Это заставляет SSH слать пакеты каждые 15 секунд. Если три подряд потеряны — сервер закрывает соединение. На домашней стороне ServerAliveInterval настроен так же. В итоге соединение стабильно даже через самые агрессивные NAT.

Ещё я столкнулся с тем, что после перезагрузки домашнего сервера туннель не поднимался, пока я не зайду по SSH локально. Оказалось, network.target не гарантирует, что сеть реально готова. Пришлось добавить network-online.target и wating-for-interface:

After=network-online.target
Wants=network-online.target

И вручную добавить интерфейс в /etc/systemd/system/ssh-tunnel.service.d/override.conf:

Requires=sys-subsystem-net-devices-enp2s0.device
After=sys-subsystem-net-devices-enp2s0.device

После этого туннель стал подниматься даже после глубокого ребута. Я проверял — выключал сервер из розетки, включал, через минуту туннель уже висел.

Туннель для коллег

Однажды понадобилось дать доступ к нашему GitLab (который дома) паре фрилансеров. Не заводить же их в WireGuard. Я просто создал ещё один туннель на VPS с портом 8443, который смотрит на GitLab. Фрилансеры подключаются к VPS по HTTPS — и видят наш GitLab. Никаких открытых портов на домашнем роутере.

Единственное — VPS должен быть достаточно мощным, чтобы проксировать трафик. Для 2-3 человек хватает самого дешёвого. Если бы было 20 человек — пришлось бы брать VPS посерьёзнее или ставить nginx на VPS как reverse proxy.

Я так и сделал: повесил nginx на VPS, который слушает port 443 и проксирует запросы через локальный туннель. Получился полноценный HTTPS-доступ к домашним сервисам через Let’s Encrypt.

Что пошло не так

Когда я только начинал, я не знал про GatewayPorts. По умолчанию SSH на VPS слушает -R порты только на localhost. То есть я подключался к VPS, а порт 2222 был виден только внутри VPS. Бесполезно, если хочется заходить с другого компа.

В /etc/ssh/sshd_config надо включить:

GatewayPorts yes

Тогда -R порты открываются на 0.0.0.0, и я могу достучаться до туннеля с любого устройства в интернете — конечно, если фаервол на VPS разрешает.

Ещё я наступал на грабли с конфликтом портов. Если дома уже крутится что-то на порту 8006, а я пытаюсь пробросить тот же порт — SSH падает с ошибкой. Лечится выбором другого порта на VPS: -R 8007:localhost:8006.

Хитрость: я запретил парольный вход на VPS вообще. Только ключи. Потому что если у кого-то будет доступ к VPS — он получит доступ к туннелю. На VPS только fail2ban крутится и фаервол с белым списком IP — стран, с которых я захожу.

Личный вердикт

SSH туннели — самый простой и безопасный способ получить доступ к домашнему серверу через NAT. Не нужен VPN, не нужно открывать порты на роутере. Только SSH и ключи.

Я теперь запускаю это на всех клиентах, где нужен удалённый доступ. Настройка заняла вечер, зато работает годами без проблем. Да, поначалу были грабли с GatewayPorts и keepalive — но когда разобрался, всё встало на свои места.

Если у тебя домашний сервер за NAT — не парься с DynDNS и UPnP. Поставь SSH туннель и живи спокойно. А если VPS жалко 3$ — попробуй поднять туннель через бесплатный Oracle Cloud Always Free. У меня там тоже крутится один туннель для экспериментов.

Ваш комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *