SSH: или как не застревать на подключении к серверу

SSH — это не просто замена telnet или rlogin. Это инструмент, который должен работать тихо, быстро и без сюрпризов. В реальных проектах от него зависит не только доступ к машинам, но и скорость релизов, безопасность инфраструктуры и даже комфорт команды. Если SSH настроен неправильно, он может стать источником головной боли: от зависаний подключений до утечек данных.

В этой статье разберём, как SSH работает на практике, какие настройки критичны для продакшена, как избежать типичных ловушек и как оптимизировать его под реальные задачи — от ручного администрирования до автоматизации.


Проблема: SSH как болото из конфигов и флагов

В теории SSH — это простой протокол. На практике он превращается в зону, где:

  • Конфиги лежат в разных местах (~/.ssh/config, /etc/ssh/sshd_config, /etc/ssh/ssh_config) и перекрывают друг друга непредсказуемо.
  • Ключи хранятся в неожиданных местах (не только в ~/.ssh/id_rsa, но и в /etc/ssh/ssh_host_* или даже в облачных KMS).
  • Производительность зависит от сотни параметров, которые никто не проверял нагрузочными тестами.
  • Безопасность часто сводится к "запретим root-логин", хотя реальные атаки идут по другим векторам.

Если не разобраться в этих деталях, SSH может:

  • Тормозить из-за неоптимальных параметров ClientAliveInterval или TCPKeepAlive.
  • Падать при сетевых сбоях из-за неверной настройки ServerAliveInterval.
  • Стать уязвимым из-за слабых ключей, открытых портов или лишних сервисов (PermitRootLogin, X11Forwarding).

Практика: как SSH должен работать в продакшене

1. Базовые настройки сервера (sshd_config)

Начнём с минимально жизнеспособного конфига для продакшена. Вот что обязательно должно быть:

# /etc/ssh/sshd_config
Port 2222                     # Нестандартный порт — снижает шум от сканеров
Protocol 2                    # SSHv1 — это музей
PermitRootLogin no            # Корень должен входить только по ключу (или не должен)
PasswordAuthentication no      # Логины по паролю — это legacy
PubkeyAuthentication yes      # Только ключи
AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2
ClientAliveInterval 300       # Пингуем клиента каждые 5 минут, чтобы не кикало
ClientAliveCountMax 3         # Если 3 раза не отвечает — закрываем соединение
TCPKeepAlive yes              # Поддерживаем соединение при простоях
LoginGraceTime 60             # 60 секунд на ввод пароля/ключа — хватит
MaxAuthTries 3                # После 3 неудачных попыток — блокируем IP
MaxSessions 10                # Ограничиваем количество сессий на пользователя
AllowUsers admin devops       # Только конкретные пользователи
DenyGroups *                  # Все остальные — бан
UseDNS no                     # Обратные DNS-запросы — лишняя нагрузка
Banner /etc/ssh/banner.txt    # Предупреждение перед логином

Почему так?

  • Port 2222 — стандартный 22-й порт сканируется постоянно. Сдвиг на 2222 или другой нестандартный порт сразу уменьшает шум.
  • PasswordAuthentication no — пароли — основной вектор атак. Если у вас должны быть пароли (например, для временного доступа), используйте ChallengeResponseAuthentication yes с PAM и ограничьте доступ по времени.
  • ClientAliveInterval — если клиент молчит дольше 5 минут, сервер закрывает соединение. Это спасает от "висящих" подключений при нестабильной сети.
  • MaxAuthTries 3 + Fail2Ban (о нём позже) — стандартный брутфорс становится бесполезным.

2. Конфигурация клиента (~/.ssh/config)

Конфиг клиента — это то, что спасает от рутинной работы. Пример для типичного workflow:

# ~/.ssh/config
Host prod-*                  # Все хосты с префиксом prod-
    User deploy              # Пользователь по умолчанию
    IdentityFile ~/.ssh/id_rsa_prod
    IdentitiesOnly yes       # Использовать только указанные ключи
    Port 2222                # Порт из sshd_config
    ServerAliveInterval 60   # Пингуем сервер каждые 60 секунд
    TCPKeepAlive yes
    LogLevel QUIET            # Тишина, если не нужны детали
    Compression delayed       # Сжимаем только при необходимости
    ProxyJump bastion.prod   # Через бастион (Jump Host)

