SSD Nodes Learn 🎉 VPS от $5.50/мес
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-08-21

Как обойти CGNAT с помощью VPS и обратного туннеля

Провайдер не дает публичный IP из-за CGNAT? Настройте обратный туннель через frp на VPS. В статье пошаговая инструкция по пробросу портов и установке SSL для вашего сервера.

Почему проброс портов не работает за CGNAT

За CGNAT (carrier-grade network address translation) WAN-адрес вашего маршрутизатора является общим для других абонентов, поэтому у вас нет публичного IP-адреса и портов для проброса. Эту проблему решает обратный туннель: недорогой VPS имеет публичный IP, ваш домашний сервер устанавливает исходящее соединение с VPS, а входящие запросы передаются обратно по уже открытому соединению. Вы сохраняете имеющееся оборудование. Вы арендуете только то, что провайдер не предоставляет — маршрутизируемый адрес.

Каждая команда ниже помечена в зависимости от того, на какой машине она выполняется. Требуются две машины: VPS с публичным IP и домашний сервер, на котором запущен сервис, к которому вы хотите получить доступ.

Как определить, находитесь ли вы за CGNAT

Откройте страницу администратора вашего маршрутизатора и посмотрите WAN-адрес, который он отображает. Затем узнайте, какой адрес видит интернет.

# on the home box
curl -4 -s https://ifconfig.me; echo

Если эти два адреса совпадают, у вас есть публичный IP-адрес, и вам не нужны описанные здесь решения. Настройте проброс портов и можете не читать дальше. Если WAN-адрес маршрутизатора находится внутри 100.64.0.0/10, вы находитесь за CGNAT. Этот диапазон представляет собой общее адресное пространство согласно RFC 6598, зарезервированное именно для таких целей. Некоторые интернет-провайдеры вместо этого назначают 10.0.0.0/8 на стороне WAN, что по сути является той же ситуацией под другим названием.

Перед тем как арендовать какие-либо ресурсы, проверьте один момент. Многие провайдеры с CGNAT предоставляют реальный префикс IPv6. Если ваше домашнее устройство имеет глобальный IPv6-адрес, вы можете открыть брандмауэр для этого адреса и полностью отказаться от использования туннеля. Однако это перестанет работать, как только посетитель окажется в сети, поддерживающей только IPv4, поэтому большинство пользователей в конечном итоге всё равно приходят к использованию туннелей.

Принцип работы обратного туннеля VPS, инициируемого изнутри

CGNAT и обычные домашние маршрутизаторы блокируют входящие соединения, инициированные извне. Корпоративные межсетевые экраны делают то же самое. Однако они не блокируют исходящие соединения, так как именно их используют браузеры и клиенты обновлений. Устройство NAT, обнаружив исходящее TCP-соединение, создает для него правило трансляции и разрешает обратный трафик в рамках этого же соединения. Внешние узлы не могут инициировать подключение к вашему домашнему устройству. Поэтому домашнее устройство само устанавливает соединение, а туннель передает трафик обратно по этому же каналу.

В этом заключается весь механизм. Домашнее устройство подключается к VPS через определенный порт и поддерживает соединение открытым. VPS принимает публичные запросы и перенаправляет их через уже установленное соединение. Никто не пытается обратиться к вашему домашнему IP-адресу, поэтому в этом нет необходимости.

Из этого следуют два полезных вывода. Ваша DNS-запись указывает на VPS, а не на ваш домашний адрес. Ваш публичный IP-адрес теперь совпадает с адресом VPS, поэтому при проверке IP-адреса сторонний наблюдатель получит информацию об арендованном сервере, а не о вашем домашнем интернет-канале.

Три способа реализации

  1. ssh -R: уже установлен на обоих концах и подходит для одного сервиса или временной демонстрации. В нём нет панели управления и полноценной логики переподключения.
  2. frp: небольшой сервер на Go (frps) и соответствующий клиент (frpc). Подходит для постоянной настройки с несколькими сервисами за одним именем хоста. Это основная часть руководства.
  3. Mesh VPN: Tailscale или собственный сервер WireGuard. Подходит, если вы хотите, чтобы ваши устройства взаимодействовали друг с другом в приватном режиме, а не для публикации чего-либо в публичном интернете.

