Новинка Виртуальный VDS сервер в Нидерландах от 390р
Выберите продукт

SSH-туннели в Linux: local, remote и SOCKS на практике

Что такое SSH-туннели и как ими пользоваться: делаем local/remote/SOCKS-пробросы, работаем через бастион, проверяем фаервол и настраиваем автоподнятие. Внутри — безопасные примеры, диагностика и подсказки для продакшна.
SSH-туннели в Linux: local, remote и SOCKS на практике

SSH-туннель — это зашифрованный канал внутри существующего SSH-подключения, через который перенаправляется трафик с одного порта на другой. Три режима:

  • LocalForward (-L): локальный порт → удалённая цель.
  • RemoteForward (-R): удалённый порт на сервере → локальная цель.
  • DynamicForward (-D): локальный SOCKS5-прокси, через который приложения выходят в сеть «через сервер».

Это не VPN: туннели точечные (порт/сервис), а не полноценный сетевой интерфейс.

Когда туннели реально нужны

  • Доступ к БД/админке, доступной только из внутренней сети (-L).
  • Показ локального dev-сервиса внешнему миру на короткое время (-R).
  • «Выйти в интернет как с сервера» для отладки/geo-кейсов (-D).
  • Обход NAT/CGNAT, работа через бастион, «не тянуть VPN ради одного порта».

Если SSH ещё не настроен — начните с базовой статьи: SSH в Linux.

FastFox VDS
Облачный VDS-сервер
Виртуальные серверы с быстрым запуском и гибкой конфигурацией от 390₽ / мес
Доступные локации
Россия Нидерланды

Local port forward (-L): доступ к внутреннему ресурсу

Подключаемся к серверу, у себя получаем локальный порт, который «ходит» к внутреннему сервису через SSH.

Пример без бастиона:

ssh -L [LOCAL_ADDR:]LPORT:TARGET_HOST:TPORT user@SSH_HOST
# часто — так:
ssh -L 127.0.0.1:5432:db.internal:5432 user@bastion

Пояснения:

  • 127.0.0.1 — безопасный адрес привязки (binding-адрес), чтобы никто извне не подключился к туннелю.
  • TARGET_HOST:TPORT — реальная внутренняя цель, видимая с точки зрения SSH_HOST (например, db.internal:5432).
  • Добавьте -N (не запускать шелл) и -f (в фон):
    ssh -N -f -L 127.0.0.1:5432:db.internal:5432 user@bastion

Проверка:

psql -h 127.0.0.1 -p 5432 -U app dbname   # пример для PostgreSQL
# или
curl -v http://127.0.0.1:8080/health

Через бастион (-J):

ssh -J user@bastion user@internal -L 127.0.0.1:5432:db.internal:5432 -N -f

Чтобы не печатать длинные команды и управлять туннелем коротким алиасом, вынесите параметры в профиль ~/.ssh/config. Тогда туннель поднимается командой ssh -N -f bastion и закрывается ssh -O exit bastion.

Host bastion
  HostName bastion.example.com
  User user
  ServerAliveInterval 30
  ServerAliveCountMax 3
  ControlMaster auto
  ControlPersist 4h
  ExitOnForwardFailure yes
  LocalForward 127.0.0.1:5432 db.internal:5432

Что дальше: см. рецепты PostgreSQL через бастион и VNC через SSH (для RDP меняем порт на 3389).

Remote port forward (-R): показать локальный сервис наружу

Вы за NAT/фаерволом, но хотите временно показать свой локальный порт (например, dev-сайт на localhost:3000) через удалённый сервер (VPS).

Синтаксис (клиент):

ssh -R [RADDR:]RPORT:LOCAL_HOST:LPORT user@VPS
# пример: публикуем локальный 3000 на VPS:8080
ssh -R 127.0.0.1:8080:127.0.0.1:3000 user@vps.example.com

Важные детали.

  • По умолчанию (на большинстве конфигов) -R слушает только на loopback сервера.
  • Чтобы открыть порт на внешнем интерфейсе VPS, нужна серверная опция GatewayPorts:
    GatewayPorts clientspecified — доверять адресу из клиента (0.0.0.0 / ::).
    Или GatewayPorts yes — слушать на всех адресах.
  • Разрешите форвардинг на сервере: AllowTcpForwarding yes (или remote).

Пример: публичный 8080 на VPS

1. На VPS: проверьте sshd_config:

AllowTcpForwarding remote
GatewayPorts clientspecified

Примените: sudo sshd -t && sudo systemctl reload sshd

2. На клиенте:

ssh -R 0.0.0.0:8080:127.0.0.1:3000 user@vps.example.com -N -f