Host staging
    HostName staging.example.com
    User admin
    IdentityFile ~/.ssh/id_rsa_staging
    ProxyCommand ssh -W %h:%p jump-host.example.com
    LocalForward 8080 localhost:3000  # Локальный порт-форвардинг

Host jump-host.example.com
    User jumpuser
    IdentityFile ~/.ssh/id_rsa_bastion
    StrictHostKeyChecking no  # Для бастионов можно отключить проверку ключей (но не для всех хостов!)

Что здесь критично:

  • ProxyJump/ProxyCommand — это не просто удобство. Это способ контролировать доступ к внутренним сетям. Без бастиона вы рискуете открыть прямой доступ к продакшену.
  • LocalForward — полезно для debug’а сервисов, которые недоступны снаружи. Но не забывайте закрывать такие форварды после работы (ssh -L -R или kill процесс).
  • StrictHostKeyChecking no — опасно для автоматических скриптов. Лучше использовать ssh-keyscan и добавлять ключи в /etc/ssh/ssh_known_hosts или ~/.ssh/known_hosts вручную.

Примеры реальных задач

Задача 1: Автоматизация развёртываний с SSH

Если вы используете ansible, capistrano или свои скрипты на bash/python, SSH должен работать без участия человека. Вот как это сделать надёжно:

  1. Генерация ключей без пароля (для автоматических систем):

    ssh-keygen -t ed25519 -f ~/.ssh/id_rsa_deploy -N ""
    
    • ed25519 — современный и быстрый алгоритм. rsa с длиной ключа 4096 бит тоже подойдёт, но медленнее.
    • -N "" — ключ без пароля. Это опасно для ручных подключений, но необходимо для скриптов.
  2. Разрешение доступа для ключа на сервере:

    mkdir -p ~/.ssh
    echo "ssh-ed25519 AAA... user@host" >> ~/.ssh/authorized_keys
    chmod 700 ~/.ssh
    chmod 600 ~/.ssh/authorized_keys
    
  3. Тестирование подключения из скрипта:

    ssh -i ~/.ssh/id_rsa_deploy -o StrictHostKeyChecking=no -o ConnectTimeout=5 deploy@prod-server "echo 'OK'"
    
    • ConnectTimeout=5 — если сервер не отвечает за 5 секунд, скрипт не висит.
    • StrictHostKeyChecking=no — только если вы уверены, что ключи не изменятся (например, в CI/CD).

Задача 2: Туннелирование и порт-форвардинг

SSH — это не только shell. Это ещё и VPN, и прокси, и обход блокировок.

Пример: доступ к базе данных в приватной сети через бастион

ssh -L 5432:localhost:5432 -N -f user@bastion
  • -L 5432:localhost:5432 — перенаправляем локальный порт 5432 на порт 5432 сервера за бастионом.
  • -N — не запускать shell (нужно только для форвардинга).
  • -f — работать в фоновом режиме.

Пример: SOCKS-прокси для обхода геоблокировок

ssh -D 1080 -N -f user@proxy-server
  • Теперь можно настроить браузер на использование SOCKS5 на порту 1080.

Пример: безопасный доступ к веб-интерфейсу сервиса

ssh -L 8080:localhost:3000 -N -f user@prod-server
  • Открываем http://localhost:8080 в браузере, а трафик идёт через SSH-туннель.

Задача 3: Отладка сетевых проблем

SSH может помочь диагностировать сети. Например, чтобы проверить, почему не работает соединение:

  1. Проверка маршрутизации через SSH:

    ssh -v user@server
    
    • Флаг -v (verbose) покажет все этапы подключения, включая DNS-запросы, установку соединения и аутентификацию.
    • Полезно для отладки проблем с DNS, фаерволами или маршрутизацией.
  2. Перехват трафика с помощью tcpdump через SSH:

    ssh user@server "sudo tcpdump -i eth0 -w - port 80" | ssh user@local "sudo tcpdump -r - -A"
    
    • Запускаем tcpdump на удалённом сервере и перенаправляем вывод обратно через SSH.
    • Получаем трафик в удобном для анализа виде.

Типичные ошибки и как их избежать