Выберите mesh, если ваша цель — приватный доступ с устройств, которыми вы управляете. В Tailscale Serve and Funnel описана публикация из tailnet, а в собственном VPN на базе WireGuard на том же VPS — аналогичная схема без стороннего сервера координации. Прочитайте один из этих разделов и пропустите остальную часть страницы. Всё, что написано ниже, предполагает, что вам нужно публичное HTTPS-имя хоста, которое может открыть любой пользователь.

Краткая версия: ssh -R для одного сервиса

Допустим, на домашнем сервере работает приложение на 127.0.0.1:3000, и у вас уже есть SSH-доступ к VPS.

# on the home box
ssh -N \
  -o ExitOnForwardFailure=yes \
  -o ServerAliveInterval=30 \
  -o ServerAliveCountMax=3 \
  -R 127.0.0.1:8080:127.0.0.1:3000 \
  tunnel@vps.example.com

-R 127.0.0.1:8080:127.0.0.1:3000 указывает sshd на VPS слушать собственный порт 127.0.0.1:8080 и перенаправлять всё, что туда поступает, на 127.0.0.1:3000 домашнего сервера. -N означает, что не нужно запускать командную оболочку. Две опции ServerAlive заставляют ssh обнаруживать разрыв соединения примерно за 90 секунд, вместо того чтобы зависать на неактивном подключении.

Теперь часть, которая всех путает. Этот слушатель привязан к loopback, поэтому curl http://vps.example.com:8080 из любого другого места не сработает. sshd поставляется с GatewayPorts no, что означает, что удаленный проброс привязывается только к интерфейсу loopback. Не пытайтесь исправить это установкой GatewayPorts yes. Оставьте проброс на loopback и поставьте перед ним nginx, так же, как это делается в настройке frp ниже. Тогда публичным портом будет 443 с сертификатом, а порт туннеля никогда не будет смотреть в интернет. Если вы не уверены, что именно сейчас слушает порты и на каких интерфейсах, краткий обзор портов и слушателей в Linux стоит потраченных десяти минут.

Если порт на VPS уже занят, ssh выведет сообщение об этом, а ExitOnForwardFailure=yes заставит его завершить работу, вместо того чтобы поддерживать соединение без работающего туннеля:

Warning: remote port forwarding failed for listen port 8080

Обычная причина — предыдущая сессия, которая завершилась, а sshd этого не заметил. Установите ClientAliveInterval 30 и ClientAliveCountMax 3 в файле /etc/ssh/sshd_config на VPS, чтобы неактивные сессии закрывались и освобождали порт. Оберните всю команду в systemd-юнит с Restart=always и выделенным ключом или используйте autossh. Если сервисов больше одного, остановитесь на этом этапе и используйте frp.

Установка frp на VPS с фиксацией версии

frp поставляется в виде статического бинарного файла на Go и отсутствует в репозиториях Ubuntu или Debian, поэтому необходимо скачать релиз и проверить его самостоятельно. Зафиксируйте версию. Формат конфигурации изменился в версии v0.52.0, а названия опций переместились, поэтому устаревшее руководство может предложить ключи, которые ваш бинарный файл не распознает. В этом руководстве используется v0.71.0, выпущенная 14 августа 2026 года.

# on the VPS
FRP_VERSION=0.71.0
ARCH=amd64   # use arm64 if `uname -m` prints aarch64
cd /tmp
curl -fsSLO "https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/frp_${FRP_VERSION}_linux_${ARCH}.tar.gz"
curl -fsSLO "https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/frp_sha256_checksums.txt"
sha256sum --check --ignore-missing frp_sha256_checksums.txt

sha256sum должен вывести ровно одну строку:

frp_0.71.0_linux_amd64.tar.gz: OK

