Туннели SSH: локальный и обратный проброс, SOCKS, бастион и TUN

Туннели SSH: локальный и обратный проброс, SOCKS, бастион и TUN

6 октября 2026 года вышел OpenSSH 10.6, что в нём нового, я разбирал в отдельной заметке. А здесь про возможность, которая есть в OpenSSH много лет и до сих пор выручает каждый день: туннели. Через обычное SSH-подключение можно пробросить порт к внутренней базе, открыть доступ к своему локальному сервису с сервера, поднять SOCKS-прокси или даже простой IP-туннель. Ключи одинаковые в Linux, macOS и FreeBSD.

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

Трафик приложения идёт внутри уже зашифрованной SSH-сессии. Снаружи видно только соединение с портом SSH, что внутри, не видно. Это удобно, чтобы достучаться до сервисов, которые доступны только с сервера, работать через бастион и временно обойтись без отдельного VPN. Нужен только работающий sshd и доступ к нему.

Локальный проброс: -L

На вашей машине открывается порт, а соединения с него уходят через SSH-сервер к нужному адресу. Классика: база данных или админка, которые слушают только внутри сети сервера.

ssh -L [локальный_порт]:[хост]:[порт] user@ssh.example.com

# PostgreSQL сервера на своём ноутбуке
ssh -N -L 5432:127.0.0.1:5432 user@ssh.example.com
# внутренняя веб-админка
ssh -N -L 8080:internal.example.com:80 user@ssh.example.com

После этого база доступна на 127.0.0.1:5432, а админка на http://127.0.0.1:8080. Важно: адрес в середине команды ищется и открывается со стороны SSH-сервера, поэтому 127.0.0.1 там значит сам сервер, а не ваш ноутбук.

Обратный проброс: -R

Наоборот: порт открывается на SSH-сервере, а соединения с него приходят к вам. Так можно показать заказчику локальный сайт или дать серверу доступ к сервису на вашей машине.

# локальный сайт на 8080 станет доступен на сервере, порт 9000
ssh -N -R 9000:localhost:8080 user@ssh.example.com
# обратный SOCKS: сервер ходит в сеть через вас
ssh -N -R 1080 user@ssh.example.com

По умолчанию, как сказано в man sshd_config, сервер вешает такие порты только на loopback, и снаружи к ним не подключиться. Чтобы открыть их наружу, нужен GatewayPorts в sshd_config. Делайте это осознанно и только вместе с файрволом: иначе ваш локальный сервис увидит весь интернет.

SOCKS-прокси: -D

Локальный SOCKS-прокси: приложение само говорит, куда ему надо, а SSH ходит туда от имени сервера. Удобно, когда нужен доступ сразу к куче внутренних адресов или нужно временно выйти в сеть с IP сервера.

ssh -N -D 1080 user@ssh.example.com
# проверка из консоли
curl --socks5-hostname 127.0.0.1:1080 https://ifconfig.me

В браузере указывается SOCKS5, хост 127.0.0.1, порт 1080, лучше с опцией отправлять DNS-запросы через прокси. Для консольных утилит часто хватает переменной ALL_PROXY=socks5h://127.0.0.1:1080.

Полезные ключи и сценарии

  • -N только туннель, без командной оболочки.
  • -f уйти в фон после входа.
  • -o ExitOnForwardFailure=yes завершиться, если порт пробросить не удалось, а не висеть без туннеля.
  • -C сжатие, иногда помогает на медленном канале с текстом.
  • Для постоянного туннеля вместо забытого -f лучше autossh или юнит systemd с перезапуском.

Бастион: -J

Чтобы попасть на сервер через промежуточный хост, не нужно заходить по SSH дважды:

ssh -J user@bastion.example.com user@10.0.0.15
# в ~/.ssh/config
Host db
    HostName 10.0.0.15
    User user
    ProxyJump user@bastion.example.com

Несколько прыжков пишутся через запятую. С настройкой в ~/.ssh/config пробросы тоже работают через бастион: ssh -N -L 5432:127.0.0.1:5432 db.

Графические приложения: -X

ssh -X user@ssh.example.com
# или -Y, если с -X не работают нужные расширения

На сервере должно быть X11Forwarding yes, на клиенте X-сервер (в macOS это XQuartz). Для постоянной удалённой работы с графикой удобнее RDP, VNC или Waypipe.

IP-туннель: -w

Режим почти VPN: два устройства tun соединяются через SSH. На сервере нужен PermitTunnel (по умолчанию выключен), на обеих сторонах права root. Это запасной вариант, для постоянной сети удобнее WireGuard.

# /etc/ssh/sshd_config на сервере
PermitTunnel point-to-point
# перезапуск: в Debian и Ubuntu служба называется ssh
sudo systemctl restart ssh
# FreeBSD
sudo service sshd restart
# клиент: поднять tun0 с обеих сторон
sudo ssh -w 0:0 root@ssh.example.com
# сервер (Linux)
sudo ip addr add 10.0.0.1/30 dev tun0 && sudo ip link set tun0 up
# клиент (Linux)
sudo ip addr add 10.0.0.2/30 dev tun0 && sudo ip link set tun0 up
# FreeBSD
sudo ifconfig tun0 10.0.0.2 10.0.0.1 netmask 255.255.255.252 up

Чтобы клиент выходил в интернет через сервер, на сервере нужны включённая пересылка пакетов и NAT в nftables или iptables. Если меняете маршрут по умолчанию на клиенте, сначала добавьте отдельный маршрут до самого SSH-сервера через старый шлюз, иначе туннель оборвёт сам себя.

UDP через SSH

Пробросы в SSH работают только с TCP. UDP можно протащить через socat внутри туннеля, но это хрупко. Для DNS, голоса и игр надёжнее WireGuard.

Устойчивость и безопасность

  • Вход по ключу: ssh-keygen -t ed25519, пароли в sshd_config лучше выключить.
  • Ограничьте, кто может пробрасывать: AllowTcpForwarding, PermitOpen, AllowUsers.
  • Защитите сам SSH от перебора, например через Fail2Ban.
  • Чтобы NAT не рвал простаивающий туннель, в ~/.ssh/config добавьте ServerAliveInterval 60 и ServerAliveCountMax 3.
  • Не открывайте -R на все адреса без файрвола.

Типичные проблемы

  • Порт уже занят локально: смените левую часть -L или -D.
  • -R не виден снаружи: GatewayPorts и файрвол на сервере.
  • Проброс молча не работает: на сервере может стоять AllowTcpForwarding no.
  • TUN: нет tun0 или device busy: другой номер в -w, права root, PermitTunnel.

В итоге три ключа закрывают почти все задачи: -L чтобы достать сервис с сервера к себе, -R чтобы отдать свой сервис серверу и -D чтобы пустить браузер или утилиты через сервер. Бастион делается через -J, а -w и UDP оставьте для редких случаев, постоянную сеть лучше строить на WireGuard.

Источники и ссылки


Комментарии загружаются…