Детальные конфигурации для продвинутых сценариев: Xray inbound, sing-box routing, DNS, WireGuard, мониторинг и оптимизация.
Во всех примерах используются плейсхолдеры:
SERVER_IP— публичный IP-адрес VPS;ROUTER_IP— IP-адрес роутера в локальной сети (по умолчанию192.168.1.1);UUID— идентификатор клиента VLESS;PRIVATE_KEY/PUBLIC_KEY— ключи Reality;SHORT_ID— short ID Reality;SNI_DOMAIN— домен прикрытия (например,www.amazon.com).
- Xray Inbound (VLESS + Reality + Vision)
- Sing-box Config для Podkop (selective routing)
- Bypass Rules (исключение трафика из туннеля)
- WireGuard (удалённый доступ к роутеру)
- AdGuard Home + Xray
- Списки маршрутизации (selective mode)
- Тестирование и отладка
- Мониторинг и логирование
- Оптимизация производительности
- Управление 3x-ui через API
Серверный inbound. На стороне сервера в realitySettings хранится только приватный ключ; публичный ключ передаётся клиенту отдельно (в ссылке vless:// или в клиентском конфиге).
{
"inbounds": [
{
"port": 443,
"protocol": "vless",
"tag": "vless-reality",
"settings": {
"clients": [
{
"id": "UUID",
"email": "client-1",
"flow": "xtls-rprx-vision"
}
],
"decryption": "none"
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"show": false,
"dest": "SNI_DOMAIN:443",
"xver": 0,
"serverNames": [
"SNI_DOMAIN"
],
"privateKey": "PRIVATE_KEY",
"maxTimeDiff": 70000,
"shortIds": [
"SHORT_ID"
]
}
},
"sniffing": {
"enabled": true,
"destOverride": [
"http",
"tls"
]
}
}
]
}Способ 1 — через панель 3x-ui:
- Inbound List → выбрать inbound → Edit.
- В секции Security нажать Get New Cert.
- Панель сгенерирует пару ключей (приватный остаётся на сервере, публичный нужен клиенту).
- Скопировать значения.
Способ 2 — через CLI (X25519, формат для Reality):
# На VPS
xray x25519
# Вывод:
# Private key: ... → privateKey в серверный конфиг
# Public key: ... → publicKey в клиентский конфигReality использует ключи X25519, а не ECDSA prime256v1. Команда
openssl ecparamдля генерации ключей Reality не подходит — используйтеxray x25519.
Генерация short ID:
openssl rand -hex 8{
"log": {
"level": "info",
"timestamp": true
},
"inbounds": [
{
"type": "tun",
"tag": "tun-in",
"inet4_address": "198.18.0.1/15",
"auto_route": false,
"strict_route": false,
"sniff": true,
"sniff_override_destination": false,
"domain_strategy": "prefer_ipv4"
},
{
"type": "dns",
"tag": "dns-in",
"listen": "127.0.0.1",
"listen_port": 7874
}
],
"outbounds": [
{
"type": "vless",
"tag": "proxy",
"server": "SERVER_IP",
"server_port": 443,
"uuid": "UUID",
"flow": "xtls-rprx-vision",
"tls": {
"enabled": true,
"server_name": "SNI_DOMAIN",
"utls": {
"enabled": true,
"fingerprint": "chrome"
},
"reality": {
"enabled": true,
"public_key": "PUBLIC_KEY",
"short_id": "SHORT_ID"
}
}
},
{
"type": "direct",
"tag": "direct"
},
{
"type": "dns",
"tag": "dns-out"
}
],
"route": {
"rules": [
{
"protocol": "dns",
"outbound": "dns-out"
},
{
"ip_is_private": true,
"outbound": "direct"
},
{
"rule_set": "blocked-domains",
"outbound": "proxy"
},
{
"outbound": "direct"
}
],
"auto_detect_interface": true,
"override_android_vpn": true
},
"dns": {
"servers": [
{
"tag": "proxydns",
"address": "tcp+local://8.8.8.8:53",
"detour": "proxy"
},
{
"tag": "localdns",
"address": "local"
}
],
"rules": [
{
"rule_set": "blocked-domains",
"server": "proxydns"
},
{
"server": "localdns"
}
]
}
}В правилах route и dns теги серверов (
proxydns,localdns) и rule_set должны совпадать буквально. Частая ошибка — несоответствие тегов (например, сервер объявлен какproxydns, а в правиле указанproxyns), из-за чего конфиг не загружается.
Точный путь зависит от версии Podkop. Проверить кандидатов:
cat /etc/podkop/config.json
cat /usr/share/podkop/config.json
cat /etc/sing-box/config.jsonРедактирование и применение:
vi /etc/podkop/config.json
/etc/init.d/podkop restartПрименяется для трафика, который должен идти напрямую: внутренние сервисы, PPTP/GRE-туннели, CDN.
{
"route": {
"rules": [
{
"ip_cidr": [
"203.0.113.45/32",
"10.0.0.0/8"
],
"outbound": "direct"
}
]
}
}Адреса в примере — документационные (RFC 5737
203.0.113.0/24и RFC 191810.0.0.0/8). Замените на фактические IP, которые требуется исключить.
ssh root@ROUTER_IPcat > /etc/firewall.user << 'EOF'
#!/bin/sh
# Исключение трафика к указанному IP из туннеля sing-box.
# Помечает пакеты меткой 0x100; политика маршрутизации
# направляет их в таблицу с прямым шлюзом.
DIRECT_IP="203.0.113.45"
GATEWAY="192.168.1.1"
MARK="0x100"
TABLE="100"
ip rule add fwmark "$MARK" lookup "$TABLE" 2>/dev/null
ip route replace default via "$GATEWAY" table "$TABLE"
iptables -t mangle -A PREROUTING -d "$DIRECT_IP" -j MARK --set-mark "$MARK"
EOF
chmod +x /etc/firewall.user
/etc/init.d/firewall restartСпособ 3.1 (через sing-box) предпочтительнее: он не зависит от структуры firewall и не конфликтует с правилами Podkop. Способ 3.2 — резервный, для случаев когда перехват происходит вне sing-box.
Безопасная альтернатива публичному SSH.
# На роутере (OpenWRT)
apk add wireguard-tools luci-proto-wireguard
mkdir -p /etc/wireguard
cd /etc/wireguard
umask 077
# Ключи сервера (роутера)
wg genkey | tee server_private.key | wg pubkey > server_public.key
# Ключи клиента
wg genkey | tee client_private.key | wg pubkey > client_public.key
# Pre-shared key (общий)
wg genpsk > preshared.keyUCI-конфиг не выполняет подстановку $(...). Значения ключей нужно вписать явно или подставить через скрипт.
SERVER_PRIV=$(cat /etc/wireguard/server_private.key)
CLIENT_PUB=$(cat /etc/wireguard/client_public.key)
PSK=$(cat /etc/wireguard/preshared.key)
cat >> /etc/config/network << EOF
config interface 'wg0'
option proto 'wireguard'
option private_key '$SERVER_PRIV'
option listen_port '51820'
list addresses '10.10.10.1/24'
config wireguard_wg0
option description 'remote-client'
option public_key '$CLIENT_PUB'
option preshared_key '$PSK'
list allowed_ips '10.10.10.2/32'
option route_allowed_ips '1'
EOF
/etc/init.d/network restart
ip addr show wg0
wg show[Interface]
PrivateKey = <содержимое client_private.key>
Address = 10.10.10.2/24
DNS = 192.168.1.1
[Peer]
PublicKey = <содержимое server_public.key>
PresharedKey = <содержимое preshared.key>
Endpoint = SERVER_PUBLIC_IP:51820
AllowedIPs = 10.10.10.0/24, 192.168.1.0/24
PersistentKeepalive = 25
Endpointуказывает на публичный IP роутера. Для доступа к ресурсам локальной сети роутера вAllowedIPsвключается её подсеть (192.168.1.0/24).
cat >> /etc/config/firewall << 'EOF'
config zone
option name 'wireguard'
option input 'ACCEPT'
option output 'ACCEPT'
option forward 'ACCEPT'
list network 'wg0'
config forwarding
option src 'wireguard'
option dest 'lan'
config forwarding
option src 'lan'
option dest 'wireguard'
config rule
option name 'Allow-WireGuard'
option src 'wan'
option proto 'udp'
option dest_port '51820'
option target 'ACCEPT'
EOF
/etc/init.d/firewall restartПравило
Allow-WireGuardнеобходимо: без него входящие UDP-пакеты на порт 51820 из WAN будут отброшены, и подключение не установится.
# Актуальную версию см. на https://github.com/AdguardTeam/AdGuardHome/releases
VERSION="v0.107.77"
wget "https://github.com/AdguardTeam/AdGuardHome/releases/download/${VERSION}/AdGuardHome_linux_amd64.tar.gz"
tar -xzf AdGuardHome_linux_amd64.tar.gz# Запуск на loopback (не на публичном интерфейсе)
./AdGuardHome/AdGuardHome -h 127.0.0.1Веб-интерфейс первичной настройки доступен на http://127.0.0.1:3000. После настройки:
- порт DNS-сервера AdGuard Home —
53(или иной, заданный в мастере); - Upstream DNS —
8.8.8.8, резервный1.1.1.1; - в 3x-ui (Settings → DNS Settings) указать AdGuard Home как upstream:
127.0.0.1:53.
Web-порт (
3000) и DNS-порт (53) — разные порты. Xray направляется на DNS-порт, а не на web-порт.
AdGuard Home поддерживает установку в качестве сервиса собственными средствами — это надёжнее ручного unit-файла:
./AdGuardHome/AdGuardHome -s install
systemctl status AdGuardHomeУправление:
./AdGuardHome/AdGuardHome -s start
./AdGuardHome/AdGuardHome -s stop
./AdGuardHome/AdGuardHome -s uninstallСовременные версии Podkop используют форматы community-листов (geosite, rule-set, текстовые списки доменов/подсетей). Точные пути и форматы зависят от версии — ориентироваться на интерфейс LuCI.
# Домены, маршрутизируемые через туннель (proxy):
example-blocked-1.com
example-blocked-2.org
# Домены, идущие напрямую (direct):
*.lan
internal.example.local
# Через туннель (proxy):
198.51.100.0/24
# Напрямую (direct):
192.168.0.0/16
10.0.0.0/8
172.16.0.0/12
Локальные диапазоны RFC 1918 (
192.168.0.0/16,10.0.0.0/8,172.16.0.0/12) всегда должны идти напрямую, иначе сломается доступ к локальной сети.
# 1. TLS handshake с доменом прикрытия — должен вернуть
# валидный сертификат целевого сайта (SNI_DOMAIN)
openssl s_client -connect SERVER_IP:443 -servername SNI_DOMAIN </dev/null
# 2. TLS fingerprint — сравнить с эталоном браузера
curl -v --http2 https://tls.peet.ws/api/all
# Эталон: https://browserleaks.com/tls
# 3. Проверка DNS
curl 'https://dns.google/resolve?name=example.com'
# 4. Проверка IP выхода — должен совпадать с SERVER_IP
curl https://ifconfig.me# Уровень логирования в конфиге sing-box:
# "log": { "level": "debug", "timestamp": true }
# (debug — только на время диагностики, не для постоянной работы)
/etc/init.d/podkop restart
logread -e podkop -f# 1. Правила перехвата в nftables
nft list ruleset | grep -i tun
# 2. DNS-ответ должен содержать FakeIP из диапазона 198.18.0.0/15
dig @127.0.0.1 -p 7874 example.com
# 3. Записи conntrack для FakeIP во время активного трафика
cat /proc/net/nf_conntrack | grep '198.18' | head -5{
"log": {
"access": "/var/log/xray/access.log",
"error": "/var/log/xray/error.log",
"loglevel": "warning"
}
}
warningдостаточно для продакшена.infoиdebugувеличивают объём логов и нагрузку.
cat > /etc/logrotate.d/xray << 'EOF'
/var/log/xray/*.log {
daily
rotate 7
compress
delaycompress
notifempty
missingok
create 0640 root root
sharedscripts
postrotate
systemctl reload 3x-ui > /dev/null 2>&1 || true
endscript
}
EOFcat >> /etc/crontabs/root << 'EOF'
0 * * * * echo "$(date '+\%F \%T'): $(free -m | awk '/Mem/{print $3"/"$2" MB"}')" >> /tmp/mem.log
EOF
/etc/init.d/cron restartДля постоянного мониторинга предпочтительнее
luci-app-statistics(collectd) — он даёт графики и не нагружает flash частыми записями.
-
Отключить неиспользуемые сервисы:
/etc/init.d/dnsmasq stop # только если DNS полностью обрабатывает sing-boxuhttpd(LuCI) отключать не рекомендуется — теряется веб-управление. Достаточно ограничить его доступность по WAN через firewall. -
Использовать урезанные geo-базы (geoip-lite/geosite-lite) или текстовые списки доменов вместо полной базы — это основной потребитель RAM.
-
Включить SQM для управления буфером (bufferbloat):
apk add sqm-scripts luci-app-sqm # Настройка: LuCI → Network → SQM QoS
-
Увеличить TCP-буферы:
cat > /etc/sysctl.d/99-network.conf << 'EOF' net.core.rmem_max=67108864 net.core.wmem_max=67108864 net.ipv4.tcp_rmem=4096 87380 33554432 net.ipv4.tcp_wmem=4096 65536 33554432 EOF sysctl -p /etc/sysctl.d/99-network.conf
-
Включить BBR (если модуль доступен в сборке):
modprobe tcp_bbr 2>/dev/null echo "net.ipv4.tcp_congestion_control=bbr" >> /etc/sysctl.d/99-network.conf sysctl -p /etc/sysctl.d/99-network.conf sysctl net.ipv4.tcp_congestion_control # проверка
BBR влияет на исходящие соединения самого роутера. На транзитный трафик, проходящий через туннель, основное влияние оказывает congestion control на VPS — BBR имеет смысл включать и там.
Базовый клиент на Python. Учётные данные и адрес панели передаются как параметры, не хранятся в коде.
import requests
class XUIClient:
def __init__(self, base_url, username, password, verify_tls=True):
self.base_url = base_url.rstrip("/")
self.session = requests.Session()
self.session.verify = verify_tls
self._login(username, password)
def _login(self, username, password):
r = self.session.post(
f"{self.base_url}/login",
json={"username": username, "password": password},
timeout=10,
)
r.raise_for_status()
return r.json()
def add_client(self, inbound_id, client_data):
r = self.session.post(
f"{self.base_url}/panel/api/inbounds/addClient",
json={"id": inbound_id, "settings": client_data},
timeout=10,
)
r.raise_for_status()
return r.json()
def subscription_url(self, sub_id):
return f"{self.base_url}/sub/{sub_id}"
# Пример использования
client = XUIClient(
base_url="https://SERVER_IP:2053",
username="admin",
password="CHANGE_ME",
verify_tls=True, # False только при self-signed сертификате
)