3. Откройте фаервол на VPS (если нужно):

# Debian-based (UFW):

sudo ufw allow 8080/tcp && sudo ufw reload


# RHEL-based (firewalld):

sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload


# Arch: UFW или firewalld — по аналогии.

Проверяйте доступ извне (например, нашим онлайн-сканером портов или curl http://VPS:8080 с внешнего хоста).

Безопасность: если публикуете наружу, используйте временные учётки, ограничение Match User и по возможности оставляйте RADDR = 127.0.0.1 + делайте доступ через реверс-прокси/VPN. Публичный 0.0.0.0 нужен только осознанно и ненадолго.

Что дальше: см. рецепт Показ локального dev на VPS (-R).

Dynamic SOCKS (-D): гибкий прокси через SSH

Поднимаем локальный SOCKS5-порт, через который приложения выходят «как с сервера».

Команда:

ssh -D 1080 -N user@server

Проверка в CLI:

curl --socks5-hostname 127.0.0.1:1080 https://ifconfig.me

Браузер/приложения. Укажите SOCKS-прокси 127.0.0.1:1080. Используйте режим socks5-hostname, чтобы DNS-запросы шли через прокси, а не локально. Помните, что не все программы умеют SOCKS.

Что дальше: см. рецепт SOCKS/прокси через SSH.

Через бастион и мульти-хопы

Совмещайте -L/-R/-D с бастионом:

# local forward через бастион
ssh -J user@bastion user@internal -L 127.0.0.1:5432:db.internal:5432 -N -f
# SOCKS через бастион
ssh -J user@bastion -D 1080 -N user@internal

Альтернатива старой школы — ProxyCommand -W %h:%p, но -J проще.

Полезные опции клиента и конфиг

В ~/.ssh/config удобно держать всё, что нужно ежедневно:

Host vps
  HostName vps.example.com
  User user
  ServerAliveInterval 30
  ServerAliveCountMax 3
  ExitOnForwardFailure yes
  ControlMaster auto
  ControlPersist 4h
  # Примеры пробросов:
  # LocalForward 127.0.0.1:5432 db.internal:5432
  # RemoteForward 127.0.0.1:8080 127.0.0.1:3000
  # DynamicForward 1080
  • ExitOnForwardFailure yes — упасть, если форвард не встал.
  • ServerAlive* — держать соединение живым; помогает autossh/systemd.
  • ControlMaster/ControlPersist — мультиплексирование, меньше RTT при множестве команд.

Политика безопасности на сервере (sshd_config)

Ограничивайте возможности туннелей на стороне сервера — глобально или выборочно (через Match):

# Разрешить только нужное
AllowTcpForwarding local|remote|yes|no
# Управлять доступностью удалённых адресов/портов (для -L и, в новых версиях, -R)
# PermitOpen host:port
# (в свежих OpenSSH также есть PermitListen для -R — проверьте `sshd -T`)

# Публикация удалённого порта наружу
GatewayPorts no|clientspecified|yes

# Блокируем лишнее
X11Forwarding no
AllowAgentForwarding no

# Ограничения по пользователям/группам
Match User dev
    AllowTcpForwarding remote
    PermitOpen 127.0.0.1:3000

Перед применением — sudo sshd -t. Если директивы не поддерживаются вашей версией OpenSSH, они засветятся ошибкой; посмотрите, что реально активно: sudo sshd -T | grep -i forward.

Диагностика и типичные ошибки

  • channel 0: open failed: administratively prohibited — сервер запрещает форвардинг (AllowTcpForwarding no) или не разрешён нужный хост/порт (PermitOpen).
  • bind: Address already in use — порт уже занят (проверьте ss -lntp).
  • connect to 127.0.0.1 port N: Connection refused — целевой сервис не слушает или неверная цель (TARGET_HOST:TPORT).
  • Broken pipe / обрывы — добавьте ServerAliveInterval/CountMax, посмотрите сеть/фаервол.
  • Снаружи «не видно» при -R — откройте порт на VPS-фаерволе; при необходимости используйте наш онлайн-сканер портов.
  • Смотрите логи:
ssh -vvv user@host                  # подробности с клиента
sudo journalctl -u sshd -b          # серверные логи
ss -lntp; lsof -i -nP               # кто слушает/куда подключаемся

Автоподнятие и восстановление туннеля

Вариант A — autossh (просто и надёжно):

# Local forward
autossh -M 0 -N -f -L 127.0.0.1:5432:db.internal:5432 user@bastion \
  -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o ExitOnForwardFailure=yes

# Remote forward
autossh -M 0 -N -f -R 0.0.0.0:8080:127.0.0.1:3000 user@vps \
  -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o ExitOnForwardFailure=yes

Вариант B — systemd-юнит (без autossh): /etc/systemd/system/ssh-tunnel.service

[Unit]
Description=Persistent SSH tunnel (-R 8080 -> localhost:3000)
After=network-online.target
Wants=network-online.target

[Service]
User=tunnel               # выделенный пользователь
ExecStart=/usr/bin/ssh -N -o ServerAliveInterval=30 -o ServerAliveCountMax=3 \
  -o ExitOnForwardFailure=yes -R 0.0.0.0:8080:127.0.0.1:3000 user@vps.example.com
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now ssh-tunnel

Рекомендуется выделить отдельного пользователя/ключ, ограничить права Match-блоком и PermitOpen/GatewayPorts.

Unix-сокеты и нестандартные кейсы

SSH поддерживает проброс UNIX-сокетов (StreamLocalForward):

# Локально создаём сокет .pg.sock, который идёт к /var/run/postgresql/.s.PGSQL.5432 на сервере
ssh -L ~/pg.sock:/var/run/postgresql/.s.PGSQL.5432 user@db-host -N

Следите за правами на сокеты и учитывайте, что некоторые менеджеры (Docker) не любят такой проброс в проде.

Готовые рецепты (скопируй и пользуйся)

VNC через SSH

Задача A. Подключиться к удалённому VNC-серверу, не открывая 5900 наружу

1. Убедитесь, что на удалённой машине VNC действительно слушает (часто только на loopback):

# на удалённом хосте
ss -lntp | grep 59
# примеры запуска:
tigervncserver :1 -localhost yes       # порт 5901
# или для :0 будет 5900

2. Поднимите локальный туннель SSH (без шелла):

ssh -N -f -L 5901:127.0.0.1:5901 user@REMOTE

3. Откройте свой VNC-клиент и подключитесь к localhost:5901

  • macOS: open vnc://localhost:5901
  • Linux: vncviewer localhost:5901 (TigerVNC / Remmina и т. п.)
  • Windows (PuTTY): Connection → SSH → Tunnels → Source port: 5901, Destination: 127.0.0.1:5901 → Add → Open; затем в VNC-клиенте — localhost:5901.

Почему безопасно: VNC по умолчанию не шифрует трафик; -L шифрует соединение и держит сам VNC слушающим только на 127.0.0.1.

Задача B. Показать локальный VNC наружу через свой VPS (редко, осознанно)

1. На VPS в sshd_config разрешите публикацию порта:

AllowTcpForwarding remote
GatewayPorts clientspecified
sudo sshd -t && sudo systemctl reload sshd

2. На своей машине поднимите обратный туннель:

ssh -N -f -R 0.0.0.0:5901:127.0.0.1:5901 user@VPS

Теперь к вашему локальному VNC можно подключаться на VPS:5901 (лучше ограничить IP на уровне фаервола VPS).

Этот вариант потенциально опаснее: включайте его временно, с ограничениями (firewalld/UFW, Match User, возможен реверс-прокси с ACL).

Полезно знать

  • Порты VNC: :0 → 5900, :1 → 5901, :2 → 5902 ...
  • Завершить туннель: если используете ControlMaster-профиль — ssh -O exit <alias>; иначе pkill -f 'ssh -N -f -L 5901'.
# Примечание про RDP (Windows):
# тот же приём -L, только порт 3389
ssh -N -f -L 127.0.0.1:3389:WIN_HOST:3389 user@bastion
# через ProxyJump:
ssh -J user@bastion user@internal -N -f -L 127.0.0.1:3389:WIN_HOST:3389
# затем подключайтесь RDP-клиентом к localhost:3389 (MSTSC/Remmina/MRD)

PostgreSQL через бастион (-L + -J)

Задача. Получить доступ к БД db.internal:5432, которая видна только из внутренней сети сервера.

1. Проверка с бастиона (доступ к цели):

# на бастионе
nc -vz db.internal 5432 || ss -n 'dport = :5432'   # есть ли коннект
# (если есть psql)
psql -h db.internal -p 5432 -U app -c 'select 1;' || true

2. Поднять туннель с ноутбука:

ssh -J user@bastion.example.com user@internal.example.com \
  -N -f -o ExitOnForwardFailure=yes \
  -L 127.0.0.1:5432:db.internal:5432

3. Подключиться локально:

psql -h 127.0.0.1 -p 5432 -U app dbname

Зачем 127.0.0.1? Чтобы локальный порт был доступен только вам.
IPv6: цель можно писать в скобках: -L 127.0.0.1:5432:[2001:db8::10]:5432.

Удобно вынести в ~/.ssh/config:

Host db-tunnel
  HostName bastion.example.com
  User user
  ProxyJump user@bastion.example.com
  ExitOnForwardFailure yes
  ServerAliveInterval 30
  ServerAliveCountMax 3
  LocalForward 127.0.0.1:5432 db.internal:5432

Зачем это нужно: чтобы не печатать длинные команды. С профилем достаточно ssh -N -f db-tunnel (поднять) и ssh -O exit db-tunnel (закрыть).


Показ локального dev на VPS (-R):

Задача. Вы за NAT, хотите показать http://localhost:3000 наружу как http://VPS:8080.

1. На VPS в /etc/ssh/sshd_config:

AllowTcpForwarding remote
GatewayPorts clientspecified   # или yes (шире)

Применить:

sudo sshd -t && sudo systemctl reload sshd

2. С ноутбука поднять обратный туннель:

ssh -N -f -o ExitOnForwardFailure=yes \
  -R 0.0.0.0:8080:127.0.0.1:3000 user@vps.example.com

3. Открыть фаервол на VPS:

# UFW (Debian/Ubuntu):

sudo ufw allow 8080/tcp && sudo ufw reload

# firewalld (RHEL/Alma/Rocky/Fedora):

sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload

4. Проверка снаружи:

curl -I http://vps.example.com:8080/

Безопаснее вариант. Слушать только на loopback VPS:

ssh -N -f -R 127.0.0.1:8080:127.0.0.1:3000 user@vps

И отдать наружу через Nginx (ACL/базовая auth/HTTPS).
Ограничения: сделайте отдельного юзера и Match-политику:

Match User devpub
    AllowTcpForwarding remote
    PermitOpen 127.0.0.1:3000

Локальный браузер «через сервер» (-D SOCKS)

Задача. Отправлять трафик приложений через сервер (гео/отладка).

1. Поднять SOCKS-прокси:

ssh -D 1080 -N -f user@server.example.com

2. Проверка DNS и выхода:

curl --socks5-hostname 127.0.0.1:1080 https://ifconfig.me

Используйте именно --socks5-hostname, чтобы DNS-запросы шли через прокси.

3. Браузер: укажите SOCKS5 127.0.0.1:1080.
CLI: можно выставить переменные:

export ALL_PROXY=socks5h://127.0.0.1:1080
export NO_PROXY=localhost,127.0.0.1

Через бастион:

ssh -J user@bastion user@internal -D 1080 -N -f

Закрыть:

pkill -f 'ssh -D 1080'
# или, если запускали алиасом:
ssh -O exit socks-tunnel

Чек-лист перед продакшеном

  • Вход по ключам + (по ситуации) 2FA включены.
  • Фаервол/ACL согласованы (нужные порты на нужных узлах).
  • В sshd_config настроены AllowTcpForwarding, GatewayPorts, PermitOpen/PermitListen (если поддерживается).
  • Туннель стартует с ExitOnForwardFailure, живёт с ServerAlive*.
  • Логи на контроле, алиасы в ~/.ssh/config работают.
FastFox VDS
Облачный VDS-сервер
Виртуальные серверы с быстрым запуском и гибкой конфигурацией от 390₽ / мес
Доступные локации
Россия Нидерланды

Заключение

Мы разобрали, как использовать SSH для создания защищённых туннелей — локальных, обратных и динамического SOCKS — и как настраивать их безопасно и стабильно. Надеемся, материал поможет организовать надёжную связь в Интернете.

Поделиться статьей

Вам будет интересно

Как безопасно выполнить bootstrap MariaDB Galera после полного останова

Как безопасно выполнить bootstrap MariaDB Galera после полного останова

После одновременной остановки всех узлов Galera обычного запуска MariaDB недостаточно. Сначала найдите сервер с последней транзакц ...
CoreDNS в Kubernetes: исправляем CrashLoopBackOff, SERVFAIL и DNS-петлю

CoreDNS в Kubernetes: исправляем CrashLoopBackOff, SERVFAIL и DNS-петлю

Разберём, почему CoreDNS перезапускается или отвечает SERVFAIL, как обнаружить замкнутую пересылку DNS-запросов, проверить доступн ...
Apache Kafka KRaft на VDS: отдельные controller и broker

Apache Kafka KRaft на VDS: отдельные controller и broker

Соберём новый Kafka-кластер без ZooKeeper на шести Linux VDS: три узла controller образуют отказоустойчивый metadata quorum, а три ...