HTTP-туннель для локального сайта собрали на OpenSSH и Nginx
Винсент Берна показал, как предоставить доступ к локальному веб-сервису через собственный сервер. В схеме OpenSSH создаёт туннель, а Nginx принимает HTTPS-запросы и проверяет доступ.
5 октября 2026 года Винсент Берна описал способ открыть локальный веб-сервис через собственный сервер без отдельного туннельного клиента. Как пишет автор в материале Self-hosted HTTP tunnels with SSH and Nginx, для этого он использует OpenSSH и Nginx: SSH связывает удалённый порт с локальным сервисом, а Nginx принимает HTTPS-запросы и направляет их в туннель. Задача из материала — дать другому человеку посмотреть черновик блога, который пока доступен только на localhost:8080.
Как запрос попадает на локальный порт
Разработчик запускает обратное перенаправление SSH с удалённого сервера к локальному сервису. В команде ssh -N -R 0:localhost:8080 web02.luffy.cx ноль означает, что свободный удалённый порт выберет сервер. В примере из материала он выдал порт 41535. Пока SSH-соединение открыто, запросы к этому порту можно передавать сервису на машине разработчика.
На удалённом сервере Nginx принимает HTTPS-запросы к поддоменам вида p41535.ssh.luffy.cx. Номер порта он извлекает из имени хоста и проксирует запрос на 127.0.0.1:41535. Для таких адресов автор настраивает DNS-запись с подстановочным именем и сертификат Let’s Encrypt для группы поддоменов. Проверка для выдачи сертификата проходит через DNS-01; в его случае NixOS получает сертификаты автоматически.
Схема не привязана к одному заранее назначенному порту: при следующем подключении SSH может выделить другой, а правило Nginx возьмёт новое число из адреса. Обратная сторона — одного Nginx недостаточно. Нужны сервер с OpenSSH, настроенные DNS и HTTPS, а также открытое SSH-соединение с машиной, где работает локальный сервис. Это способ задействовать имеющуюся инфраструктуру, а не избавиться от её настройки.
Ссылка с проверкой доступа
В базовой конфигурации номер порта остаётся единственным препятствием для доступа к странице: если его узнать, запрос пройдёт через прокси. Берна добавляет проверку через модуль Nginx ngx_http_secure_link_module. Ссылка содержит хеш и время истечения, записанные как имя пользователя перед доменом. Клиент передаёт эту часть адреса средствами HTTP Basic, а Nginx сравнивает полученный хеш с вычисленным по сроку действия, номеру порта и серверному секрету.
При неверном или отсутствующем хеше сервер отвечает кодом 401 и запрашивает учётные данные; после истечения срока ссылки возвращает 410. В примере автора срок действия составляет 86400 секунд. Перед передачей запроса в локальный сервис Nginx удаляет заголовок Authorization, в котором пришли данные для проверки. Конфигурация также предусматривает проксирование WebSocket-соединений.
Собирать такой адрес вручную неудобно: сначала нужно узнать выделенный SSH порт, затем вычислить хеш и сформировать ссылку. Поэтому автор написал вспомогательный скрипт для сервера. Он находит процессы SSH-сеанса, определяет связанные с ними слушающие порты, печатает адреса и удерживает сеанс открытым. Для поиска портов скрипт запускает ss через sudo -n — при повторении схемы этот доступ придётся настроить. Запись в SSH-конфигурации позволяет вызывать скрипт при подключении.
Что это даёт при работе с сайтом
Подтверждённый материалом сценарий — показать человеку черновик страницы, работающий на локальной машине. Получатель открывает выданный HTTPS-адрес; запрос приходит на сервер с Nginx и через обратное SSH-перенаправление попадает к локальному сервису. Отдельно размещать черновик на удалённом веб-сервере в этом сценарии не требуется, но туннель работает, пока открыт SSH-сеанс.
В ссылке находятся данные для доступа. Если переслать её ещё кому-то, новый получатель тоже сможет открыть страницу до истечения срока действия ссылки. Срок ограничивает доступ по времени, но сам по себе не ограничивает круг людей, которым адрес достался.
Моё мнение: меньше зависимостей, больше своей ответственности
Мне нравится, что здесь можно проследить путь запроса и назначение каждого элемента. SSH отвечает за перенаправление, Nginx — за HTTPS и проверку ссылки. Если оба уже работают на сервере, специализированный туннельный клиент не нужен. Но я бы не называл настройку простой лишь потому, что команда подключения короткая: DNS, сертификат, правила Nginx и доступ скрипта к сведениям о портах остаются частью решения.
Прежде чем советовать такой вариант команде, я бы спросил, как часто ей нужны временные превью и кто будет поддерживать серверную конфигурацию. Для редкого просмотра черновика выгода для меня неочевидна, если всё приходится настраивать с нуля. При регулярной работе я бы заранее договорился о сроке жизни ссылок и о том, кому их пересылают. Иначе удобная команда скрывает длинный перечень допущений, который проявится уже при использовании.
Источники
Откуда новость. Текст — пересказ своими словами, факты — из источника, мнение — автора.
- Self-hosted HTTP tunnels with SSH and Nginx vincent.bernat.ch
фронтенд-разработчик e-commerce
Поддерживаю магазины с тяжёлыми каталогами и регулярно разгребаю последствия «быстрого» внедрения плагинов. Перед тем как принять совет про новый стек, спрашиваю, что именно он улучшит и как это измерить.
Все публикации автораПохожие статьи
Взлом инфраструктуры зон .gh, .sl и .as позволил выпустить чужие TLS-сертификаты
Взлом инфраструктуры доменных зон .gh, .sl и .as позволил злоумышленникам изменить DNS-записи и получить чужие HTTPS-сертификаты, в том числе для доменов Google. Компания заблокировала их в Chrome и рекомендует владельцам сайтов проверить журналы CT.
Next.js выпустил обновления безопасности для веток 15.5 и 16.3
Next.js выпустил версии 16.3.8 и 15.5.27, исправляющие уязвимости кеширования, раскрытия данных и оптимизации изображений. Командам рекомендуют обновить приложения и проверить используемые функции.
Bez генерирует браузерный движок по веб-спецификациям и тестам
Bez создаёт браузерный движок по веб-спецификациям, сверяя результат с Chromium, Firefox и WebKit. Проект пока на ранней стадии, а опубликованные показатели не означают готовность движка.
Обсуждение 5
Этери Тевдорадзе
Интересная схема для показа черновика без стороннего туннельного клиента. Правильно понимаю, что при `ssh -R 0:...` удалённый порт доступен только через Nginx, а сам OpenSSH не выставляет его наружу? И как вы ограничиваете доступ к черновику — проверкой в Nginx достаточно или добавляете ещё аутентификацию на уровне приложения?
Петерис Залитис Автор публикации
@eto_dev, при стандартном `GatewayPorts no` удалённый форвард `-R` слушает loopback на сервере, так что снаружи к нему напрямую не подключиться; Nginx на том же сервере может проксировать запросы к этому порту. Если включить `GatewayPorts`, порт можно выставить на внешний интерфейс — тогда правило Nginx уже не единственный барьер. Проверки в Nginx хватит для черновика, если она действительно закрывает и все нужные маршруты; для чувствительных данных я бы оставил ещё и авторизацию приложения, чтобы утечка ссылки или ошибка в конфиге прокси не открыла контент.
Ура Элиас
@peter_zalitis, для черновика блога я бы ещё отдельно проверил, что происходит после обрыва SSH-соединения: туннель исчезает, а Nginx может продолжить отдавать ошибку по публичному адресу. Тут полезно запускать форвард как управляемый сервис с автоматическим перезапуском и мониторингом, но с ограниченным временем жизни — чтобы временный доступ не пережил саму задачу показа.
Петр Томан
@ura_elias, автоматический перезапуск может продлить доступ после обрыва ровно тогда, когда его уже не ждут; для разового показа я бы скорее завершал туннель по таймеру и проверял его состояние, чем держал постоянно перезапускаемый сервис.
Филипп Уралов
@ura_elias Ещё стоит учесть, что после перезапуска SSH при `-R 0:...` сервер может выдать уже другой порт, а Nginx останется настроен на старый. Тут нужен не только рестарт туннеля, но и способ обновить конфиг прокси или закрепить порт, если это поддерживается схемой.