Проброс портов в SSH — одна из тех возможностей, которые копируют со Stack Overflow, разок заставляют работать и так до конца и не понимают, — поэтому в следующий раз всё опять сводится к гаданию, какой из -L, -R и -D тут нужен. Три флага несложны; они просто неудачно названы. Вот единственная модель в голове, которая делает их очевидными, пример к каждому и флаги с ловушками, от которых зависит, доживёт ли туннель до вечера.
Один вопрос, который выбирает флаг
Любой SSH-туннель открывает слушающий порт на одной машине и незаметно переправляет всё, что в него приходит, туда, куда достаёт другая машина. Поэтому вопрос один: на какой стороне открывается порт, к которому вы подключаетесь?
-L(local) — порт открывается на вашей машине. Вы подключаетесь локально; трафик выходит на стороне сервера.-R(remote) — порт открывается на сервере. Кто-то подключается там; трафик выходит на вашей стороне.-D(dynamic) — порт открывается на вашей машине, но это SOCKS-прокси: назначение выбирается для каждого соединения, а не фиксируется заранее.
Всё остальное — детали. Держите в голове «на какой стороне открывается порт» — и больше никогда не потянетесь не за тем флагом.
Локальный проброс (-L) — подтянуть удалённый сервис на localhost
Самый частый. База данных, внутренний дашборд, UI с метриками — что-то привязанное к 127.0.0.1 на сервере или на машине, до которой достаёт только сервер. Вы хотите видеть это на ноутбуке, как будто оно локальное.
ssh -L 5432:localhost:5432 deploy@db.example.com
Читайте слева направо: открой 5432 на моей машине и всё, что придёт, переправляй на localhost:5432 как его видит сервер. Теперь psql -h 127.0.0.1 на ноутбуке разговаривает с Postgres на сервере (на английском) — через зашифрованное SSH-соединение, вообще не выставляя базу в сеть.
Промежуточный хост разрешается на стороне сервера — именно это и делает приём таким мощным:
# reach a database that only the bastion can see
ssh -L 5432:10.0.0.9:5432 deploy@bastion.example.com
Здесь 10.0.0.9 — приватный адрес, до которого ваш ноутбук не может дотянуться маршрутом, а бастион может. Туннель наводит мост.
Удалённый проброс (-R) — открыть вашу машину для стороны сервера
Зеркальное отражение, и тот самый, который понимают задом наперёд (у него есть свой отдельный разбор — Обратные SSH-туннели). Порт открывается на сервере; подключения к нему выходят на вашей стороне. Два классических применения:
# let the server (or its network) reach a dev server running on your laptop
ssh -R 8080:localhost:3000 deploy@app.example.com
Теперь всё, что попадает на localhost:8080 на сервере, переправляется на localhost:3000 вашего ноутбука — удобно, чтобы показать коллеге на машине свою незавершённую работу или дать удалённому сервису достучаться обратно до чего-то, что запущено только у вас.
Это же приём для доступа к машине, застрявшей за NAT без входящего доступа: пусть она откроет обратный туннель наружу — к серверу, которым вы управляете, — а вы подключаетесь через сервер. Машина, которая не может принимать подключения, вместо этого устанавливает исходящее.
Один подвох: по умолчанию сервер привязывает проброшенный порт к localhost, так что пользоваться им может только сам сервер. Чтобы до него достали другие машины в сети сервера, в sshd_config сервера нужен GatewayPorts yes (или clientspecified) — настройка на стороне сервера, а не то, что можно продавить с клиента.
Динамический проброс (-D) — SOCKS-прокси одним флагом
-L переправляет к одному фиксированному назначению. -D вместо этого открывает SOCKS-прокси, так что назначение выбирается на каждый запрос:
ssh -D 1080 deploy@jump.example.com
Направьте браузер (или curl --socks5 127.0.0.1:1080) на 127.0.0.1:1080 — и каждый запрос маршрутизируется от сервера. Это быстрый способ побродить по внутренней сети, посмотреть, как выглядит сервис с IP дата-центра, или достать до горстки внутренних хостов, не делая по -L на каждый.
Флаги, которые делают его пригодным к работе
Голая команда туннеля заодно открывает оболочку, которая вам не нужна, и умирает в тот момент, когда вы её закрываете. На практике:
ssh -N -L 5432:localhost:5432 deploy@db.example.com
-N— не запускать удалённую команду. Вам нужен только туннель, а не оболочка.-f— увести процесс в фон после подключения (сочетайте с-N).-o ExitOnForwardFailure=yes— если порт не удалось занять (уже используется, отказано в доступе), громко упасть, а не подключиться всё равно и молча ничего не пробрасывать.- Keepalive — долгоживущий туннель умирает от таймаутов простоя без
ServerAliveInterval 30. Добавьте его, иначе туннель отвалится, как только вы перестанете им пользоваться.
Сделайте его постоянным в ssh_config
У всего, что вы передали бы через -L/-R/-D, есть директива конфигурации, так что сохранённый хост несёт свои туннели с собой:
Host db-tunnel
HostName db.example.com
User deploy
LocalForward 5432 localhost:5432
ServerAliveInterval 30
RequestTTY no
Теперь ssh -N db-tunnel приносит базу на 127.0.0.1:5432 без флагов, которые надо помнить. RemoteForward и DynamicForward работают так же. (Подробнее о самом файле конфигурации — в гайде по ~/.ssh/config (на английском).)
Три ловушки
- Адрес привязки имеет значение.
-L 5432:...слушает только на127.0.0.1. Чтобы туннелем могли пользоваться другие устройства в вашей локальной сети, добавьте адрес привязки впереди:-L 0.0.0.0:5432:...— и осознайте, что вы только что выставили его в свою локальную сеть. - «Address already in use» означает, что локальный порт занят (часто прошлым туннелем, который не завершился). Выберите другой локальный порт или убейте старый;
ExitOnForwardFailureпревращает молчаливый сбой в ошибку, которую вы действительно заметите. - Промежуточный хост разрешается на дальней стороне.
localhostв-L a:localhost:bозначает localhost сервера, а не ваш. Перепутать это — самая частая ошибка проброса портов.
Когда туннель не привязывается
Если команда падает с bind: Address already in use или bind: Permission denied, слушатель так и не открылся — пройдитесь по короткому списку. Уже используется: порт кто-то держит, часто вчерашний забытый туннель — ss -tlnp называет процесс, дальше выберите другой порт или убейте старый. Отказ в доступе локально обычно означает привилегированный порт: всё, что ниже 1024, требует root, поэтому слушайте на 8443 вместо 443. На удалённом конце (-R) у сервера есть право вето: AllowTcpForwarding no в sshd_config блокирует проброс наотрез, а без GatewayPorts удалённый слушатель привязывается только к localhost, какой бы адрес привязки вы ни запросили. А на закалённых хостах семейства RHEL SELinux может запретить sshd привязку, даже когда конфигурация её разрешает, — проверьте журнал аудита, прежде чем винить свою команду.
Когда жонглировать туннелями вручную надоедает
Проброс — отличный примитив, и каждый разработчик должен знать три флага назубок. Трение проявляется позже: вы держите черновик с командами ssh -N -L ... (на английском), перезапускаете их после каждого сна ноутбука, забываете, какой локальный порт на какой сервис отображается, и с одного взгляда не можете понять, жив ли туннель вообще.
Вот где помогает клиент с нормальным менеджером туннелей. Termalin держит локальные, удалённые и динамические пробросы привязанными к хосту как сохранённую конфигурацию — запускайте их кликом, видьте, какие живы, и позволяйте им подниматься вместе с подключением, а не перенабираться командой. Тот же список хостов (с ключами, jump-хостами и keepalive) синхронизируется между вашими машинами со сквозным шифрованием, а каждый сервер проверяется при первом подключении. Командная строка учит, как работают туннели; клиент избавляет вас от необходимости пересобирать их вручную каждое утро.
Так или иначе, модель — это и есть весь фокус: решите, на какой стороне открывается порт, и остальное во флаге напишется само.
Termalin — бесплатный кроссплатформенный SSH-клиент со встроенными туннелями, SFTP и менеджером ключей — скачайте его или посмотрите, как он безопасно обращается с ключами.