Аннотация

Если домашний сервер находится за 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-сервиса такая схема даёт разумный баланс между контролем, воспроизводимостью и эксплуатационной безопасностью.