VPS-бастион: доступ к домашнему серверу без белого IP

Страницы:  1

Ответить
 

Professor Seleznov


Что имеем и зачем всё это
У вас дома крутятся полезные сервисы: Home Assistant, Jellyfin, code-server или Nextcloud. Или вы только собираетесь их запустить. Пока вы дома, всё открывается по локальному IP. Но стоит выйти за пределы квартиры, и сервисы пропадают. Причина: провайдер выдал серый IP-адрес, то есть применяет CGNAT. Белый адрес получить либо нельзя (примерно, как советский дефицит), либо он стоит дополнительных денег.
Сначала разберёмся с железом. Если домашний сервер уже есть, отлично. Если только планируете, вот варианты под любой бюджет:
  • Российские одноплатники. Repka Pi или Элтай ЭсСи (Eltay SC) от новосибирской компании «Элрон» на базе процессора «Скиф». Прямые аналоги Raspberry Pi на отечественной элементной базе.
  • Китайские одноплатники. Orange Pi, Banana Pi, Radxa. Доступны, имеют активные русскоязычные сообщества, часто предлагают лучшие характеристики за меньшие деньги.
  • Старый ноутбук или ПК. Самый народный путь. Мощности даже 10–15-летнего устройства с избытком хватит для домашнего сервера. У меня старенький, 14-летний Acer, рука не поднялась выбросить, поцарапанный, со сколами, но старый конь борозды не портит.
Возникает резонный вопрос: почему бы просто не арендовать VPS и не перенести все сервисы туда? Формально можно. Но у домашнего сервера есть преимущества, которых нет у VPS за 300–500 рублей:
  • Дисковое пространство. На домашнем сервере у вас может быть терабайт фотографий, фильмов и музыки. VPS с таким объёмом диска стоит совсем других денег.
  • Локальный доступ без интернета. Home Assistant продолжит управлять умным домом, даже если интернет пропадет. А Jellyfin будет раздавать кино по локальной сети без задержек и без расхода трафика.
  • Приватность. Данные лежат у вас дома, а не в дата-центре. Для кого-то это важно, в том числе для меня.
  • Производительность. Старый ноутбук с i5 может быть мощнее бюджетного VPS с одним vCPU.
Поэтому мы не заменяем домашний сервер, а даём ему публичный адрес через недорогой VPS-бастион. Сам VPS выступает только как прокси и не хранит данные.
Что делаем. Мы арендуем VPS с публичным IP ( можно найти конфигурации за 300–500 рублей в месяц). На самом деле проверяйте актуальные цены у провайдеров, так как рынок очень динамичный. Этот VPS будет точкой входа, или бастионом. Домашний сервер сам установит SSH-соединение с VPS и скажет: «Всё, что придёт на твой порт 8123, пересылай мне на локальный порт 8123». Получается зашифрованный туннель из дома наружу, которому не страшен NAT провайдера.
Моя цель — показать не просто работающий, а безопасный и отказоустойчивый метод. Я расскажу, почему для ключа мы выбираем ed25519 с дополнительными раундами KDF, зачем отключаем пароль для пользователя туннеля, как autossh и systemd вместе держат соединение даже при обрывах сети. Всё это сделано, чтобы туннель не падал каждую ночь и чтобы злоумышленник не мог подобрать пароль или ключ.
Такой подход выгоден по нескольким причинам:
  • Не нужно открывать порты на роутере.
  • Трафик между вами и домом шифруется.
  • Работает с любым провайдером, даже если используется CGNAT.
  • Мы не зависим от сторонних сервисов (будь то заблокированный Cloudflare или отечественные решения вроде DDoS-Guard или Qrator Labs) и полностью контролируем свои данные.
Как работает обратный SSH-туннель
Механизм простой. Выполняя команду
ssh -R 8123:localhost:8123 tunneluser@vps_ip
домашний сервер просит удалённую сторону (VPS) начать слушать порт 8123 и всё, что на него приходит, отправлять обратно через это же SSH-соединение на локальный порт 8123 домашней машины.
Когда кто-то из интернета стучится на <IP_VPS>:8123, трафик проходит такой путь:
Клиент → VPS:порт 8123 → SSH-туннель → Домашний сервер:порт 8123
Соединение всегда инициируется изнутри дома наружу, поэтому NAT на стороне провайдера не мешает. Чтобы пробросить несколько сервисов, просто добавляют несколько ключей -R. Чтобы туннель сам восстанавливался после обрывов связи, используют autossh. Аutossh это умная обёртка над обычным SSH.
Шаг 1. Подготовка VPS
Подключаемся к VPS по SSH.
Первым делом открываем конфигурационный файл SSH-сервера:
sudo nano /etc/ssh/sshd_config
Нам нужно явно разрешить проброс портов на все сетевые интерфейсы. По умолчанию SSH пробрасывает порты только на loopback (127.0.0.1), и подключиться к ним извне не получится. Ищем или добавляем две строки:
GatewayPorts yes
AllowTcpForwarding yes