--ignore-missing необходим, так как файл контрольных сумм охватывает все восемнадцать активов релиза, а вы скачали только один из них. Без этого флага sha256sum сообщит об отсутствии остальных семнадцати файлов и завершится с ненулевым кодом, что выглядит как ошибка проверки, хотя на самом деле всё в порядке.

# on the VPS
tar xzf "frp_${FRP_VERSION}_linux_${ARCH}.tar.gz"
sudo install -m 755 "frp_${FRP_VERSION}_linux_${ARCH}/frps" /usr/local/bin/frps
sudo useradd --system --no-create-home --shell /usr/sbin/nologin frp
sudo install -d -m 750 -o root -g frp /etc/frp
frps --version

frps --version выводит 0.71.0. Только frps размещается на VPS. frpc размещается на домашнем компьютере. Установка обоих бинарных файлов везде приводит к тому, что пользователи случайно запускают сервер туннелирования у себя дома.

Конфигурация VPS: токен, принудительный TLS, слушатели на loopback

Сначала создайте токен. Это единственное препятствие между вашим туннелем и любым, кто сканирует порты вашего VPS.

# on the VPS
openssl rand -base64 32

Запишите это значение в /etc/frp/frps.toml:

bindAddr = "0.0.0.0"
bindPort = 7000

# Every listener frp creates for a proxy, including the HTTP vhost, stays on loopback.
proxyBindAddr = "127.0.0.1"
vhostHTTPPort = 8080

auth.method = "token"
auth.token = "PASTE_THE_OPENSSL_OUTPUT_HERE"

transport.tls.force = true

webServer.addr = "127.0.0.1"
webServer.port = 7500
webServer.user = "admin"
webServer.password = "PASTE_A_SECOND_SECRET_HERE"

log.level = "info"

Четыре из этих строк отвечают за безопасность, поэтому разберем их по порядку.

auth.token должен совпадать с auth.token на клиенте. Без этого frps примет любого клиента, который обнаружит порт 7000, и этот клиент сможет опубликовать что угодно через ваш VPS и ваш сертификат.

transport.tls.force = true отклоняет любое управляющее соединение, которое не использует TLS (transport layer security). Клиенты используют TLS по умолчанию начиная с версии v0.50.0, поэтому на практике это не требует усилий, но исключает ситуацию, когда старый или самописный клиент подключается в открытом виде без вашего ведома.

proxyBindAddr = "127.0.0.1" — это строка, которую пропускают большинство руководств, и именно она делает эту конфигурацию безопасной для постоянной работы. Она переносит все слушатели, которые frp открывает от имени прокси (как HTTP vhost, так и любые remotePort, запрашиваемые клиентом), на интерфейс loopback. Интернет не может получить доступ к этим слушателям напрямую. Единственная публичная точка входа — это nginx на порту 443, который вы настраиваете и контролируете.

webServer.addr = "127.0.0.1" скрывает панель управления от публичного интерфейса. Панель управления представляет собой полную карту ваших приватных сервисов и их трафика, защищенную только одним паролем HTTP basic auth, поэтому ей не место на 0.0.0.0.

Установите права доступа, чтобы токен не был доступен для чтения всем пользователям, а затем проверьте синтаксис перед запуском:

# on the VPS
sudo chown root:frp /etc/frp/frps.toml
sudo chmod 640 /etc/frp/frps.toml
sudo -u frp frps verify -c /etc/frp/frps.toml

При корректном файле будет выведено:

frps: the configuration file /etc/frp/frps.toml syntax is ok

Одно замечание по формату, которое сэкономит вам время. frp выбирает парсер на основе расширения файла, поддерживаются .toml, .yaml, .yml и .json. Старые файлы .ini по-прежнему загружаются через механизм обратной совместимости, но формат INI считается устаревшим, а новые параметры документируются только для TOML. Если в руководстве показана секция [common] и server_addr = x.x.x.x, оно написано до выхода версии v0.52.0, и названия ключей в нем не будут соответствовать бинарному файлу, который вы только что установили.

