Зачем вообще нужен SSH port forwarding
SSH часто воспринимают как «зайти на сервер по SSH». Но для админов и девопсов куда полезнее другая часть протокола: туннелирование (port forwarding). Оно позволяет безопасно протащить TCP-трафик через SSH-сессию, даже если прямой доступ к сервису закрыт фаерволом, NAT или сетевой политикой.
Терминология обычно путает, потому что «локальный/удалённый/динамический» описывает не то, где запущен SSH, а то, где создаётся порт, в который вы подключаетесь. Разберём три режима:
- Local forward (локальная переадресация,
-L) — порт открывается на вашей машине. - Remote forward (удалённая переадресация,
-R) — порт открывается на удалённом SSH-сервере. - Dynamic SOCKS (
-D) — на вашей машине поднимается SOCKS-прокси, а SSH сам устанавливает соединения к целям.
Шпаргалка по синтаксису
Минимальные формы команд (ниже будет практика и типовые ошибки):
ssh -L [bind_addr:]LPORT:DEST_HOST:DEST_PORT user@ssh_host
ssh -R [bind_addr:]RPORT:DEST_HOST:DEST_PORT user@ssh_host
ssh -D [bind_addr:]DPORT user@ssh_host
Что означают параметры:
LPORT,RPORT,DPORT— локальный/удалённый/локальный(SOCKS) порт, который будет «слушать».DEST_HOST:DEST_PORT— куда SSH подключится с той стороны туннеля (на стороне сервера при-L, на стороне клиента при-R).bind_addr— адрес привязки слушающего порта (например,127.0.0.1или0.0.0.0).
Важно: OpenSSH port forwarding по умолчанию переносит TCP. UDP напрямую не поддерживается — это отдельная тема с другими инструментами и компромиссами.
Если вы поднимаете бастион/точку входа в инфраструктуру, это обычно логичнее делать на отдельной машине (с понятными правилами доступа, логами и ограничениями). Для таких задач удобнее VDS, чтобы не смешивать прод-приложения и административный шлюз.
Local forward (-L): доступ к приватным сервисам «как будто они локальные»
Local forward — самый частый сценарий: сервис доступен только изнутри сервера (или вообще только на 127.0.0.1), а вам нужно подключиться со своей машины. Мы открываем порт на локальной машине и «прокидываем» его к нужному сервису через SSH.
Пример: подключиться к PostgreSQL/MySQL, которые слушают только localhost
Допустим, на сервере база слушает 127.0.0.1:5432 (PostgreSQL), а SSH доступен извне. Поднимаем локальный порт 15432:
ssh -L 15432:127.0.0.1:5432 user@server
Теперь на вашей машине можно подключаться к 127.0.0.1:15432, а трафик уйдёт на server и попадёт в PostgreSQL на 127.0.0.1:5432.
Чтобы SSH не открывал интерактивную оболочку (только туннель), используйте -N. Чтобы отправить процесс в фон — -f (обычно вместе):
ssh -N -f -L 15432:127.0.0.1:5432 user@server
Пример: доступ к админке/внутреннему HTTP-сервису
Частый кейс: внутренняя панель мониторинга/админка доступна только на 127.0.0.1:3000 сервера. Поднимаем локальный порт:
ssh -N -L 13000:127.0.0.1:3000 user@server
Дальше вы открываете сервис на локальном 127.0.0.1:13000 и работаете с ним как с «локальным», не публикуя его наружу.
Типичные ошибки и полезные флаги
- Порт занят локально — выберите другой
LPORT. - Туннель поднят, но соединения нет — проверьте, куда SSH должен подключаться на стороне сервера: часто это
127.0.0.1, а не внешний IP. - Нужна диагностика — добавьте
-v,-vvили-vvv. - Не хотите слушать на всех интерфейсах — явно привяжитесь к
127.0.0.1:
ssh -N -L 127.0.0.1:13000:127.0.0.1:3000 user@server
Когда туннели используются регулярно, удобно хранить их в ~/.ssh/config, а не в истории терминала: меньше ошибок, проще масштабировать, легче ревьюить доступы.

