---
tags: [network, security, tooling]
verified: 2026-09-01
---

# Самохостинг за NAT: VPS как точка входа

## Обзор

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

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

Отдельный раздел — про альтернативы. Самодельная схема на WireGuard даёт максимум контроля, но она не всегда лучший выбор, и честнее знать, где готовое решение объективно выигрывает.

## Почему порт не пробросить

Стоит сначала точно понять, с чем имеем дело, — от этого зависит, нужна ли вся конструкция вообще.

**Серый IP за домашним роутером.** Обычная ситуация: роутер получает от провайдера публичный адрес, а домашние устройства сидят за его NAT. Это лечится пробросом порта в веб-интерфейсе роутера, и никакой VPS не нужен.

**CGNAT** (Carrier-Grade NAT). Провайдер выдаёт роутеру адрес из приватного диапазона и сам транслирует его в публичный, разделяемый между сотнями абонентов. Пробрасывать нечего: на внешнем адресе вы не хозяин. Диагностируется за минуту — адрес на WAN-интерфейсе роутера сравнивается с тем, что показывает внешний сервис:

```bash
curl -s https://api.ipify.org; echo          # что видит интернет
ip -4 addr show dev "$(ip route show default | awk '{print $5; exit}')"
```

Если адрес на интерфейсе принадлежит `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16` или `100.64.0.0/10` (последний зарезервирован именно под CGNAT), а внешний отличается — вы за CGNAT. Дальше два пути: попросить у провайдера белый адрес за доплату либо строить точку входа самостоятельно.

## Схема и её границы

```text
   клиент                VPS (белый IP)              домашняя машина
     │                  ┌──────────────┐            ┌──────────────┐
     │   443/tcp        │  eth0        │            │              │
     ├─────────────────>│              │            │              │
     │                  │  DNAT + FWD  │            │              │
     │                  │  wg0 ────────┼── туннель ─┼──> wg0       │
     │                  │  10.100.0.1  │  WireGuard │   10.100.0.2 │
     │                  └──────────────┘            │   сервис:443 │
     │                                              └──────────────┘
     │                     ↑ CGNAT провайдера обходится тем,
     │                       что соединение всегда исходящее
```

Работает это благодаря одной асимметрии: домашняя машина сама устанавливает исходящее соединение к VPS. Исходящие через CGNAT проходят беспрепятственно, а трансляция, созданная этим соединением, дальше используется в обе стороны. Публичный адрес VPS становится адресом ваших сервисов.

Чего схема не даёт, стоит зафиксировать сразу, потому что это часто выясняется постфактум:

- **Пропускная способность ограничена VPS**, а не домашним каналом. Виртуалка за пять евро с портом 100 Мбит/с — это потолок для всего, что вы публикуете, независимо от того, что дома гигабит.
- **Задержка складывается.** Клиент → VPS → дом, и обратно тем же путём. Если VPS во Франкфурте, а вы и клиент в одном городе, трафик всё равно ходит через Франкфурт.
- **Лимит трафика — реальное ограничение.** У большинства дешёвых провайдеров это 1–20 ТБ в месяц; медиасервер с внешними зрителями съедает такой объём быстрее, чем кажется.
- **VPS становится единой точкой отказа** и единственной публичной поверхностью атаки одновременно.

Из этого следует область применимости: схема отлично подходит для веб-сервисов, API, панелей управления и личного доступа; она плохо подходит для раздачи большого объёма медиа наружу и для нагрузок, чувствительных к задержке.

## Туннель

Почему WireGuard, а не OpenVPN: для постоянного канала между двумя машинами он проще в конфигурации, легче по накладным расходам и не требует демона с собственным состоянием — интерфейс живёт в ядре. Подробное сравнение с разбором того, где выигрывает каждый, вынесено в [отдельную заметку](./openvpn-vs-wireguard.md).

### Ключи и адресация

Туннелю нужна своя маленькая подсеть, не пересекающаяся ни с домашней, ни с сетью VPS. Возьмём `10.100.0.0/24`: `.1` — VPS, `.2` — домашняя машина.

