SSH Port Forwarding — безопасный доступ к серверу
Как получить доступ к pgAdmin, Grafana и админ-панелям через SSH-туннель без открытия портов в интернет. Практические примеры и автоматизация.
SSH Port Forwarding (туннелирование) — это проброс сетевого трафика через зашифрованное SSH-соединение. Позволяет получить доступ к pgAdmin, Grafana, RabbitMQ и другим сервисам на сервере, не открывая их порты в интернет. Одна команда заменяет настройку VPN, правил firewall и дополнительной аутентификации.
Если SSH-ключи на сервере ещё не настроены — начните с гайда по SSH-ключам на Ubuntu. Там же описан ~/.ssh/config, который пригодится для автоматизации туннелей дальше в этой статье.
Когда SSH-туннель лучше альтернатив
SSH-туннель не единственный способ закрыть админ-панели от интернета. Но в большинстве случаев он оптимален:
| Решение | Когда подходит | Когда избыточно |
|---|---|---|
| SSH Port Forwarding | 1–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 |
| pgAdmin | http://localhost:5050 |
| Grafana | http://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»
Три причины и диагностика:
- Приложение не запущено на сервере
- Приложение слушает не на
localhost, а на конкретном IP - 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 при старте системы.
Полезные ссылки
- SSH-ключи на Ubuntu — генерация, настройка, безопасность — ed25519, config, agent, fail2ban
- Настройка Ubuntu VPS для production — 8 шагов — firewall, Docker, Nginx, SSL, бэкапы
- Интерактивный чеклист: подготовка сервера к production — те же шаги с отслеживанием прогресса
- OpenSSH Manual: ssh
- autossh — monitor and restart SSH sessions