Настройка sshd_config в редакторе nano

pic
Настройка sshd_config: разрешаем проброс портов на все сетевые интерфейсы
Сохраняем файл и перезагружаем sshd, чтобы настройки применились:
sudo systemctl restart sshd
Теперь создадим отдельного пользователя, от имени которого домашний сервер будет устанавливать туннель. Использовать для этого root или своего обычного пользователя небезопасно: если злоумышленник завладеет ключом, он не должен получить возможность выполнять команды на VPS. Пользователю tunneluser мы это запретим, но пока создадим его с обычной оболочкой, чтобы скопировать ключ.
Создаём пользователя и задаём ему временный пароль:
sudo adduser tunneluser --disabled-password
sudo passwd tunneluser
Теперь с домашнего сервера копируем публичный ключ. На домашнем сервере выполните:
ssh-copy-id -i ~/.ssh/id_ed25519.pub tunneluser@<IP_VPS>
После ввода временного пароля ключ будет добавлен. Проверим, что вход работает:
ssh tunneluser@<IP_VPS> hostname
Появится имя VPS и сразу соединение закроется. Если ошибка тогда проверьте права на ~/.ssh и authorized_keys на VPS у пользователя tunneluser.
Теперь, когда ключ скопирован, меняем оболочку на /usr/sbin/nologin, чтобы пользователь не мог выполнять команды. Проброс портов через -R продолжит работать, потому что для этого не нужен shell.
sudo usermod -s /usr/sbin/nologin tunneluser
В некоторых дистрибутивах (включая Ubuntu) /usr/sbin/nologin отсутствует в файле /etc/shells. Тогда sshd при подключении с ключом может отказать, даже если выполняется только проброс. Добавим оболочку в список разрешённых:
echo /usr/sbin/nologin | sudo tee -a /etc/shells
Теперь от имени tunneluser создадим каталог .ssh и выставим правильные права. Временно переключимся в его окружение:
sudo su - tunneluser -s /bin/bash
mkdir -p ~/.ssh
chmod 700 ~/.ssh
exit

Создание каталога .ssh для пользователя tunneluser

pic
Переключаемся в окружение tunneluser, создаём каталог .ssh и выставляем права 700
Запретим вход по паролю для tunneluser. Снова откроем /etc/ssh/sshd_config и в конец добавим:
Match User tunneluser
PasswordAuthentication no
Перезагружаем sshd:
sudo systemctl restart sshd
Теперь tunneluser сможет зайти только по ключу, пароль не сработает.
Также установим пакет psmisc, если его нет. В минимальных образах VPS утилита fuser может отсутствовать, а она понадобится скрипту для освобождения портов после обрыва туннеля.
sudo apt install -y psmisc

Установка пакета psmisc и управление паролем tunneluser