Ошибка Последствия Как исправить
Отсутствие ограничения на количество сессий (MaxSessions) Утечка ресурсов, DoS-атаки через открытые SSH-сессии. Установить MaxSessions 10 или меньше.
Использование паролей для аутентификации Брутфорс, утечка хэшей паролей. Перейти на ключи, отключить PasswordAuthentication.
Неправильная настройка ClientAliveInterval Нестабильные соединения, "висящие" процессы. Установить ClientAliveInterval 300 и ServerAliveInterval 60.
Отсутствие Fail2Ban или аналогичного Массовые попытки подбора пароля. Настроить fail2ban с правилом для SSH.
Хранение ключей в репозиториях или на дисках без шифрования Утечка ключей при компрометации системы. Использовать ssh-agent или хранить ключи в HSM/KMS.
Игнорирование StrictHostKeyChecking в скриптах Риск MITM-атак при изменении ключей сервера. Либо отключать проверку только для доверенных хостов, либо использовать ssh-keyscan.
Открытый порт 22 без дополнительной защиты Сканирование, атаки по умолчанию. Сменить порт, настроить фаервол, использовать fail2ban.
Неоптимальные настройки сжатия (Compression) Задержки при медленном соединении. Использовать Compression delayed или отключить сжатие для быстрых сетей.
Использование слабых алгоритмов (ssh-rsa с длиной ключа < 4096 бит) Возможность взлома ключей. Перейти на ed25519 или rsa с длиной 4096+.

Как SSH ведёт себя в экстремальных условиях

1. Высоконагруженные системы

Если SSH используется для массовых подключений (например, в CI/CD или при мониторинге), стоит оптимизировать:

  • Пул соединений: Использовать connection pooling (например, через pmssm или libssh).
  • Алгоритмы: Отдать предпочтение ed25519 или rsa-sha2-512 — они быстрее, чем rsa-sha2-256 с большими ключами.
  • Кэширование ключей: Настроить HashKnownHosts yes в ~/.ssh/config, чтобы не пересчитывать хеши ключей при каждом подключении.

Пример оптимизированного sshd_config для высокой нагрузки:

HostKeyAlgorithms ssh-ed25519,rsa-sha2-512
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com

2. Нестабильные сети

Если соединения часто обрываются:

  • Увеличьте ServerAliveInterval до 120 секунд.
  • Добавьте TCPKeepAlive yes и ClientAliveCountMax 5.
  • Используйте ControlMaster и ControlPath для мультиплексирования соединений (одно соединение — много сессий).

Пример конфига для нестабильных сетей:

# ~/.ssh/config
Host unstable-*
    ServerAliveInterval 120
    ClientAliveCountMax 5
    ControlMaster auto
    ControlPath ~/.ssh/socket-%r@%h:%p
    ControlPersist 1h

3. Защита от атак

Если сервер подвергается атакам:

  1. Настройте fail2ban:
    [sshd]
    enabled = true
    port    = ssh
    filter  = sshd
    logpath = /var/log/auth.log
    maxretry = 3
    bantime = 1h
    
  2. Ограничьте скорость аутентификации:
    AuthenticationMethods publickey
    MaxStartups 10:30:100  # Максимум 10 подключений в секунду, 30 в минуту, 100 в час
    
  3. Используйте RateLimiting (OpenSSH 8.8+):
    RateLimit 30/1h  # Не более 30 попыток аутентификации в час с одного IP
    

Финальный вывод: как SSH должен работать в вашей инфраструктуре

  1. SSH — это не просто подключение, а часть системы безопасности. Если он настроен неправильно, он становится брешью, а не защитой.
  2. Автоматизация требует ключей без пароля, но ключи без пароля нужно хранить безопасно (в ssh-agent, HSM или KMS).
  3. Производительность зависит от алгоритмов и настроек. Не ленитесь тестировать ssh -vvv на реальных нагрузках.
  4. Логи и мониторинг — обязательны. Настройте LogLevel VERBOSE в sshd_config и отправляйте логи в SIEM.
  5. Не изобретайте велосипед. Используйте готовые решения:
    • mosh для стабильных соединений.
    • tmux/screen для сессий, которые не должны обрываться.
    • Ansible/Fabric для автоматизации через SSH.

Проверьте свои настройки прямо сейчас:

# Проверка уязвимостей
ssh-audit -v localhost -p 2222

# Тест скорости подключения
time ssh -o BatchMode=yes -o ConnectTimeout=10 user@server "exit"

Если SSH в вашей инфраструктуре работает без проблем, значит, вы сделали всё правильно. Если нет — начните с базовых настроек и постепенно оптимизируйте. В 90% случаев проблемы решаются правильным sshd_config и ~/.ssh/config.