```bash
wg genkey | tee privatekey | wg pubkey > publickey
chmod 600 privatekey
```

Конфигурация на VPS — `/etc/wireguard/wg0.conf`:

```ini
[Interface]
Address = 10.100.0.1/24
ListenPort = 51820
PrivateKey = <приватный ключ VPS>

[Peer]
# домашняя машина
PublicKey = <публичный ключ дома>
AllowedIPs = 10.100.0.2/32
```

Конфигурация дома:

```ini
[Interface]
Address = 10.100.0.2/24
PrivateKey = <приватный ключ дома>

[Peer]
PublicKey = <публичный ключ VPS>
Endpoint = vps.example.com:51820
AllowedIPs = 10.100.0.1/32
PersistentKeepalive = 25
```

Асимметрия здесь принципиальна и отражает суть схемы. У домашнего пира прописан `Endpoint` — он знает, куда стучаться. У VPS `Endpoint` для домашнего пира не указан: адрес за CGNAT непредсказуем и меняется, поэтому VPS узнаёт его из первого пришедшего пакета и запоминает. Это называется roaming и работает автоматически.

### AllowedIPs — таблица маршрутов, а не список доступа

Самая частая ошибка в конфигурации WireGuard происходит из-за названия параметра. `AllowedIPs` выполняет две работы сразу: на приём — фильтрует, какие адреса-источники допустимы от этого пира (криптографическая привязка ключа к диапазону), на отправку — определяет, какой трафик вообще пойдёт в туннель. Второе важнее и неочевиднее: это фактически таблица маршрутизации, и `wg-quick` буквально создаёт по ней маршруты.

Отсюда прямой ответ на вопрос, какой трафик заворачивать. В нашей схеме — только трафик самого туннеля, то есть `/32`-адреса пиров. Прописать дома `AllowedIPs = 0.0.0.0/0` означает отправить **весь** интернет-трафик домашней машины через VPS: она потеряет свой канал, упрётся в полосу виртуалки и начнёт ходить в сеть с чужого адреса. Для схемы публикации сервисов это не нужно совершенно.

Если наружу надо отдать не одну машину, а всю домашнюю подсеть, `AllowedIPs` у домашнего пира на стороне VPS расширяется до `10.100.0.2/32, 192.168.1.0/24`, а дома включается форвардинг. Но начинать стоит с одной машины.

### PersistentKeepalive: почему он здесь обязателен

Параметр выглядит необязательной оптимизацией, и в документации `wg` прямо сказано, что большинству пользователей он не нужен. В нашей схеме он нужен всегда, и вот почему.

WireGuard не имеет понятия соединения: нет ни установления, ни поддержания сессии, при отсутствии данных по проводу не идёт вообще ничего. Промежуточному NAT провайдера это неизвестно — он держит запись трансляции ограниченное время и выбрасывает её по таймауту неактивности, обычно от 30 секунд до нескольких минут. После этого пакет от VPS к дому приходить некуда: трансляции нет, и до восстановления пройдёт время, пока домашняя сторона сама что-нибудь не отправит.

Симптом узнаваем: туннель работает, пока им пользуются, и «залипает» после простоя — первое соединение отваливается по таймауту, второе проходит. `PersistentKeepalive = 25` заставляет отправлять аутентифицированный пустой пакет каждые 25 секунд, что удерживает трансляцию открытой. Значение выбрано с запасом относительно самых агрессивных таймаутов; документация WireGuard рекомендует именно его.

Ставить keepalive нужно **на стороне за NAT** — дома. На VPS он бессмыслен: у виртуалки публичный адрес, ей нечего удерживать. Плата за это — небольшой постоянный трафик (порядка мегабайта в сутки) и то, что домашняя машина не уходит в глубокие энергосберегающие состояния; для сервера это не имеет значения.

### Запуск и контроль живости

```bash
systemctl enable --now wg-quick@wg0
wg show                                   # состояние: пиры, ключи, счётчики
wg show wg0 latest-handshakes             # unix-время последнего рукопожатия
ping -c 3 10.100.0.1                      # с домашней машины
```

