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