Remote forward (-R): «вход» к вам через сервер (обход NAT)
Remote forward нужен, когда к вашей машине или внутреннему сервису нельзя подключиться напрямую: вы за NAT, за корпоративным фаерволом или в закрытом сегменте. Тогда вы сами поднимаете SSH-сессию на публичный сервер и открываете порт на сервере, который будет проксировать подключения обратно к вам.
Пример: временно отдать доступ к локальному веб-сервису
Допустим, на вашем ноутбуке поднят сервис на 127.0.0.1:8080, а у вас есть сервер server с публичным IP. Команда:
ssh -N -R 18080:127.0.0.1:8080 user@server
На server появится слушающий порт 127.0.0.1:18080 (по умолчанию именно localhost). Любой запрос, пришедший на этот порт на сервере, пойдёт по SSH обратно на ваш 127.0.0.1:8080.
GatewayPorts: когда удалённый порт должен слушать не только localhost
Частый вопрос: «Я сделал -R, но снаружи к порту не подключиться». По умолчанию это нормально: OpenSSH привязывает удалённый порт к 127.0.0.1. Чтобы порт стал доступен извне, есть два механизма — оба требуют осознанности, потому что это фактическая публикация порта.
- Явно указать адрес привязки в
-R:
ssh -N -R 0.0.0.0:18080:127.0.0.1:8080 user@server
- Разрешить это на SSH-сервере настройкой
GatewayPortsвsshd_config.
Режимы GatewayPorts (как обычно, «по умолчанию безопаснее»):
GatewayPorts no— удалённые форварды только на localhost.GatewayPorts yes— клиент может открывать порт на всех интерфейсах.GatewayPorts clientspecified— сервер уважаетbind_addr, который вы указали в-R.
Если нет чёткого сценария «публикуем порт наружу», держите
GatewayPortsв режимеnoилиclientspecifiedи всегда задавайтеbind_addrявно.
PermitOpen: ограничить, куда вообще можно форвардить
Если вы даёте SSH-доступ коллегам/подрядчикам и хотите контролировать направления туннелей, используйте ограничения на стороне сервера. В OpenSSH есть параметр PermitOpen: он задаёт allowlist «куда разрешено подключаться через форвардинг».
Практическая логика такая: даже если пользователь попробует сделать форвард на произвольный хост/порт, сервер запретит неразрешённые направления. Это особенно важно для bastion-хостов, где один лишний туннель может превратить «точку входа» в обход всей сетевой сегментации.
Dynamic SOCKS (-D): универсальный туннель для множества хостов
Dynamic SOCKS — это локальный SOCKS-прокси поверх SSH. В отличие от -L, где вы заранее фиксируете DEST_HOST:DEST_PORT, режим -D позволяет клиентам выбирать адрес назначения на лету.
Запуск SOCKS-прокси через SSH
ssh -N -D 127.0.0.1:1080 user@server
Дальше приложения, которые умеют SOCKS5, можно настроить на 127.0.0.1:1080.
SOCKS5 и DNS: чтобы резолв имён тоже шёл через туннель
Тонкость: некоторые клиенты при SOCKS-прокси продолжают резолвить DNS локально, а не «на той стороне». В сетях с ограничениями это либо ломает доступ к внутренним доменам, либо создаёт лишние утечки.
Ищите в настройках клиента режим «proxy DNS» / «remote DNS» / «SOCKS5 hostname». Принцип один: передавать доменное имя через SOCKS, чтобы резолв происходил там, где у вас есть доступ. Если вы часто отлаживаете DNS-поведение и политики резолвинга, пригодится материал про split-horizon DNS (views) в BIND.