Запуск frps в качестве непривилегированной службы

bindPort — это 7000, а vhostHTTPPort — 8080. Оба порта больше 1024, поэтому frps не требует прав root и не нуждается в CAP_NET_BIND_SERVICE. Именно по этой причине не следует назначать vhost на 80 порт; лучше предоставить это Nginx.

Создайте /etc/systemd/system/frps.service:

[Unit]
Description=frp reverse tunnel server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=frp
Group=frp
ExecStart=/usr/local/bin/frps -c /etc/frp/frps.toml
Restart=on-failure
RestartSec=5s
LimitNOFILE=65535
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true

[Install]
WantedBy=multi-user.target
# on the VPS
sudo systemctl daemon-reload
sudo systemctl enable --now frps
sudo journalctl -u frps -n 20 --no-pager

В логах должны отображаться оба слушателя, при этом адреса важнее портов:

frps tcp listen on 0.0.0.0:7000
http service listen on 127.0.0.1:8080

ProtectSystem=strict делает всю файловую систему доступной только для чтения для этой службы. frps работает в таком режиме, так как по умолчанию выводит логи в стандартный поток вывода, который перехватывается journald. Если вы установите log.to в путь к файлу, служба не сможет записывать в него данные, пока вы не добавите соответствующую строку ReadWritePaths=, поэтому оставьте значение по умолчанию без изменений.

Межсетевой экран: открытие одного порта вместо диапазона

# on the VPS
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 7000/tcp
sudo ufw enable
sudo ufw status numbered

Всего четыре правила, одно из которых существует только для обновления сертификатов. 22 — это SSH. 80 перенаправляет на 443 и отвечает на запрос ACME (automatic certificate management environment). 443 обслуживает все туннелируемые приложения. 7000 — это порт управления frp, и это единственный порт, к которому должен обращаться клиент.

Руководства, предлагающие открыть диапазон, такой как sudo ufw allow 20000:30000/tcp, описывают другую архитектуру, где каждый сервис занимает собственный публичный TCP-порт. Здесь это не требуется, так как весь трафик поступает на 443, а frp распределяет его по имени хоста. Если позже вам понадобится один публичный TCP-порт, верните proxyBindAddr в 0.0.0.0 и добавьте ограничения, чтобы клиент мог занимать только указанные вами порты:

allowPorts = [
  { start = 20000, end = 20010 }
]
maxPortsPerClient = 5

Большинство провайдеров также используют сетевой экран в панели управления, отдельный от ufw на самом сервере. Если правило выглядит корректным в sudo ufw status, но соединение всё равно прерывается по тайм-ауту, обычно оно блокируется именно там. В статье Правила ufw, действительно необходимые VPS подробно описана настройка default-deny, которая подразумевается в этом разделе.

Завершение HTTPS на VPS с использованием реального сертификата

Направьте A-запись для home.example.com на публичный IP-адрес вашего VPS. Не на ваш домашний адрес. У вашего дома нет адреса, на который можно было бы направить запись, и именно эту проблему вы сейчас решаете.

# on the VPS
sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx

Сначала создайте /etc/nginx/sites-available/home.example.com с простым блоком для 80-го порта, чтобы у certbot был соответствующий server_name для работы:

server {
    listen 80;
    listen [::]:80;
    server_name home.example.com;
    location / { return 404; }
}
# on the VPS
sudo ln -s /etc/nginx/sites-available/home.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot certonly --nginx -d home.example.com

nginx -t выводит nginx: configuration file /etc/nginx/nginx.conf test is successful, если файлы конфигурации синтаксически верны. Запускайте эту команду перед каждой перезагрузкой. nginx продолжает использовать старую конфигурацию, если перезагрузка не удалась, поэтому ошибочные правки могут выглядеть так, будто ничего не изменилось.

Для работы WebSocket-соединений требуется карта (map) на уровне http. Добавьте её в /etc/nginx/conf.d/upgrade.conf:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

Теперь замените файл конфигурации сайта на итоговый:

server {
    listen 80;
    listen [::]:80;
    server_name home.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name home.example.com;

    ssl_certificate     /etc/letsencrypt/live/home.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/home.example.com/privkey.pem;

    client_max_body_size 512m;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade           $http_upgrade;
        proxy_set_header Connection        $connection_upgrade;
        proxy_read_timeout 3600s;
    }
}

proxy_set_header Host $host; здесь обязателен. HTTP vhost в frp выполняет маршрутизацию на основе заголовка Host, сопоставляя его со списком customDomains в конфигурации клиента. Если пропустить этот заголовок, nginx отправит Host: 127.0.0.1, frp не найдёт прокси для этого имени, и вместо страницы приложения посетитель получит 404 от самого frp. В статье Разбор каждой строки блока reverse proxy в nginx подробно описано назначение остальных заголовков.

# on the VPS
sudo nginx -t && sudo systemctl reload nginx
sudo certbot renew --dry-run

Тестовый запуск (dry run) подтверждает, что обновление сертификатов сработает через 90 дней, когда вы не будете следить за процессом. Для этого необходим доступ к 80-му порту, поэтому правило ufw должно быть активно.

Домашняя сторона: frpc как непривилегированный сервис

Установите frpc на домашний компьютер точно так же, как вы устанавливали frps: используйте ту же версию и выполните проверку контрольной суммы, затем создайте того же пользователя frp и каталог /etc/frp. Создайте файл /etc/frp/frpc.toml:

serverAddr = "vps.example.com"
serverPort = 7000

auth.method = "token"
auth.token = "PASTE_THE_SAME_TOKEN_HERE"

transport.tls.enable = true
loginFailExit = false

proxies = [
  { name = "home-app", type = "http", localIP = "127.0.0.1", localPort = 3000, customDomains = ["home.example.com"] }
]

Порядок ключей в этом файле имеет значение, и не только из соображений стиля. Формат TOML присваивает каждый ключ, указанный после заголовка таблицы, именно этой таблице. Поэтому настройка верхнего уровня, такая как serverAddr, записанная после заголовка прокси-таблицы, будет ошибочно воспринята как настройка прокси, которую frp проигнорирует. Запись списка прокси в виде встроенного массива, как показано выше, позволяет избежать этой ловушки: каждый ключ верхнего уровня остается однозначно определенным.

type = "http" направляет этот прокси через vhost-слушатель вместо того, чтобы занимать отдельный публичный TCP-порт, поэтому количество правил файрвола осталось равным четырем. customDomains должен содержать имя хоста, которое nginx передает в заголовке Host, поэтому это home.example.com, а не IP-адрес VPS.

loginFailExit = false важнее, чем кажется на первый взгляд. Значение по умолчанию — true, из-за чего frpc завершает работу, если первая попытка входа не удалась. На домашнем компьютере, который загружается быстрее, чем устанавливается соединение с провайдером, это приведет к тому, что сервис будет неактивен до тех пор, пока вы не заметите проблему. Установите значение false, и frpc будет повторять попытки до тех пор, пока VPS не ответит.

Создайте файл /etc/systemd/system/frpc.service:

[Unit]
Description=frp reverse tunnel client
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=frp
Group=frp
ExecStart=/usr/local/bin/frpc -c /etc/frp/frpc.toml
Restart=always
RestartSec=10s
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true

[Install]
WantedBy=multi-user.target
# on the home box
sudo chown root:frp /etc/frp/frpc.toml
sudo chmod 640 /etc/frp/frpc.toml
sudo -u frp frpc verify -c /etc/frp/frpc.toml
sudo systemctl daemon-reload
sudo systemctl enable --now frpc
sudo journalctl -u frpc -n 20 --no-pager

Клиент, который успешно подключился, записывает в лог идентификатор запуска (run id):

login to server success, get run id [3a1f9c2b7d4e5f60]

Откройте https://home.example.com в браузере, и вы должны увидеть приложение, которое работает на 127.0.0.1:3000 у вас дома. Restart=always на стороне клиента настроен намеренно: домашние соединения могут обрываться, и сервис должен восстанавливаться автоматически без вашего участия.