Ключевой показатель здоровья — время последнего рукопожатия. WireGuard обновляет его не чаще, чем раз в две минуты при наличии трафика; значение старше пяти минут при живом keepalive означает, что туннель фактически мёртв, даже если интерфейс поднят. На это стоит завести простую проверку:

```bash
#!/usr/bin/env bash
# /usr/local/bin/wg-watchdog — перезапуск туннеля при потере связи
last=$(wg show wg0 latest-handshakes | awk '{print $2; exit}')
[[ -z $last || $last -eq 0 ]] && exit 0        # ещё ни разу не поднимался
if (( $(date +%s) - last > 300 )); then
    logger -t wg-watchdog "рукопожатия нет $(( $(date +%s) - last ))s, перезапуск"
    systemctl restart wg-quick@wg0
fi
```

Вешается на таймер systemd с интервалом в минуту. Автоматического восстановления в самом `wg-quick` нет: юнит имеет тип `oneshot` и после успешного поднятия интерфейса завершается, поэтому `Restart=` там ничего не даст — systemd нечего перезапускать, процесс и должен был завершиться.

## Маршрутизация и firewall на входной точке

### Форвардинг

Ядро по умолчанию не пересылает пакеты между интерфейсами. Пока это не включено, DNAT работать не будет, и это первое, что стоит проверять при разборе неработающей схемы:

```bash
# /etc/sysctl.d/99-forward.conf
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
```

```bash
sysctl --system
sysctl net.ipv4.ip_forward        # должно быть 1
```

### Развилка: сохранять реальный IP клиента или нет

Это главное решение в схеме, и принимать его надо осознанно, потому что от него зависит и конфигурация firewall, и то, что вы увидите в логах.

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

Отсюда три рабочих варианта:

| Вариант | Что видит сервис | Цена |
|---------|------------------|------|
| DNAT + SNAT на VPS | адрес туннеля VPS (`10.100.0.1`) | простота; реальный IP потерян |
| DNAT + policy routing дома | настоящий адрес клиента | сложнее настройка, разметка соединений |
| Обратный прокси на VPS | адрес VPS + `X-Forwarded-For` | только HTTP; TLS терминируется снаружи |

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

### nftables на VPS

Вариант с SNAT, публикация HTTP и HTTPS:

```bash
#!/usr/sbin/nft -f
flush ruleset

table inet filter {
    chain input {
        type filter hook input priority filter; policy drop;

        ct state established,related accept
        ct state invalid drop
        iif lo accept
        ip protocol icmp accept
        ip6 nexthdr icmpv6 accept

        udp dport 51820 accept          # WireGuard
        iifname "wg0" tcp dport 22 accept   # SSH только изнутри туннеля
    }

    chain forward {
        type filter hook forward priority filter; policy drop;

        ct state established,related accept
        iifname "eth0" oifname "wg0" tcp dport { 80, 443 } ct state new accept

        # без этого большие ответы не пройдут — см. раздел про MTU
        tcp flags syn tcp option maxseg size set rt mtu
    }
}

table ip nat {
    chain prerouting {
        type nat hook prerouting priority dstnat; policy accept;
        iifname "eth0" tcp dport { 80, 443 } dnat to 10.100.0.2
    }

    chain postrouting {
        type nat hook postrouting priority srcnat; policy accept;
        oifname "wg0" ip daddr 10.100.0.2 masquerade
    }
}
```

Существенные детали. Цепочка `input` с политикой `drop` защищает саму виртуалку, `forward` — управляет транзитом; путать их легко, а последствия разные. Правило SSH принимает подключения только с интерфейса `wg0`: после того как туннель заработал, публичный SSH становится необязательным, и его закрытие убирает основную мишень для перебора паролей. Порт 51820 остаётся единственным открытым наружу, помимо публикуемых.

Порядок действий здесь имеет значение. Правило, оставляющее SSH только внутри туннеля, применяется **после** того, как туннель проверен и работает, — иначе набор правил запрёт вас снаружи, а зайти изнутри будет ещё нельзя. На арендованной виртуалке это чинится только через консоль в панели провайдера, и хорошо, если она есть.