ProxyCommand и ProxyJump: доступ через бастион и цепочки
ProxyCommand — механизм OpenSSH, который определяет, как устанавливать TCP-соединение до конечного SSH-сервера. Самый частый кейс — доступ к внутренним хостам через bastion/jump host (когда внутренний сервер недоступен напрямую).
Сегодня чаще используют ProxyJump (ключ -J), но ProxyCommand всё ещё часто встречается в старых конфигурациях, CI-скриптах и inventory-файлах.
Пример ProxyCommand через бастион
Пример записи в ~/.ssh/config:
Host internal-app
HostName 10.10.0.10
User deploy
ProxyCommand ssh -W %h:%p bastion
Смысл: вы подключаетесь к internal-app, но фактически SSH сначала идёт на bastion, а дальше пробрасывает TCP до 10.10.0.10:22 через ssh -W.
Совмещение bastion-доступа и port forwarding
Самое полезное начинается, когда вы совмещаете bastion и туннели. Например, база доступна только во внутренней сети, SSH к внутреннему хосту — только из bastion, а вам нужен -L на ноутбук. Подход такой: сначала добейтесь стабильного ssh internal-app, и только потом добавляйте -L (или -D). Так проще локализовать проблему.
Безопасность: как не превратить туннели в «универсальный обход»
SSH port forwarding — мощный инструмент, но в среде с несколькими пользователями он может стать дырой в политике доступа, если оставить всё «как есть». На стороне sshd обычно контролируют три вещи: можно ли форвардить вообще, куда можно форвардить и не публикуются ли порты наружу.
AllowTcpForwarding: кто вообще может форвардить
Базовая настройка — разрешён ли TCP forwarding. Обычно это параметр AllowTcpForwarding. Его можно задавать глобально и в секциях Match (например, по пользователю/группе). Для сервисных учёток часто разумно отключать форвардинг, если он не нужен.
PermitOpen: allowlist направлений
PermitOpen помогает ограничить destination-направления. Это особенно актуально для bastion-хостов: вы можете разрешить туннели только к нескольким административным сервисам, а не ко всей сети.
GatewayPorts: не публикуйте лишнее
GatewayPorts влияет на то, будет ли remote forward доступен извне. Если у вас нет чёткого сценария публикации порта наружу, избегайте GatewayPorts yes.
Логи и наблюдаемость
Если бастион — контролируемая точка входа, важно видеть попытки и факты форвардинга. В зависимости от настроек логирования sshd можно получить записи о запросах на открытие каналов. Это полезно для расследований «почему сервис стал доступен» и «кто прокидывал туннели».
Практические рецепты
1) Открыть туннель и не держать лишнюю сессию
ssh -N -f -L 127.0.0.1:18000:127.0.0.1:8000 user@server
2) Поднять SOCKS как универсальный выход «в сеть сервера»
ssh -N -D 127.0.0.1:1080 user@server
3) Remote forward для доступа к вашему локальному сервису через сервер
ssh -N -R 127.0.0.1:19000:127.0.0.1:9000 user@server
Этот вариант безопаснее, потому что порт слушает только localhost на сервере. Дальше вы решаете, кто на сервере может к нему подключаться (локально, через другой туннель, через reverse proxy и т.д.).
4) Проверить, куда реально «слушает» порт
На локальной машине:
ss -lntp
На сервере (для remote forward):
ss -lntp
Как выбирать режим: краткая логика
- Нужно с ноутбука попасть в приватный сервис на сервере или в его сети — используйте local forward (
-L). - Нужно, чтобы кто-то на сервере мог подключаться к сервису у вас за NAT — используйте remote forward (
-R) и аккуратно настройтеGatewayPorts. - Нужно много направлений через один туннель (браузер/CLI) — используйте dynamic SOCKS (
-D). - Есть bastion и внутренние хосты — добавляйте ProxyCommand или
-J, а уже поверх делайте-L/-R/-D.
Итог
SSH port forwarding — «швейцарский нож» для админа: доступ к закрытым панелям, базам и внутренним API без публикации сервисов наружу, плюс удобные маршруты через bastion. Держите в голове два вопроса: где именно открывается порт (local/remote), и что разрешено политиками на sshd (AllowTcpForwarding, PermitOpen, GatewayPorts). Тогда туннели останутся управляемым инструментом, а не обходом инфраструктурных ограничений.
Если захотите продолжение, логичное углубление — «боевой» ~/.ssh/config: мультиплексирование ControlMaster, устойчивые туннели в стиле autossh и шаблоны для dev/stage/prod.


