Аннотация
Если домашний сервер находится за NAT или CGNAT, не имеет белого IP-адреса, а проброс портов на роутере невозможен или нежелателен, сервисы всё равно можно опубликовать безопасно. Один из практичных вариантов — использовать VPS как публичную точку входа, а домашний сервер подключать к нему через обратный SSH-туннель.
В такой схеме домашний сервер сам инициирует исходящее SSH-соединение к VPS. На стороне VPS создаётся локальный TCP endpoint, который через SSH-туннель ведёт к сервису на домашнем сервере. Внешний HTTPS-трафик принимает Caddy, после чего проксирует запросы на локальный адрес туннеля.
Базовая схема:
Пользователь │ │ HTTPS :443 ▼ VPS с публичным IP │ │ Caddy reverse proxy / TLS termination ▼ 127.0.0.1:11000 на VPS │ │ Reverse SSH tunnel ▼ Домашний сервер │ ▼ Nextcloud / Home Assistant / Jellyfin / code-server / другой сервис
Главный принцип безопасности: backend-сервисы не публикуются в интернет прямыми портами. Наружу доступны только
80/tcp,443/tcpи, при необходимости, SSH-порт для администрирования VPS. Все прикладные сервисы остаются доступными только через loopback на VPS и reverse proxy.
1. Что получится в результате
После настройки будет получена следующая схема:
домен вида
cloud.example.com;HTTPS через Caddy;
домашний сервис за NAT/CGNAT;
reverse SSH tunnel через VPS;
автозапуск туннеля через
systemd;автоматическое восстановление соединения через
autossh;backend-порты, закрытые от прямого доступа из интернета.
Итоговый маршрут запроса:
Пользователь → HTTPS → VPS/Caddy → 127.0.0.1 на VPS → SSH-туннель → домашний сервер
2. Цель и границы решения
В статье рассматривается практическая конфигурация:
Домашний сервер → исходящее SSH-соединение → VPS VPS: 127.0.0.1:11000 → SSH-туннель → домашний сервер: 127.0.0.1:8080 Интернет → https://cloud.example.com → Caddy на VPS → 127.0.0.1:11000
Используемые компоненты:
HOME_SERVER— домашний Linux-сервер, мини-ПК, Raspberry Pi, NAS или иной узел за NAT/CGNAT;VPS— внешний сервер с публичным IPv4/IPv6-адресом;tunneluser— ограниченная учётная запись на VPS только для remote port forwarding;OpenSSH— транспортный уровень и механизмssh -R;autossh— автоматическое восстановление SSH-сессии;systemd— запуск и удержание туннеля как службы;Caddy— reverse proxy, TLS-терминация и маршрутизация HTTP(S);UFW— базовый firewall на VPS.
Примеры ориентированы на Ubuntu/Debian-подобные системы. На других дистрибутивах логика та же, но могут отличаться имена пакетов, пути и команды управления службами.
3. Проверено на
Примерная тестовая среда:
VPS: Ubuntu Server 24.04 LTS Домашний сервер: Debian/Ubuntu-подобная Linux-система SSH: OpenSSH из системного репозитория Reverse proxy: Caddy 2 Firewall: UFW Автовосстановление туннеля: autossh Supervisor: systemd
Если используется другой дистрибутив, могут отличаться:
имя SSH-службы:
sshилиsshd;способ установки Caddy;
расположение конфигурационных файлов;
правила firewall;
версия OpenSSH и поддержка отдельных параметров
authorized_keys.
4. Предварительные условия
4.1. Требования к VPS
На стороне VPS необходимы:
публичный IPv4 или IPv6;
административный доступ по SSH;
домен или поддомен, указывающий на IP-адрес VPS;
Linux-система, например Ubuntu Server 22.04/24.04 или Debian 12;
права
rootлибо пользователь сsudo;открытые порты
80/tcpи443/tcpдля Caddy;доступный SSH-порт для администрирования.
4.2. Требования к домашнему серверу
На стороне домашнего сервера необходимы:
Linux-система с исходящим доступом в интернет;
установленный OpenSSH-клиент;
сервис, который нужно опубликовать через VPS;
возможность установить
autossh;локальная доступность публикуемого сервиса.
Если сервис слушает 127.0.0.1:8080, проверяем:
curl -I http://127.0.0.1:8080
Или:
curl -I http://localhost:8080
Если сервис локально не отвечает, сначала нужно исправить работу backend-приложения, а уже потом настраивать туннель.
5. Концептуальная модель reverse SSH tunnel
SSH-соединение инициирует домашний сервер:
Домашний сервер → VPS
Но endpoint для доступа появляется на стороне VPS:
VPS: локальный порт → SSH-туннель → домашний сервис
Пример:
ssh -N \ -R 127.0.0.1:11000:127.0.0.1:8080 \ tunneluser@203.0.113.10
Разбор параметра -R:
-R 127.0.0.1:11000:127.0.0.1:8080 │ │ │ │ │ │ │ └─ порт сервиса на домашнем сервере │ │ └────────── адрес сервиса на домашнем сервере │ └──────────────── порт, создаваемый на VPS └────────────────────────── адрес прослушивания на VPS
На VPS появляется локальный endpoint:
127.0.0.1:11000
Через SSH-туннель он связан с домашним сервисом:
127.0.0.1:8080
Важно указывать именно
127.0.0.1на стороне VPS. Тогда порт туннеля не становится публичным сетевым интерфейсом и не принимает входящие соединения напрямую из интернета. Внешний трафик должен идти только через Caddy.
6. Подготовка VPS
Подключаемся к серверу:
ssh root@203.0.113.10
Обновляем систему:
apt update && apt upgrade -y
Устанавливаем базовые утилиты:
apt install -y curl wget git ufw htop nano ca-certificates gnupg lsb-release
Проверяем версию дистрибутива:
lsb_release -a
Проверяем внешний IPv4:
curl -4 ifconfig.me && echo
До завершения настройки SSH лучше не закрывать текущую SSH-сессию. Ошибка в sshd_config может привести к потере удалённого доступа.
7. Создание пользователя для туннеля
Создаём отдельного пользователя:
adduser --disabled-password --gecos "" tunneluser
Создаём каталог для SSH-ключей:
mkdir -p /home/tunneluser/.ssh chmod 700 /home/tunneluser/.ssh chown -R tunneluser:tunneluser /home/tunneluser/.ssh
Отключаем интерактивный shell:
usermod -s /usr/sbin/nologin tunneluser
Проверяем:
getent passwd tunneluser
Ожидаемый результат должен содержать:
/usr/sbin/nologin
Этот пользователь не предназначен для обычного входа в систему. Его задача — аутентификация по ключу и создание разрешённых remote forwarding endpoints.
8. Генерация SSH-ключа на домашнем сервере
На домашнем сервере создаём отдельную ключевую пару только для туннеля:
ssh-keygen -t ed25519 -f ~/.ssh/vps_reverse_tunnel_ed25519 -C "home-server-to-vps-reverse-tunnel"
Для автоматического запуска через systemd passphrase обычно оставляют пустым. Более строгая схема с passphrase, ssh-agent или аппаратным ключом возможна, но усложняет unattended-запуск после перезагрузки.
Проверяем файлы:
ls -la ~/.ssh/vps_reverse_tunnel_ed25519*
Ожидаются:
vps_reverse_tunnel_ed25519 vps_reverse_tunnel_ed25519.pub
Выводим публичный ключ:
cat ~/.ssh/vps_reverse_tunnel_ed25519.pub
Ключ будет выглядеть примерно так:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... home-server-to-vps-reverse-tunnel
Приватный ключ остаётся только на домашнем сервере. На VPS передаётся только публичный ключ.
9. Установка публичного ключа на VPS
На VPS открываем файл:
nano /home/tunneluser/.ssh/authorized_keys
Вставляем публичный ключ домашнего сервера.
Задаём права:
chmod 600 /home/tunneluser/.ssh/authorized_keys chown tunneluser:tunneluser /home/tunneluser/.ssh/authorized_keys
Проверяем:
ls -la /home/tunneluser/.ssh
Ожидаемые права доступа:
drwx------ 2 tunneluser tunneluser 4096 ... . -rw------- 1 tunneluser tunneluser 120 ... authorized_keys
Некорректные права на .ssh или authorized_keys — частая причина отказа OpenSSH в аутентификации по ключу.
10. Ограничение OpenSSH на стороне VPS
Для tunneluser разрешаем только функционал, необходимый для обратного TCP forwarding. Не нужно глобально включать GatewayPorts yes.
Создаём отдельный конфиг:
nano /etc/ssh/sshd_config.d/90-reverse-tunnel.conf
Добавляем:
# The tunnel account is permitted to create remote port forwards only. # Interactive access and unrelated forwarding mechanisms are disabled. Match User tunneluser AllowTcpForwarding remote GatewayPorts no PermitTTY no X11Forwarding no AllowAgentForwarding no PermitTunnel no PasswordAuthentication no PubkeyAuthentication yes
Указание параметров:
AllowTcpForwarding remote— разрешает только remote forwarding (ssh -R), без local forwarding;GatewayPorts no— удерживает remote forwarding на loopback-интерфейсах и не позволяет открыть проброшенный порт на внешних интерфейсах VPS;PermitTTY no— запрещает интерактивный терминал;X11Forwarding no— отключает X11 forwarding;AllowAgentForwarding no— запрещает проброс SSH-agent;PermitTunnel no— запрещает TUN/TAP-туннели;PasswordAuthentication no— запрещает парольную аутентификацию для этого пользователя;PubkeyAuthentication yes— оставляет аутентификацию по публичному ключу.
Проверяем конфиг:
sshd -t
Если ошибок нет, перезапускаем SSH:
systemctl restart ssh
На некоторых системах:
systemctl restart sshd
Проверяем статус:
systemctl status ssh --no-pager
Практическое правило: **никогда не перезапускайте SSH после изменения конфигурации без предварительного **sshd -t.
11. Первичная проверка туннеля вручную
На домашнем сервере добавляем host key VPS в known_hosts:
ssh-keyscan -H 203.0.113.10 >> ~/.ssh/known_hosts
В production-сценарии fingerprint ключа желательно сверить с данными из консоли VPS-провайдера или с выводом на самом сервере. ssh-keyscan удобен для автоматизации, но сам по себе не доказывает, что ключ получен именно от нужного сервера.
Проверяем ключевую аутентификацию:
ssh -i ~/.ssh/vps_reverse_tunnel_ed25519 tunneluser@203.0.113.10
Так как у пользователя установлен /usr/sbin/nologin, интерактивный вход может завершиться сообщением:
This account is currently not available.
Для интерактивного входа это нормально. Для туннеля используется режим -N, без shell.
Проверяем reverse tunnel:
ssh -N \ -i ~/.ssh/vps_reverse_tunnel_ed25519 \ -o ExitOnForwardFailure=yes \ -o ServerAliveInterval=30 \ -o ServerAliveCountMax=3 \ -o StrictHostKeyChecking=yes \ -R 127.0.0.1:11000:127.0.0.1:8080 \ tunneluser@203.0.113.10
Команда должна остаться в foreground-режиме.
Во втором терминале подключаемся к VPS:
ssh root@203.0.113.10
Проверяем listener:
ss -tulpn | grep 11000
Ожидаемый результат:
tcp LISTEN 0 128 127.0.0.1:11000 ... sshd
Проверяем HTTP через туннель:
curl -I http://127.0.0.1:11000
Если backend отвечает, транспортная схема работает.
12. Установка autossh на домашнем сервере
Обычный ssh -N -R подходит для ручной проверки, но для эксплуатации нужен автоперезапуск.
Устанавливаем autossh:
sudo apt update sudo apt install -y autossh
Проверяем:
autossh -V
13. Создание systemd-службы
systemd будет запускать autossh, удерживать процесс и перезапускать службу при сбоях.
13.1. Простой unit-файл
Предположим, локальный пользователь на домашнем сервере называется andrei.
Создаём unit:
sudo nano /etc/systemd/system/reverse-tunnel.service
Содержимое:
[Unit] Description=Reverse SSH tunnel to VPS After=network-online.target Wants=network-online.target [Service] User=andrei Environment="AUTOSSH_GATETIME=0" Environment="AUTOSSH_PORT=0" ExecStart=/usr/bin/autossh -M 0 -N \ -i /home/andrei/.ssh/vps_reverse_tunnel_ed25519 \ -o ExitOnForwardFailure=yes \ -o ServerAliveInterval=30 \ -o ServerAliveCountMax=3 \ -o StrictHostKeyChecking=yes \ -R 127.0.0.1:11000:127.0.0.1:8080 \ tunneluser@203.0.113.10 Restart=always RestartSec=10 [Install] WantedBy=multi-user.target
Активируем:
sudo systemctl daemon-reload sudo systemctl enable reverse-tunnel.service sudo systemctl start reverse-tunnel.service
Проверяем:
systemctl status reverse-tunnel.service --no-pager
Логи:
journalctl -u reverse-tunnel.service -f
13.2. Instance-unit
Если нужен параметризованный unit:
sudo nano /etc/systemd/system/reverse-tunnel@.service
Содержимое:
[Unit] Description=Reverse SSH tunnel to VPS for %i After=network-online.target Wants=network-online.target [Service] User=%i Environment="AUTOSSH_GATETIME=0" Environment="AUTOSSH_PORT=0" ExecStart=/usr/bin/autossh -M 0 -N \ -i /home/%i/.ssh/vps_reverse_tunnel_ed25519 \ -o ExitOnForwardFailure=yes \ -o ServerAliveInterval=30 \ -o ServerAliveCountMax=3 \ -o StrictHostKeyChecking=yes \ -R 127.0.0.1:11000:127.0.0.1:8080 \ tunneluser@203.0.113.10 Restart=always RestartSec=10 [Install] WantedBy=multi-user.target
Для пользователя andrei:
sudo systemctl daemon-reload sudo systemctl enable reverse-tunnel@andrei.service sudo systemctl start reverse-tunnel@andrei.service
14. Проверка после запуска службы
На VPS:
ss -tulpn | grep 11000
Корректный результат должен содержать:
127.0.0.1:11000
Проверяем backend:
curl -I http://127.0.0.1:11000
15. Firewall на VPS
Настраиваем UFW:
ufw default deny incoming ufw default allow outgoing
Разрешаем SSH:
ufw allow 22/tcp
Если SSH работает не на 22/tcp, разрешаем фактический порт, например:
ufw allow 2222/tcp
Разрешаем HTTP и HTTPS:
ufw allow 80/tcp ufw allow 443/tcp
Перед включением firewall убедитесь, что разрешён именно тот SSH-порт, через который вы сейчас подключены.
Включаем firewall:
ufw enable
Проверяем:
ufw status verbose
Ожидаемый набор:
22/tcp ALLOW IN 80/tcp ALLOW IN 443/tcp ALLOW IN
Порт 11000 открывать не нужно. Он должен слушать только 127.0.0.1.
16. Установка Caddy на VPS
Устанавливаем Caddy из официального репозитория:
apt install -y debian-keyring debian-archive-keyring apt-transport-https curl curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list apt update apt install -y caddy
Проверяем:
caddy version
Статус службы:
systemctl status caddy --no-pager
17. DNS-настройка
В DNS-зоне создаём запись:
Type: A Name: cloud Value: 203.0.113.10
Для IPv6 можно добавить:
Type: AAAA Name: cloud Value: 2001:db8::10
Проверяем:
dig +short cloud.example.com
Или:
nslookup cloud.example.com
До тех пор пока домен не указывает на VPS, автоматическая выдача TLS-сертификата Caddy может не сработывать
Если домен проксируется через Cloudflare, на время первичной проверки можно использовать “DNS-only”. После выпуска сертификата следует отдельно проверить режим SSL/TLS: обычно это Full или Full Strict, в зависимости от схемы сертификатов.
18. Конфигурация Caddy
Открываем Caddyfile:
nano /etc/caddy/Caddyfile
Минимальная конфигурация:
cloud.example.com { reverse_proxy 127.0.0.1:11000 }
Проверяем:
caddy validate --config /etc/caddy/Caddyfile
Форматируем:
caddy fmt --overwrite /etc/caddy/Caddyfile
Перезагружаем Caddy:
systemctl reload caddy
Проверяем:
systemctl status caddy --no-pager
Проверяем HTTPS:
curl -I https://cloud.example.com
При корректном DNS и доступности портов 80/tcp и 443/tcp Caddy автоматически выпустит TLS-сертификат.
19. Security headers в Caddy
Для типового web-приложения можно добавить базовые HTTP security headers:
cloud.example.com { encode zstd gzip header { Strict-Transport-Security "max-age=31536000; includeSubDomains" X-Content-Type-Options "nosniff" X-Frame-Options "SAMEORIGIN" Referrer-Policy "strict-origin-when-cross-origin" Permissions-Policy "geolocation=(), microphone=(), camera=()" } reverse_proxy 127.0.0.1:11000 }
Для сложных приложений, особенно Nextcloud, заголовки следует подбирать осторожно. Чрезмерно жёсткая политика может нарушить WebDAV, загрузки файлов, предпросмотр, интеграции или мобильные клиенты.
Отдельно стоит относиться к директиве preload в HSTS. Директиву не следует включать механически: сначала нужно убедиться, что домен и все необходимые поддомены стабильно работают по HTTPS. Для учебного стенда или временного домена лучше использовать HSTS без preload либо временно не задавать HSTS вовсе.
20. Публикация нескольких сервисов
Допустим, на домашнем сервере работают:
Nextcloud 127.0.0.1:8080 Home Assistant 127.0.0.1:8123 Jellyfin 127.0.0.1:8096
В unit-файле можно объявить несколько remote forwards:
ExecStart=/usr/bin/autossh -M 0 -N \ -i /home/andrei/.ssh/vps_reverse_tunnel_ed25519 \ -o ExitOnForwardFailure=yes \ -o ServerAliveInterval=30 \ -o ServerAliveCountMax=3 \ -o StrictHostKeyChecking=yes \ -R 127.0.0.1:11000:127.0.0.1:8080 \ -R 127.0.0.1:18123:127.0.0.1:8123 \ -R 127.0.0.1:18096:127.0.0.1:8096 \ tunneluser@203.0.113.10
На VPS Caddyfile:
cloud.example.com { reverse_proxy 127.0.0.1:11000 } home.example.com { reverse_proxy 127.0.0.1:18123 } media.example.com { reverse_proxy 127.0.0.1:18096 }
Проверяем и применяем:
caddy validate --config /etc/caddy/Caddyfile systemctl reload caddy
21. Особенности Nextcloud
Если Nextcloud на домашнем сервере слушает порт 11000, remote forward может быть таким:
-R 127.0.0.1:11000:127.0.0.1:11000
Caddyfile на VPS:
cloud.example.com { reverse_proxy 127.0.0.1:11000 }
Для Nextcloud важны trusted domains и proxy-настройки. В Docker-инсталляции команда может выглядеть так:
docker exec -u www-data nextcloud php occ config:system:set trusted_domains 1 --value=cloud.example.com
Если приложение видит запросы через reverse proxy и туннель, может потребоваться явно задать HTTPS-схему:
docker exec -u www-data nextcloud php occ config:system:set overwriteprotocol --value=https
Проверяем trusted domains:
docker exec -u www-data nextcloud php occ config:system:get trusted_domains
В более сложных конфигурациях дополнительно проверяются trusted_proxies, overwritehost, overwrite.cli.url, X-Forwarded-For, X-Forwarded-Proto и WebDAV.
22. Ограничение ключа в authorized_keys
Дополнительно можно наложить ограничения прямо на строку ключа в authorized_keys.
Открываем:
nano /home/tunneluser/.ssh/authorized_keys
Для remote forwarding полезен параметр permitlisten, если версия OpenSSH его поддерживает:
no-pty,no-agent-forwarding,no-X11-forwarding,permitlisten="127.0.0.1:11000" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... home-server-to-vps-reverse-tunnel
Для нескольких туннелей:
no-pty,no-agent-forwarding,no-X11-forwarding,permitlisten="127.0.0.1:11000",permitlisten="127.0.0.1:18123",permitlisten="127.0.0.1:18096" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... home-server-to-vps-reverse-tunnel
permitopen и permitlisten — не одно и то же. Для ssh -R существеннее именно ограничение адресов и портов прослушивания на удалённой стороне.
После изменения authorized_keys нужно заново проверить запуск туннеля.
23. Диагностика
23.1. Состояние службы на домашнем сервере
systemctl status reverse-tunnel.service --no-pager
23.2. Журнал службы
journalctl -u reverse-tunnel.service -f
23.3. Проверка listener на VPS
ss -tulpn | grep 11000
Корректно:
127.0.0.1:11000
Нежелательно:
0.0.0.0:11000
Если порт слушает 0.0.0.0, он привязан ко всем интерфейсам. Для этой схемы это ошибка конфигурации.
23.4. Проверка HTTP через туннель
На VPS:
curl -I http://127.0.0.1:11000
23.5. Проверка Caddy
systemctl status caddy --no-pager
journalctl -u caddy -f
23.6. Проверка Caddyfile
caddy validate --config /etc/caddy/Caddyfile
23.7. Проверка DNS
dig +short cloud.example.com
23.8. Проверка HTTPS
curl -Iv https://cloud.example.com
Диагностика:
DNS → Caddy/TLS → локальный endpoint на VPS → SSH-туннель → backend на домашнем сервере
24. Типовые неисправности
24.1. Порт на VPS занят
Симптом:
remote port forwarding failed for listen port 11000
Проверка:
ss -tulpn | grep 11000
Решение: освободить порт, остановить конфликтующий процесс или выбрать другой локальный порт.
24.2. Caddy возвращает 502 Bad Gateway
502 означает, что Caddy не может получить корректный ответ от upstream.
Проверяем на VPS:
curl -I http://127.0.0.1:11000
Если ответа нет, проблема ниже по цепочке: SSH-туннель, systemd-служба, домашний сервис или сеть.
24.3. SSH-туннель не поднимается
Проверяем журнал:
journalctl -u reverse-tunnel.service -n 100 --no-pager
Запускаем SSH в verbose-режиме:
ssh -vvv -N \ -i ~/.ssh/vps_reverse_tunnel_ed25519 \ -R 127.0.0.1:11000:127.0.0.1:8080 \ tunneluser@203.0.113.10
По выводу -vvv обычно видно, где проблема: аутентификация, forwarding, host key или конфликт порта.
24.4. DNS ещё не обновился
Проверка:
dig +short cloud.example.com
Если домен не указывает на VPS, Caddy не сможет корректно обслужить публичный HTTPS endpoint.
24.5. Некорректные права на ключи
На домашнем сервере:
chmod 700 ~/.ssh chmod 600 ~/.ssh/vps_reverse_tunnel_ed25519 chmod 644 ~/.ssh/vps_reverse_tunnel_ed25519.pub
На VPS:
chmod 700 /home/tunneluser/.ssh chmod 600 /home/tunneluser/.ssh/authorized_keys chown -R tunneluser:tunneluser /home/tunneluser/.ssh
25. Минимальный hardening VPS
Устанавливаем firewall, защиту SSH от перебора и автоматические security updates:
apt update && apt upgrade -y apt install -y ufw fail2ban unattended-upgrades
Включаем unattended upgrades:
dpkg-reconfigure --priority=low unattended-upgrades
Firewall:
ufw default deny incoming ufw default allow outgoing ufw allow 22/tcp ufw allow 80/tcp ufw allow 443/tcp ufw enable
Проверяем:
ufw status verbose
SSH-конфиг:
sshd -t
Открытые порты:
ss -tulpn
Снаружи не должны быть опубликованы backend-порты:
8080 8096 8123 11000 18123 18096
Такие порты либо не должны слушать внешние интерфейсы, либо должны быть привязаны к 127.0.0.1.
26. SSH-доступ к домашнему серверу через VPS
Иногда нужен административный доступ к домашнему серверу. Его можно организовать отдельным reverse tunnel:
-R 127.0.0.1:12222:127.0.0.1:22
После этого с VPS можно подключиться к домашнему серверу:
ssh -p 12222 homeuser@127.0.0.1
Публиковать этот порт наружу не рекомендуется. Плохой вариант:
-R 0.0.0.0:2222:127.0.0.1:22
Так SSH домашнего сервера фактически оказывается в интернете через VPS.
Более безопасный паттерн:
Администратор → SSH на VPS → SSH через 127.0.0.1:12222 → домашний сервер
27. Сравнение с альтернативами
Подход | Когда уместен | Сильные стороны | Ограничения |
|---|---|---|---|
Reverse SSH tunnel через VPS | Нужен контроль над всей цепочкой | Простота, прозрачность, отсутствие vendor lock-in | Нужно самому администрировать безопасность |
WireGuard через VPS | Нужна приватная overlay-сеть | Производительность, криптографическая строгость | Более сложная первичная настройка |
Cloudflare Tunnel | Нужен быстрый HTTPS без своего ingress | Простота, Cloudflare Access | Зависимость от Cloudflare |
Tailscale | Доступ нужен себе или малой группе | Удобство, WireGuard-based | Публичная публикация требует дополнительных решений |
frp/rathole | Много reverse endpoints за NAT | Инструменты специально для reverse proxy/tunneling | Нужны отдельные server/client-компоненты |
Reverse SSH tunnel уместен, когда нужна минимальная зависимость от внешних платформ и понятная эксплуатационная модель на стандартных компонентах Linux.
28. Рекомендуемая production-схема
Итоговая модель:
Internet │ │ HTTPS ▼ VPS: Caddy :443 │ │ reverse_proxy to loopback ▼ VPS: 127.0.0.1:11000 │ │ reverse SSH tunnel ▼ Home Server: 127.0.0.1:8080
Свойства корректной конфигурации:
туннель слушает только
127.0.0.1на VPS;внешний HTTPS принимает Caddy;
backend-порты не публикуются в интернет;
tunneluserограничен до remote forwarding;SSH-ключ используется только для туннеля;
StrictHostKeyCheckingвключён;GatewayPortsне разрешает публикацию remote forwards на внешних интерфейсах VPS;firewall разрешает только необходимые входящие порты;
systemdобеспечивает автозапуск и перезапуск туннеля;Caddy автоматически управляет TLS-сертификатами.
29. Сжатый набор команд
Перед использованием замените 203.0.113.10, cloud.example.com и имя локального пользователя на свои значения.
29.1. На VPS
apt update && apt upgrade -y apt install -y curl wget git ufw htop nano ca-certificates gnupg lsb-release adduser --disabled-password --gecos "" tunneluser mkdir -p /home/tunneluser/.ssh chmod 700 /home/tunneluser/.ssh chown -R tunneluser:tunneluser /home/tunneluser/.ssh usermod -s /usr/sbin/nologin tunneluser
SSH-конфиг:
nano /etc/ssh/sshd_config.d/90-reverse-tunnel.conf
Содержимое:
Match User tunneluser AllowTcpForwarding remote GatewayPorts no PermitTTY no X11Forwarding no AllowAgentForwarding no PermitTunnel no PasswordAuthentication no PubkeyAuthentication yes
Проверяем и применяем:
sshd -t systemctl restart ssh
Firewall:
ufw default deny incoming ufw default allow outgoing ufw allow 22/tcp ufw allow 80/tcp ufw allow 443/tcp ufw enable ufw status verbose
29.2. На домашнем сервере
ssh-keygen -t ed25519 -f ~/.ssh/vps_reverse_tunnel_ed25519 -C "home-server-to-vps-reverse-tunnel" cat ~/.ssh/vps_reverse_tunnel_ed25519.pub
Публичный ключ вставляем на VPS:
nano /home/tunneluser/.ssh/authorized_keys
После вставки на VPS:
chmod 600 /home/tunneluser/.ssh/authorized_keys chown tunneluser:tunneluser /home/tunneluser/.ssh/authorized_keys
На домашнем сервере:
ssh-keyscan -H 203.0.113.10 >> ~/.ssh/known_hosts sudo apt update sudo apt install -y autossh
Для строгой схемы перед добавлением host key следует сверить fingerprint VPS, а не полагаться только на сетевой ответ ssh-keyscan.
Создаём службу:
sudo nano /etc/systemd/system/reverse-tunnel.service
Содержимое:
[Unit] Description=Reverse SSH tunnel to VPS After=network-online.target Wants=network-online.target [Service] User=andrei Environment="AUTOSSH_GATETIME=0" Environment="AUTOSSH_PORT=0" ExecStart=/usr/bin/autossh -M 0 -N \ -i /home/andrei/.ssh/vps_reverse_tunnel_ed25519 \ -o ExitOnForwardFailure=yes \ -o ServerAliveInterval=30 \ -o ServerAliveCountMax=3 \ -o StrictHostKeyChecking=yes \ -R 127.0.0.1:11000:127.0.0.1:8080 \ tunneluser@203.0.113.10 Restart=always RestartSec=10 [Install] WantedBy=multi-user.target
Запускаем:
sudo systemctl daemon-reload sudo systemctl enable reverse-tunnel.service sudo systemctl start reverse-tunnel.service systemctl status reverse-tunnel.service --no-pager
29.3. Caddy на VPS
apt install -y debian-keyring debian-archive-keyring apt-transport-https curl curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list apt update apt install -y caddy
Caddyfile:
nano /etc/caddy/Caddyfile
cloud.example.com { reverse_proxy 127.0.0.1:11000 }
Проверяем и применяем:
caddy validate --config /etc/caddy/Caddyfile caddy fmt --overwrite /etc/caddy/Caddyfile systemctl reload caddy
30. Финальный контрольный список
Перед вводом схемы в постоянную эксплуатацию нужно проверить:
sshd -t
ufw status verbose
ss -tulpn
systemctl status reverse-tunnel.service --no-pager
journalctl -u reverse-tunnel.service -n 50 --no-pager
systemctl status caddy --no-pager
curl -I http://127.0.0.1:11000
curl -I https://cloud.example.com
Критерий корректности: внешний пользователь видит только HTTPS-домен, а прикладные backend-порты недостижимы из интернета напрямую.
Заключение
Обратный SSH-туннель через VPS — простой и управляемый способ публикации домашних сервисов без белого IP-адреса и без входящего проброса портов на домашнем маршрутизаторе.
Сильная сторона подхода — использование стандартных компонентов: OpenSSH, systemd, autossh, Caddy и firewall. Слабая сторона — необходимость аккуратно удерживать границы доверия. Ошибочная публикация backend-портов, глобальное включение GatewayPorts yes, отключение проверки host key или использование привилегированного пользователя для туннеля могут превратить простую схему в рискованную.
Практически корректная модель:
Домашний сервер инициирует исходящее SSH-соединение. VPS предоставляет только локальный tunnel endpoint. Caddy принимает внешний HTTPS-трафик. Firewall не публикует backend-порты. SSH-пользователь ограничен задачей remote forwarding.
Для домашней лаборатории, Nextcloud, Home Assistant, Jellyfin, code-server, документационного портала или личного self-hosted-сервиса такая схема даёт разумный баланс между контролем, воспроизводимостью и эксплуатационной безопасностью.