Две привычки, которые стоят потраченной минуты:

```bash
nft -c -f /etc/nftables.conf                      # проверить синтаксис, не применяя
systemd-run --on-active=5m nft flush ruleset      # страховка: откат через 5 минут
```

Вторая команда ставит отложенный сброс правил: если после применения связь потерялась, через пять минут firewall очистится сам. Когда всё в порядке, таймер снимается через `systemctl stop` по имени из вывода команды.

### Сохранение реального адреса клиента

Если выбран второй вариант, SNAT с VPS убирается, а на домашней машине настраивается возврат ответов через туннель. Механика: соединения, пришедшие из `wg0`, помечаются меткой в conntrack, метка восстанавливается на исходящих пакетах, и по ней срабатывает отдельное правило маршрутизации.

```bash
#!/usr/sbin/nft -f
table inet mangle {
    chain prerouting {
        type filter hook prerouting priority mangle; policy accept;
        iifname "wg0" ct state new ct mark set 0x1
    }
    chain output {
        type route hook output priority mangle; policy accept;
        ct mark 0x1 meta mark set 0x1
    }
}
```

```bash
ip rule add fwmark 0x1 lookup 100
ip route add default dev wg0 table 100
```

Теперь ответы на соединения, пришедшие через туннель, уходят обратно в туннель, а весь остальной трафик машины продолжает ходить через домашнего провайдера. Хранить это надо не в командах, а в конфигурации сети — в `systemd-networkd` через `RoutingPolicyRule` и `[Route]`, либо в `PostUp`-строках `wg-quick`.

### PF, если входная точка на FreeBSD

Здесь важна версия, и это тот случай, когда конфигурация из статьи трёхлетней давности просто не загрузится. FreeBSD 14 и старше используют синтаксис трансляции, унаследованный от OpenBSD 4.5: отдельные правила `rdr` и `nat` со стрелкой. В FreeBSD 15 подтянут современный синтаксис OpenBSD, где трансляция стала модификатором обычного правила — `rdr-to` и `nat-to`.

FreeBSD 14 и раньше:

```conf
ext_if = "vtnet0"
tun_if = "wg0"
internal = "10.100.0.2"

rdr pass on $ext_if proto tcp from any to any port { 80, 443 } -> $internal
nat on $tun_if from any to $internal -> ($tun_if)

pass out on $tun_if
```

FreeBSD 15:

```conf
ext_if = "vtnet0"
tun_if = "wg0"
internal = "10.100.0.2"

pass in quick on $ext_if proto tcp to port { 80, 443 } rdr-to $internal
pass out on $tun_if to $internal nat-to ($tun_if)
```

Скобки вокруг имени интерфейса — не украшение: они означают «взять адрес динамически» и нужны там, где адрес интерфейса может смениться. Форвардинг включается через `net.inet.ip.forwarding=1` в `/etc/sysctl.conf`, а проверка набора правил делается как `pfctl -nf /etc/pf.conf`.

## Реальный адрес клиента и где терминировать TLS

Симптом «в логах у всех один и тот же адрес» имеет две разные причины, и лечатся они по-разному.

Если на VPS стоит SNAT, адрес теряется на сетевом уровне, и никакими заголовками его не вернуть — информации об оригинальном источнике в пакете просто нет. Решение — либо перейти на схему с policy routing, либо поставить на VPS обратный прокси, который добавит адрес в заголовок.

Если на VPS стоит обратный прокси, адрес теряется потому, что соединение к домашней машине устанавливает прокси от своего имени. Но здесь информация не утрачена: прокси знает настоящий адрес и передаёт его в `X-Forwarded-For`. Дальше вопрос в том, доверяет ли внутренний сервер этому заголовку и правильно ли настроена цепочка — тема разобрана в [заметке про обратный прокси](./reverse-proxy-tls.md), там же про то, почему доверять `X-Forwarded-*` можно только от контролируемых адресов.

