Все статьи🇷🇺 Русский
7 мин

SSH Port Forwarding — безопасный доступ к серверу

Как получить доступ к pgAdmin, Grafana и админ-панелям через SSH-туннель без открытия портов в интернет. Практические примеры и автоматизация.

sshsecurityport-forwardingtunnelingserver

SSH Port Forwarding (туннелирование) — это проброс сетевого трафика через зашифрованное SSH-соединение. Позволяет получить доступ к pgAdmin, Grafana, RabbitMQ и другим сервисам на сервере, не открывая их порты в интернет. Одна команда заменяет настройку VPN, правил firewall и дополнительной аутентификации.

Если SSH-ключи на сервере ещё не настроены — начните с гайда по SSH-ключам на Ubuntu. Там же описан ~/.ssh/config, который пригодится для автоматизации туннелей дальше в этой статье.

Когда SSH-туннель лучше альтернатив

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

РешениеКогда подходитКогда избыточно
SSH Port Forwarding1–3 разработчика, один серверБольшая команда, постоянный доступ
VPN (WireGuard)Вся команда, множество сервисовРазовый доступ одного человека
Bastion HostДесятки серверов, корпоративная инфраОдин VPS
Cloudflare TunnelВеб-доступ без VPN для командыCLI-инструменты и базы данных

На практике это выглядит проще, чем кажется: для одного VPS с Docker-стеком SSH-туннель поднимается за 10 секунд, не требует установки дополнительного ПО и работает с любым протоколом — HTTP, PostgreSQL, Redis, AMQP.

Типы SSH Port Forwarding

Local Port Forwarding (-L) — основной сценарий

Local forwarding пробрасывает порт с удалённого сервера на локальную машину. Используется в 90% случаев — когда нужно открыть в браузере или подключиться клиентом к сервису, работающему на сервере.

Синтаксис:

ssh -L [локальный_порт]:[хост_назначения]:[порт_назначения] user@server

Схема трафика:

Ваш компьютер (localhost:8080) → SSH-туннель → Сервер (localhost:3001)

Remote Port Forwarding (-R) — для демо и вебхуков

Remote forwarding работает в обратном направлении: пробрасывает порт с локальной машины на сервер. Сценарии — показать заказчику локальный dev-сервер или принять вебхук от внешнего сервиса.

ssh -R [удалённый_порт]:[локальный_хост]:[локальный_порт] user@server

Dynamic Port Forwarding (-D) — SOCKS-прокси

Создаёт SOCKS5-прокси на локальной машине. Весь трафик, направленный через прокси, идёт через SSH-соединение. Используется для доступа ко всей приватной сети сервера, а не к конкретному порту.

ssh -D 1080 user@server

Практические примеры

Доступ к pgAdmin

pgAdmin развёрнут в Docker-контейнере на порту 5050 сервера. Порт не открыт в firewall — и не должен быть:

ssh -L 5050:localhost:5050 deployer@YOUR_SERVER_IP

После подключения http://localhost:5050 в браузере открывает pgAdmin. Трафик зашифрован, порт закрыт от интернета.

Прямое подключение к PostgreSQL

Для работы через DBeaver, DataGrip или psql — проброс порта 5432:

ssh -L 5432:localhost:5432 deployer@YOUR_SERVER_IP

Параметры подключения в клиенте:

Host: localhost
Port: 5432
Database: your_database
User: your_user

Помню, в одном из проектов всю неделю коллега подключался к базе через публичный порт 5432, открытый в firewall. В логах PostgreSQL за это время накопилось более 12 000 попыток брутфорса от ботов. SSH-туннель закрыл вопрос за минуту.

Несколько сервисов одновременно

Админ-панель на порту 3001, pgAdmin на 5050, Grafana на 3000 — всё в одной команде:

ssh -L 8080:localhost:3001 \
    -L 5050:localhost:5050 \
    -L 3000:localhost:3000 \
    deployer@YOUR_SERVER_IP

После подключения:

СервисЛокальный адрес
Админ-панельhttp://localhost:8080
pgAdminhttp://localhost:5050
Grafanahttp://localhost:3000

Проброс через Jump Server (бастион)

Приложение в приватной сети (10.0.0.5:3001), доступ только через промежуточный сервер:

Ваш ПК → Jump Server (публичный IP) → Приватный сервер (10.0.0.5:3001)
ssh -L 8080:10.0.0.5:3001 deployer@jump-server.com

SSH-туннель строится до jump-server, а оттуда трафик идёт на 10.0.0.5:3001. Локально всё доступно на http://localhost:8080.

Фоновый режим и автоматизация

Туннель без интерактивной сессии

Флаги -f и -N запускают туннель в фоне без выполнения команд на сервере:

ssh -f -N -L 8080:localhost:3001 deployer@YOUR_SERVER_IP

Для завершения:

# Найти PID
ps aux | grep "ssh -f"

# Завершить
kill <PID>

autossh — автоматическое переподключение