Ограничение доступа к панели управления

С помощью webServer.addr = "127.0.0.1" панель управления отвечает только на самом VPS. Получите к ней доступ со своего ноутбука через локальный проброс портов вместо открытия порта:

# on your laptop
ssh -N -L 7500:127.0.0.1:7500 you@vps.example.com

Откройте http://127.0.0.1:7500 и войдите, используя webServer.user и webServer.password из frps.toml. На странице отображается список всех подключенных клиентов и счетчики трафика для каждого прокси. Это самый быстрый способ ответить на вопрос, подключен ли в данный момент домашний узел. Закройте сессию ssh, и панель управления снова станет недоступной.

Чего туннель не делает

Прочитайте этот раздел дважды, так как именно здесь чаще всего допускаются ошибки. Туннель делает приватный сервис доступным из публичного интернета. Он не выполняет аутентификацию пользователей, которые получают к нему доступ. Как только https://home.example.com разрешится, сканеры обнаружат его в течение нескольких дней, независимо от того, сообщали ли вы кому-либо имя хоста. Логи Certificate Transparency публикуют каждое имя хоста, для которого вы выпускаете сертификат, поэтому имя становится публичным в тот же момент, когда certbot завершает работу успешно.

Всё, что вы выставляете наружу, должно иметь собственную аутентификацию. Если у приложения есть полноценная система входа с ограничением частоты запросов (rate limiting) — это хорошо. Если вход осуществляется через один общий пароль или отсутствует вовсе, установите перед ним аутентифицирующий прокси на VPS. Размещение oauth2-proxy перед приложением — стандартное решение; он встраивается между nginx и vhost в frp без необходимости менять настройки на обеих сторонах туннеля.

Токен в frps.toml защищает туннель, а не приложения. Он предотвращает ситуацию, когда посторонний регистрирует свой прокси на вашем VPS. Он никак не влияет на запросы, поступающие на 443 порт для имени хоста, которое вы опубликовали намеренно.

Стоит выработать две полезные привычки. Обновляйте токен, редактируя оба файла и перезапуская оба сервиса, так как сам по себе он не истекает. И поддерживайте frp в актуальном состоянии: этот бинарный файл является вашей входной дверью, обращённой в интернет. В примечаниях к версии v0.71.0 упоминается паника сервера, вызванная некорректным значением, отправленным клиентом — это именно тот класс ошибок, которые нужно исправлять обновлением, а не пытаться анализировать.

Режимы сбоев и соответствующие сообщения

Клиент не подключается. journalctl -u frpc повторяет connect to server error:, после чего следует таймаут соединения. На порт 7000 ничего не приходит. Проверьте ufw на VPS, затем сетевой брандмауэр провайдера в панели управления, после чего подтвердите разрешение имени с помощью getent hosts vps.example.com.

Неверный токен. Клиент сообщает об этом прямо:

login to the server failed: token in login doesn't match token from configuration

Скопируйте токен еще раз. Завершающий символ переноса строки или $ в неэкранированной строке оболочки, которая раскрылась в пустоту, являются причиной почти всех подобных ошибок. Именно поэтому вывод openssl rand -base64 32 в файле TOML должен быть заключен в кавычки.

Туннель поднят, но браузер выдает 404. frpc сообщает об успешном входе, и панель управления отображает прокси, однако страница возвращает 404 без стилей приложения. Это означает, что frp не находит прокси для данного заголовка Host. Протестируйте vhost напрямую на VPS, минуя nginx и TLS:

# on the VPS
curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: home.example.com' http://127.0.0.1:8080/

Ошибка 404 в ответе этой команды означает, что customDomains указан неверно. Любой другой код означает, что запрос не получил правильный Host от nginx.

Ошибка 502 от nginx. nginx отвечает, а frp — нет. sudo ss -lntp | grep 8080 на VPS должен показать, что frps слушает порт 127.0.0.1:8080. Пустой вывод означает, что frps остановлен или vhostHTTPPort не задан в frps.toml.