Для не-HTTP протоколов заголовков нет, и вместо них используется **PROXY protocol**: прокси перед началом обмена отправляет короткий заголовок с оригинальными адресами и портами. Работает на уровне TCP, поддерживается nginx (модуль `stream`), HAProxy и большинством современных серверов приложений. Включать его нужно с обеих сторон согласованно — принимающая сторона обязана ожидать заголовок, иначе он попадёт в поток как мусорные байты и сломает протокол.

**Где терминировать TLS** — отдельное решение с ясным критерием: доверяете ли вы провайдеру VPS.

Терминация на VPS удобнее: сертификаты выпускаются там, где виден порт 80 и куда указывает DNS, ACME работает без дополнительных ухищрений, внутрь туннеля идёт обычный HTTP. Но приватный ключ и расшифрованный трафик находятся на арендованной машине, к которой у провайдера есть физический доступ.

Сквозная терминация на домашней машине означает, что VPS проксирует TLS вслепую, не имея ключа. На уровне TCP это делается пробросом порта 443 без разбора содержимого; ACME при этом требует либо DNS-01, либо проброса 80-го порта внутрь. Это правильный вариант для чувствительных данных и лишняя сложность для панели управления торрент-клиентом.

## Альтернативы

Самодельная схема — не единственный и не всегда лучший путь.

| Решение | Что нужно | Свой домен | Область | Когда честно лучше |
|---------|-----------|-----------|---------|--------------------|
| `ssh -R` | только SSH-доступ к VPS | да | один-два порта | разовая задача, ничего не хочется настраивать |
| frp | VPS + демон с обеих сторон | да | TCP/UDP/HTTP, P2P-режим | нужен проброс портов, но не нужен L3-туннель |
| Tailscale | ничего своего | Funnel, ограниченно | mesh между своими устройствами | личный доступ, а не публикация |
| Headscale | свой сервер управления | нет Funnel | то же, но без внешней зависимости | mesh без доверия внешнему сервису |
| Cloudflare Tunnel | ничего своего | да | HTTP(S) | нет VPS и не нужен медиатрафик |

**`ssh -R`** — самый короткий путь: `ssh -N -R 8080:localhost:80 vps` открывает на VPS порт, ведущий на локальный сервис. По умолчанию проброшенный порт слушает только на loopback, так что для публикации наружу на сервере нужен `GatewayPorts yes` (или `clientspecified`) в `sshd_config`. Обвязка обязательна: голый `ssh` умирает вместе с сетью и не возвращается, поэтому используют `autossh` с `ServerAliveInterval` либо юнит systemd с `Restart=always`. Годится для разовых задач и административного доступа; для постоянной публикации проигрывает WireGuard по накладным расходам и по устойчивости.

**frp** — специализированный демон обратного проксирования: `frps` на VPS, `frpc` дома. Умеет TCP, UDP, HTTP и HTTPS с виртуальными хостами, имеет режим `stcp` с предварительным ключом и `xtcp` с прямым P2P-соединением между клиентами. Разумный выбор, когда задача — именно опубликовать несколько сервисов, а полноценная L3-связность между сетями не нужна.

**Tailscale и Headscale** решают в первую очередь другую задачу — mesh-сеть между вашими устройствами с автоматическим обходом NAT. Публикация наружу у Tailscale называется Funnel и работает только по HTTPS. Существенная деталь для тех, кто рассматривает самостоятельный хостинг: в Headscale, открытой реализации сервера управления, Funnel не реализован. То есть Headscale даёт независимость от внешнего сервиса, но публиковать сервисы наружу с ним придётся тем же способом, что и в этой статье.

**Cloudflare Tunnel** убирает необходимость в VPS вообще: демон `cloudflared` на домашней машине устанавливает исходящее соединение к сети Cloudflare, публичных портов не открывается ни одного. Это лучшее решение по безопасности из перечисленных — публичной поверхности атаки просто нет. Ограничение существенное и касается ровно медиасерверов: правило о непропорциональной доле не-HTML-контента, раньше жившее в разделе 2.8 пользовательского соглашения, перенесено в отдельные условия для CDN, но продолжает действовать для трафика, проходящего через CDN, а публичные маршруты Tunnel идут именно через него. Видео и крупные файлы, размещённые вне сервисов Cloudflare, под ограничение подпадают. Для панелей, API и обычных сайтов — отличный вариант, для раздачи медиатеки наружу — нет.