pic
Устанавливаем psmisc (нужен для утилиты fuser) и задаём временный пароль
На этом подготовка VPS завершена.
Шаг 2. Настройка домашнего сервера
Все следующие команды выполняются на вашем домашнем устройстве: Raspberry Pi, Orange Pi, старом ноутбуке и так далее.
2.1. Генерируем SSH-ключ
Для аутентификации на VPS без пароля нам нужна пара ключей. Рекомендую использовать алгоритм ed25519: он быстрее и безопаснее, чем RSA, а ключи получаются короткими. Параметр -a 100 задаёт количество раундов KDF (функции формирования ключа). Если вы решите защитить приватный ключ паролем, это значительно замедлит попытки его подбора. В нашем случае ключ будет без пароля, чтобы autossh мог работать автоматически, но привычка указывать -a 100 полезна на будущее.
ssh-keygen -t ed25519 -a 100 -C "home-to-vps-tunnel"
На запрос пароля нажимаем Enter дважды оставляем пустым.
2.2. Устанавливаем autossh
Обычный SSH не умеет перезапускать туннель при обрыве. Если связь пропадёт, порты на VPS останутся «висеть» в занятом состоянии, и новый туннель не поднимется. autossh решает эту проблему: он мониторит состояние соединения и при необходимости перезапускает процесс.
sudo apt update
sudo apt install -y autossh
2.3. Список сервисов для проброса
Чтобы не редактировать скрипт каждый раз, когда добавляется новый сервис, заведём отдельный файл конфигурации. В нём просто перечисляются пары портов: удалённый порт на VPS и локальный порт домашнего сервера.
sudo nano /etc/sshtunnels/services.conf
Пример содержимого:
# Home Assistant
8123:8123
# Jellyfin
8096:8096
# code-server
8443:8443
# SSH на домашний сервер (нестандартный порт, чтобы не мешать VPS)
2222:22
Формат: удалённый_порт_на_VPS:локальный_порт_домашнего_сервера. Строки, начинающиеся с #, игнорируются.
Обратите внимание на проброс SSH: домашний сервер станет доступен снаружи на порту 2222. Убедитесь, что на домашнем сервере также настроена аутентификация только по ключу, а вход по паролю отключён.
2.4. Пишем скрипт для запуска туннеля
Создадим скрипт, который будет читать services.conf и формировать команду для autossh.
sudo nano /usr/local/bin/tunnel-manager.sh
Скрипт выглядит так (замените REMOTE_HOST и путь к ключу на свои):
#!/bin/bash
REMOTE_USER="tunneluser"
REMOTE_HOST="123.123.123.123" # ваш IP VPS
SSH_KEY="/home/pi/.ssh/id_ed25519" # путь к приватному ключу
SSH_PORT="22"
CONFIG_FILE="/etc/sshtunnels/services.conf"
FORWARD_OPTS=""
while IFS=: read -r remote_port local_port; do
# Пропускаем пустые строки и комментарии
[[ -z "$remote_port" || "$remote_port" =~ ^# ]] && continue
# Убиваем старые процессы на удалённом порту, если туннель упал и порт остался занят
ssh -p "$SSH_PORT" -i "$SSH_KEY" "$REMOTE_USER@$REMOTE_HOST" \
"command -v fuser && fuser -k ${remote_port}/tcp 2>/dev/null || true"
FORWARD_OPTS="$FORWARD_OPTS -R ${remote_port}:localhost:${local_port}"
done < "$CONFIG_FILE"
echo "Запускаем туннели к $REMOTE_HOST с параметрами: $FORWARD_OPTS"
exec autossh -M 0 \
-o "ServerAliveInterval=30" \
-o "ServerAliveCountMax=3" \
-o "ExitOnForwardFailure=yes" \
-o "StrictHostKeyChecking=no" \
-o "GatewayPorts=yes" \
-N -T \
-i "$SSH_KEY" \
-p "$SSH_PORT" \
$FORWARD_OPTS \
"$REMOTE_USER@$REMOTE_HOST"

Скрипт запуска туннеля tunnel-manager.sh

pic
Полный код скрипта tunnel-manager.sh. В строках REMOTE_HOST и SSH_KEY замените значения на свои.
Поясню ключевые моменты.
  • command -v fuser && fuser -k ... || true — сначала проверяем, есть ли fuser на VPS. Если нет, ничего не делаем, и код возврата всегда нулевой. Иначе скрипт мог бы упасть при отсутствии fuser.
  • ExitOnForwardFailure=yes — если по какой-то причине не удалось пробросить хотя бы один порт, SSH немедленно завершится. Тогда systemd зафиксирует падение и перезапустит скрипт.
  • StrictHostKeyChecking=no — отключает проверку отпечатка ключа хоста. Мы доверяем своему VPS, иначе при первом подключении система спросила бы подтверждение и скрипт завис. Если хотите усилить безопасность, выполните на домашнем сервере ssh-keyscan -H <IP_VPS> >> ~/.ssh/known_hosts и замените StrictHostKeyChecking=no на StrictHostKeyChecking=accept-new. В этом случае туннель примет только сохранённый отпечаток.
  • GatewayPorts=yes — на всякий случай дублирует глобальную настройку, гарантируя, что проброшенные порты будут слушать все интерфейсы.
  • -N — не выполнять никаких команд на удалённой стороне, только проброс портов.
  • -T — не выделять псевдотерминал, что уменьшает накладные расходы.
  • ServerAliveInterval=30 и ServerAliveCountMax=3 — каждые 30 секунд SSH отправляет keepalive-пакет. Если три пакета подряд не получат ответа, соединение считается разорванным, и autossh инициирует переподключение.
  • -M 0 — отключает встроенный мониторинговый порт autossh. Мы полагаемся на механизмы keepalive самого SSH и перезапуск через systemd.
Делаем скрипт исполняемым:
sudo chmod +x /usr/local/bin/tunnel-manager.sh
2.5. Создаём systemd-сервис
Чтобы туннель автоматически стартовал при загрузке системы, оформим его как службу systemd.
Пользователям Windows (WSL): стандартная установка WSL не использует systemd. Чтобы команды ниже работали, включите systemd: добавьте в /etc/wsl.conf строки:
[boot]
systemd=true
Затем перезапустите WSL командой wsl --shutdown в PowerShell и снова откройте терминал Ubuntu. Если systemd не нужен, просто запускайте туннель вручную через nohup, но тогда он не будет стартовать автоматически при загрузке.
Создаём файл службы:
sudo nano /etc/systemd/system/autossh-tunnel.service
Содержимое:
[Unit]
Description=Persistent SSH Reverse Tunnel to VPS
After=network-online.target
Wants=network-online.target
[Service]
Type=exec
ExecStart=/usr/local/bin/tunnel-manager.sh
Restart=always
RestartSec=20
StartLimitIntervalSec=0
Environment="AUTOSSH_GATETIME=0"
Environment="AUTOSSH_POLL=60"
[Install]
WantedBy=multi-user.target

Файл службы

pic
Содержимое unit-файла для автоматического запуска и перезапуска туннеля
Параметры:
  • After=network-online.target — служба стартует только после полной инициализации сети.
  • Restart=always — перезапускать при любом завершении, кроме явной остановки через systemctl stop.
  • RestartSec=20 — ждать 20 секунд перед повторной попыткой. Небольшая пауза полезна, чтобы не заддосить VPS при быстро повторяющихся ошибках.
  • AUTOSSH_GATETIME=0 — считать первое соединение успешным сразу, без задержки.
  • AUTOSSH_POLL=60 — интервал, с которым autossh опрашивает состояние соединения (в дополнение к ServerAliveInterval).
Шаг 3. Запуск и проверка
Перед запуском туннеля на VPS нужно открыть порты, которые мы будем пробрасывать. Иначе туннель установится, но подключиться извне не получится. Выполните на VPS:
sudo ufw allow 8123/tcp
sudo ufw allow 8096/tcp
sudo ufw allow 8443/tcp
sudo ufw allow 2222/tcp
sudo ufw enable
Если вы пробрасываете дополнительные порты, откройте и их, например sudo ufw allow 8080/tcp.
Теперь на домашнем сервере активируем и запускаем службу:
sudo systemctl daemon-reload
sudo systemctl enable autossh-tunnel
sudo systemctl start autossh-tunnel
Проверяем статус:
sudo systemctl status autossh-tunnel
Теперь возьмите телефон, отключите Wi-Fi (чтобы трафик шёл через мобильную сеть) и откройте браузер. В адресной строке введите http://<IP\_VPS>:8123. Должен открыться интерфейс Home Assistant. Аналогично проверьте Jellyfin на порту 8096 и другие сервисы.

Доступ к домашнему серверу через мобильную сеть

pic
Доступ с телефона через мобильную сеть
Для контроля можно проверить проброс на VPS:
ss -tulpn | grep 8123
Если не работает:
  • Убедитесь, что на домашнем сервере сервис действительно слушает нужный порт: ss -tulpn | grep 8123. Если ss отсутствует, используйте netstat -tulpn | grep 8123 или lsof -i :8123.
  • Проверьте, не занят ли этот порт на самом VPS каким-то процессом: ss -tulpn | grep 8123.
  • Возможно, на VPS включен файрвол ufw, и порты закрыты. Разрешите их, как описано выше.
  • Если на VPS не найдена утилита fuser, установите её: sudo apt install -y psmisc. Она нужна скрипту для очистки портов после обрыва туннеля.
Типичная ситуация: если VPS перезагрузился в тот момент, когда туннель был активен, порты на VPS могут на короткое время остаться в состоянии TIME_WAIT. autossh с настройками выше переподнимет соединение, но возможна задержка до минуты. Если порт долго не освобождается, зайдите на VPS и проверьте ss -tulpn | grep <порт>. При необходимости завершите зависший процесс вручную.
Шаг 4. Базовая безопасность
4.1. Файрвол на VPS
Мы уже открыли нужные порты в ufw. Если вы всегда подключаетесь с одного IP (например, с работы), можно дополнительно ограничить доступ к чувствительным сервисам:
sudo ufw allow from <ваш_статический_IP> to any port 8123
После настройки Caddy и переноса сервисов на HTTPS прямые порты рекомендуется закрыть (см. раздел 4.3).
4.2. Регулярная замена ключей
Раз в полгода генерируйте новую пару ключей на домашнем сервере и заменяйте публичный ключ на VPS в /home/tunneluser/.ssh/authorized_keys. Так вы перестрахуетесь на случай, если старый ключ скомпрометируют, то есть попросту угонят.
4.3. HTTPS для сервисов
Пока мы ходим по HTTP, трафик не зашифрован между клиентом и VPS. Чтобы это исправить, нужен обратный прокси с SSL. Самый простой вариант — Caddy, потому что он автоматически получает сертификаты Let’s Encrypt и требует минимум настроек.
Установка на VPS:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
Конфигурация (/etc/caddy/Caddyfile):
hass.my-domain.ru {
reverse_proxy localhost:8123
}
jellyfin.my-domain.ru {
reverse_proxy localhost:8096
}
code.my-domain.ru {
reverse_proxy localhost:8443
}

Конфигурация Caddy для HTTPS и обратного прокси

pic
Пример Caddyfile с поддоменами для Home Assistant, Jellyfin и code-server
Примечание: для автоматического HTTPS нужен реальный домен, направленный A-записью на IP вашего VPS. Если домена пока нет, Caddy можно использовать как обычный обратный прокси. Минимальная конфигурация для теста:
:80 {
reverse_proxy localhost:8080
}
После привязки домена замените :80 на ваш поддомен (например, hass.my-domain.ru) — Caddy сам запросит сертификат и настроит HTTPS.
Перезапуск:
sudo systemctl restart caddy
Проверим локально, что Caddy проксирует запросы:
curl -I http://localhost:80

Проверка работы Caddy

pic
Ответ curl -I http://localhost:80 подтверждает, что Caddy проксирует запросы (HTTP 200 OK)
Caddy сам запросит сертификаты, и через минуту ваши сервисы будут доступны по HTTPS с человеческими адресами. После того как Caddy заработал, прямые порты можно закрыть, чтобы сервисы не светились по HTTP:
sudo ufw delete allow 8123/tcp
sudo ufw delete allow 8096/tcp
sudo ufw delete allow 8443/tcp
Теперь трафик будет ходить только через зашифрованные соединения на 443 порт.
При желании можно добавить запрос пароля на уровне Caddy:
sudo apt install apache2-utils
htpasswd -c /etc/caddy/.htpasswd username
И в блок сервиса добавить:
hass.my-domain.ru {
basicauth {
username $2a$14$...
}
reverse_proxy localhost:8123
}
Хэш пароля берётся из созданного файла.
Примечание: при использовании обратного прокси все сервисы будут видеть запросы как пришедшие с 127.0.0.1, что может мешать анализу логов. В продвинутых сценариях это исправляется добавлением в конфигурацию Caddy директивы trusted_proxies и пробросом реального IP клиента. Для базовой установки такое поведение обычно не критично.
Шаг 5. Задержка
Добавление VPS в цепочку неизбежно увеличивает пинг, но SSH-туннель вносит минимальный оверхед. Если VPS находится в том же регионе, что и вы, задержка вырастает на 2–8 миллисекунд. Это практически незаметно даже при просмотре видео.
Альтернативы
Если autossh и ручная настройка кажутся громоздкими, можно посмотреть на утилиты вроде rathole, frp или bore. Они тоже делают проброс портов, но требуют установки своего ПО как на домашний сервер, так и на VPS. Наш метод хорош тем, что на VPS не нужно ставить ничего, кроме штатного SSH-сервера, который уже есть в любой Ubuntu.
Итог
Теперь у вас есть полноценный доступ к домашним сервисам из любой точки мира. Всё, что для этого потребовалось: VPS за 300–500 рублей в месяц и полчаса на настройку. Туннель работает прозрачно и шифрует трафик по пути. Самое важное: мы сделали его устойчивым к обрывам и защищённым от самых очевидных атак. Надеюсь, этот туториал поможет вам наладить свою инфраструктуру без лишней головной боли. Если что-то пошло не так, заглядывайте в логи journalctl -u autossh-tunnel и пишите в комментариях, разберёмся вместе.-Источник
 
Loading...
Error