Приложение считает всех посетителей локальными. Ваше приложение записывает 127.0.0.1 для каждого запроса. frp устанавливает X-Forwarded-For, а nginx добавляет к нему данные, поэтому реальный адрес клиента находится в этом заголовке. Настройте приложение так, чтобы оно доверяло этому заголовку. Не пропускайте этот шаг, если приложение ограничивает частоту запросов по IP-адресу, иначе все посетители из интернета будут ограничены общим лимитом.

Длительные запросы обрываются через 60 секунд. Загрузка файлов или потоковые ответы прерываются на середине. Это стандартное значение proxy_read_timeout в nginx, а не проблема туннеля. Блок выше увеличивает его до 3600 секунд. client_max_body_size — это соответствующий лимит на размер загружаемого файла; стандартное значение в 1 МБ приводит к отклонению более крупных запросов с ошибкой 413.

Все работает, но перестает после перезагрузки роутера. Параметр Restart=always в юните frpc вместе с loginFailExit = false решают эту проблему. Проверьте состояние с помощью sudo systemctl is-enabled frpc, команда должна вывести enabled.

FAQ

Как узнать, нахожусь ли я за CGNAT?

Сравните WAN-адрес на странице администратора вашего роутера с тем, что показывает curl -4 -s https://ifconfig.me из той же сети. Если они различаются, а WAN-адрес роутера находится в диапазоне 100.64.0.0/10, ваш провайдер использует carrier-grade NAT. Этот диапазон является общим адресным пространством согласно RFC 6598 и существует именно для этих целей. Некоторые провайдеры вместо этого используют 10.0.0.0/8 на стороне WAN, что означает то же самое. Если оба адреса совпадают, у вас публичный IP: настройте проброс порта, и на этом всё.

Нужен ли домен для обратного туннеля?

Для описанной здесь настройки HTTPS — да. Сертификат выдается на имя хоста, а HTTP vhost в frp маршрутизирует запросы по заголовку Host, поэтому обе стороны должны использовать общее имя. Обычный TCP-прокси на конкретном порту будет работать с прямым IP-адресом VPS без домена, но в этом случае у вас не будет сертификата и маршрутизации по имени хоста, поэтому один публичный порт сможет обслуживать только один сервис.

Безопасно ли запускать frp на публичном VPS?

Это безопасно, если открыт только порт управления и настроена аутентификация. Установите auth.token в случайное значение на обеих сторонах и настройте transport.tls.force = true на сервере. Затем установите proxyBindAddr = "127.0.0.1", чтобы сервисы, которые frp открывает для проксирования, не были доступны из интернета, а панель управления оставьте на webServer.addr = "127.0.0.1", доступ к которой осуществляется через SSH local forward. Обновляйте бинарный файл при выходе новых релизов, так как именно этот процесс слушает ваш публичный адрес.

Почему никто не может подключиться к моему проброшенному порту ssh -R?

sshd поставляется с настройкой GatewayPorts no, поэтому удаленный проброс привязывается только к интерфейсу loopback на VPS. curl, запущенный на самом VPS, работает, а curl из любого другого места выдает тайм-аут. Правильное решение — оставить проброс на loopback и поставить перед ним nginx на 443 порт. Настройка GatewayPorts yes открывает «сырой» порт без сертификата и TLS, что создает больше проблем, чем решает.

Что лучше использовать: frp или mesh VPN, например Tailscale или WireGuard?

Используйте mesh VPN, если доступ нужен только вашим собственным устройствам, так как в этом случае ничего не публикуется и нет публичного имени хоста для сканирования. Используйте frp, если вам нужен публичный HTTPS-адрес, который может открыть любой браузер, например, для получения вебхуков или страницы, которой вы делитесь с людьми, не желающими устанавливать VPN-клиент. Эти решения отлично сосуществуют на одном VPS, используя разные порты и выполняя разные задачи.

#frp#cgnat#nat#tunnel#reverse-proxy