## Грабли

### MTU и фрагментация

Самая коварная проблема схемы, потому что она проявляется избирательно. Симптом: короткие запросы проходят, страницы открываются, а скачивание больших файлов виснет намертво; `ping` работает, `ping -s 1400` — нет.

Механика такая. Пакет, идущий в туннель, обрастает внешним заголовком: IP, UDP, служебные поля WireGuard и метка аутентификации. `wg-quick` берёт MTU маршрута до эндпоинта (или 1500 по умолчанию) и вычитает **80 байт** — отсюда типовые 1420 на интерфейсе `wg0`. Число 80 взято по худшему случаю: 40 байт заголовка IPv6, 8 UDP, 16 служебных полей WireGuard и 16 байт тега Poly1305. Для IPv4 накладные расходы на 20 байт меньше, так что 1420 — консервативная оценка.

Проблема возникает, когда где-то на пути MTU оказывается ещё меньше — PPPoE у провайдера, дополнительный туннель, экзотическая сеть у клиента. Отправитель узнаёт об этом через ICMP-сообщение «требуется фрагментация», и если этот ICMP отфильтрован (а фильтруют его повсеместно, «на всякий случай»), обнаружение MTU пути ломается: отправитель шлёт большие пакеты, они молча отбрасываются, соединение зависает. Явление известно как чёрная дыра PMTU.

Надёжное лекарство — не полагаться на ICMP, а править MSS в SYN-пакетах на входной точке, приводя его к MTU исходящего маршрута. Это то самое правило из набора выше:

```bash
tcp flags syn tcp option maxseg size set rt mtu
```

Диагностика — поиск максимального проходящего размера пакетом с запретом фрагментации:

```bash
ping -M do -s 1400 -c 2 10.100.0.2      # уменьшать, пока не начнёт проходить
ip link show wg0 | grep -o 'mtu [0-9]*'
tracepath 10.100.0.2                     # покажет, где MTU уменьшается
```

Если после MSS-клампинга проблема осталась, MTU на `wg0` понижается вручную через параметр `MTU` в конфигурации — 1380 или 1360 решают почти все случаи ценой небольшой потери эффективности.

### Лимиты трафика

Тарифы дешёвых VPS почти всегда включают квоту: от терабайта в месяц у бюджетных провайдеров. Превышение стоит денег либо приводит к обрезанию скорости до символической. Считать надо реалистично: один вечер просмотра фильма в 1080p снаружи — это 5–10 ГБ, и месячная квота в 1 ТБ исчерпывается парой активных зрителей.

Мониторинг ставится сразу, а не после первого счёта:

```bash
vnstat -i eth0 -m                        # помесячная статистика
nft list counters                        # если счётчики заведены в правилах
```

Вывод для медиасервера: раздавать видео наружу через такую схему — плохая идея по деньгам и по полосе. Разумнее ограничить публикацию управляющими интерфейсами, а собственно доступ к медиа оставить внутри VPN.

### Безопасность

Главный сдвиг модели угроз: VPS становится единственной публичной поверхностью, и всё, что на нём открыто, открыто всему интернету.

Минимум открытых портов — 51820/udp для туннеля и то, что публикуется по существу. SSH после поднятия туннеля переводится на прослушивание изнутри `wg0` либо вовсе на адрес туннеля через `ListenAddress 10.100.0.1` в `sshd_config`: тогда перебор паролей снаружи технически невозможен. Парольная аутентификация отключается в пользу ключей.

Разделение сетей важнее, чем кажется. Компрометация VPS не должна давать доступ ко всей домашней сети — а даёт, если `AllowedIPs` на домашней стороне открывает подсеть целиком и firewall дома пропускает всё из `wg0`. Правильная настройка ограничивает доступ из туннеля конкретными адресами и портами:

```bash
# на домашней машине: из туннеля можно только к опубликованным сервисам
iifname "wg0" tcp dport { 80, 443 } accept
iifname "wg0" drop
```

Публикуемое приложение должно иметь собственную аутентификацию. Схема с VPS не добавляет никакой защиты — она ровно наоборот выносит сервис в интернет, и панель без пароля будет найдена сканерами в течение суток.

Наконец, обновления. Входная точка — машина, которую обязательно надо держать в актуальном состоянии и желательно с автоматической установкой обновлений безопасности, потому что именно она принимает удар первой.

## Источники

- [WireGuard — wg(8)](https://man7.org/linux/man-pages/man8/wg.8.html) — `AllowedIPs`, `PersistentKeepalive` и рекомендованные 25 секунд
- [WireGuard — wg-quick(8)](https://man7.org/linux/man-pages/man8/wg-quick.8.html) и [исходник `wg-quick/linux.bash`](https://github.com/WireGuard/wireguard-tools/blob/master/src/wg-quick/linux.bash) — автоопределение MTU как «MTU маршрута минус 80»
- [Документация ядра Linux: сетевая подсистема](https://docs.kernel.org/networking/index.html) — форвардинг, conntrack и его параметры
- [nftables wiki](https://wiki.nftables.org/) — приоритеты хуков, синтаксис DNAT/SNAT и MSS-клампинга; приведённые наборы правил проверены `nft -c -f` на nftables 1.0.9
- [FreeBSD: pf.conf(5)](https://man.freebsd.org/cgi/man.cgi?query=pf.conf&sektion=5) — синтаксис трансляции; расхождение между ветками проверено по дереву исходников `stable/14` (правила `rdr`/`nat`) и `stable/15` (`rdr-to`/`nat-to`)
- [frp — README](https://github.com/fatedier/frp) — типы прокси, режимы `stcp` и `xtcp`
- [Tailscale Funnel](https://tailscale.com/blog/introducing-tailscale-funnel) и [Headscale](https://github.com/juanfont/headscale) — публикация наружу и её отсутствие в открытой реализации сервера управления
- [Cloudflare: доставка видео](https://developers.cloudflare.com/fundamentals/reference/policies-compliances/delivering-videos-with-cloudflare/) и [обновление условий использования](https://blog.cloudflare.com/updated-tos) — перенос ограничения на не-HTML-контент в условия для CDN
- [PROXY protocol — спецификация HAProxy](https://www.haproxy.org/download/2.8/doc/proxy-protocol.txt) — передача адреса клиента на уровне TCP

---

## Связанные заметки

- [Linux — сеть: стек, конфигурация, firewall](./linux-networking.md) — nftables, policy routing и сетевые tunables в общем виде
- [Обратный прокси и TLS: nginx vs Caddy](./reverse-proxy-tls.md) — что делать с трафиком после того, как он попал внутрь; доверие к `X-Forwarded-*`
- [OpenVPN против WireGuard](./openvpn-vs-wireguard.md) — выбор туннеля и разбор `AllowedIPs` как множества маршрутов
- [Медиасервер: Transmission + Sonarr/Radarr + Plex](./media-server-guide.md) — типовой набор сервисов, ради которых схема и строится, и причина считать трафик
- [SSH углублённо](./ssh-advanced.md) — обратные туннели `-R`, `GatewayPorts` и ограниченные ключи для входной точки
- [FreeBSD — bhyve: виртуализация после jails](./freebsd-bhyve.md) — изолированный мост с NAT для виртуальных машин, та же конструкция
- [AEAD: аутентифицированное шифрование, nonce и лимиты](./crypto-aead.md) — откуда берутся 16 байт тега Poly1305 в расчёте MTU туннеля
- [Обмен ключами: X25519, KEM, вывод ключей и привязка к транскрипту](./crypto-key-exchange.md) — что происходит в том самом рукопожатии, по времени которого проверяется здоровье туннеля
