Что имеем и зачем всё это
У вас дома крутятся полезные сервисы: 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

Сохраняем файл и перезагружаем 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

Запретим вход по паролю для 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

На этом подготовка 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

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
Файл службы

Параметры:
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 и другие сервисы.
Доступ к домашнему серверу через мобильную сеть

Для контроля можно проверить проброс на 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 и обратного прокси

Примечание: для автоматического 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

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 и пишите в комментариях, разберёмся вместе.