SSH-туннель разрывается при нестабильном соединении. autossh отслеживает состояние и переподключает:

sudo apt install autossh

autossh -M 0 -f -N -L 8080:localhost:3001 deployer@YOUR_SERVER_IP

Флаг -M 0 отключает отдельный monitoring-порт — вместо него используются ServerAliveInterval / ServerAliveCountMax, которые уже настроены в ~/.ssh/config из гайда по SSH-ключам.

~/.ssh/config — туннели одной командой

Вместо длинных команд — конфиг на локальной машине:

Host myproject-admin
    HostName YOUR_SERVER_IP
    User deployer
    IdentityFile ~/.ssh/id_ed25519
    LocalForward 8080 localhost:3001
    LocalForward 5050 localhost:5050
    LocalForward 3000 localhost:3000
    ServerAliveInterval 60
    ServerAliveCountMax 3

Теперь:

# Было
ssh -L 8080:localhost:3001 -L 5050:localhost:5050 -L 3000:localhost:3000 deployer@YOUR_SERVER_IP

# Стало
ssh myproject-admin

Подробнее о ~/.ssh/config, wildcard-правилах и управлении несколькими серверами — в статье про SSH-ключи.

Безопасность SSH-туннелей

Привязка к localhost

По умолчанию -L привязывает проброшенный порт к 127.0.0.1 — доступ только с вашей машины. Явное указание 0.0.0.0 открывает порт для всех в локальной сети:

# Плохо — доступно всем устройствам в сети
ssh -L 0.0.0.0:8080:localhost:3001 user@server

# Правильно — только локально
ssh -L 8080:localhost:3001 user@server

Кстати, мало кто обращает внимание на этот флаг, а он превращает SSH-туннель из безопасного инструмента в дыру — любой в вашей Wi-Fi сети получит доступ к проброшенному сервису.

Не оставлять туннели открытыми

Фоновые туннели (ssh -f -N) работают до явного завершения процесса. Проверять активные туннели:

ps aux | grep "ssh -f"

Для production-серверов стоит иметь в виду один нюанс: неиспользуемый туннель — это открытое SSH-соединение, которое при компрометации локальной машины даёт атакующему прямой путь к серверным сервисам.

Дополнительные меры

SSH-туннель безопасен настолько, насколько безопасен сам SSH-доступ. Базовые меры — отключение паролей, ed25519-ключи, fail2ban, смена стандартного порта — описаны в настройке SSH-ключей и подготовке сервера к production.

Решение типичных проблем

«Address already in use»

Локальный порт уже занят другим процессом:

# Найти, кто занял порт
sudo lsof -i :8080

# Завершить процесс
kill <PID>

# Или использовать другой локальный порт
ssh -L 8081:localhost:3001 deployer@YOUR_SERVER_IP

«Connection refused»

Три причины и диагностика:

  1. Приложение не запущено на сервере
  2. Приложение слушает не на localhost, а на конкретном IP
  3. Docker-контейнер привязан к 172.x.x.x вместо 0.0.0.0
# На сервере — проверить, что сервис слушает
sudo ss -tlnp | grep :3001

# Если слушает на 172.17.0.2:3001 — пробросить через этот IP
ssh -L 8080:172.17.0.2:3001 deployer@YOUR_SERVER_IP

Туннель разрывается

Добавить в ~/.ssh/config:

Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3
    TCPKeepAlive yes

Если разрывы частые — autossh с автоматическим переподключением (описано выше).

FAQ

Чем SSH-туннель отличается от VPN?

SSH-туннель пробрасывает конкретные порты через зашифрованное SSH-соединение. VPN создаёт виртуальный сетевой интерфейс и маршрутизирует весь трафик (или подсеть) через защищённый канал. Для доступа к 2–3 сервисам одного разработчика SSH-туннель поднимается одной командой. Для всей команды с десятком сервисов — WireGuard-VPN удобнее.

Безопасно ли пробрасывать порт PostgreSQL?

Да, если туннель привязан к 127.0.0.1 (по умолчанию). Трафик идёт через зашифрованное SSH-соединение, порт 5432 не открывается в firewall, извне подключиться невозможно. Это безопаснее, чем открывать 5432 в UFW и надеяться на сложный пароль.

Как пробросить порт к Docker-контейнеру?

Если контейнер публикует порт на хост (-p 5050:5050 в docker-compose), пробрасывайте localhost:5050. Если контейнер в изолированной Docker-сети без published-портов, пробрасывайте IP контейнера: ssh -L 5050:172.17.0.3:5050 user@server. IP контейнера — docker inspect container_name | grep IPAddress.

Можно ли автоматизировать запуск туннелей?

Да. Два способа: LocalForward в ~/.ssh/config (туннель создаётся при ssh host-alias) и autossh для фоновых туннелей с автопереподключением. Для systemd-сервера можно создать unit-файл, который поднимает autossh при старте системы.

Полезные ссылки