Реверс-прокси
Caddy, Nginx, HAProxy, Traefik — конфиги дословно.
Общий фон: caddy — «скопировал вставил», серты автоматом; nginx — модули/контроль
пруф. 443 — у обратного прокси; бот 8080, панель 3000, метрики 3001, сабка 3010
пруф. Два прокси на одном сервере (nginx+caddy) не живут
пруф. nginx и бот обязаны быть в одной docker-сети, иначе host not found in upstream "remnawave_bot"
пруф.
4.1 Caddy
- Полная схема стека (мейн-сервер) пруф:
https://webhook.domain.com {
handle /tribute-webhook* { reverse_proxy localhost:8081 }
handle /cryptobot-webhook* { reverse_proxy localhost:8081 }
handle /health { reverse_proxy localhost:8081/health }
}
https://miniapp.domain.com { reverse_proxy * http://remnawave-telegram-mini-app:3020 }
https://sub.domain.com { reverse_proxy * http://remnawave-subscription-page:3010 }
https://monitoring.domain.com { reverse_proxy * http://uptime-kuma:3001 }
https://besz.domain.com { reverse_proxy * http://beszel:8090 }
:443 { tls internal; respond 204 }- Единый webhook-сервер бота (v2.6+): один порт 8080, пути
/yookassa-webhook,/platega-webhook,/cryptobot-webhook,/wata-webhook,/heleket-webhook,/tribute-webhook,/pal24-webhook,/mulenpay-webhook,/freekassa-webhook,/cloudpayments-webhook,/remnawave-webhook; эталон Caddyfile c(proxy_defaults): пруф:
(proxy_defaults) {
header_up Host {host}
header_up X-Real-IP {remote_host}
transport http { read_buffer 0 }
}
hooks.example.com {
encode gzip zstd
@webhooks {
path /yookassa-webhook
path /platega-webhook
path /cryptobot-webhook
path /wata-webhook
path /heleket-webhook
path /tribute-webhook
path /pal24-webhook
path /mulenpay-webhook
path /freekassa-webhook
path /cloudpayments-webhook
path /remnawave-webhook
}
handle @webhooks { reverse_proxy remnawave_bot:8080 { import proxy_defaults } }
handle /app-config.json {
header Access-Control-Allow-Origin "*"
reverse_proxy remnawave_bot:8080 { import proxy_defaults }
}
handle { reverse_proxy remnawave_bot:8080 { import proxy_defaults } }
}
cabinet.example.com {
encode gzip zstd
handle /api/* {
uri strip_prefix /api
reverse_proxy remnawave_bot:8080 { import proxy_defaults }
}
handle {
root * /srv/cabinet
try_files {path} /index.html
file_server
@static path *.js *.css *.woff *.woff2 *.ttf *.ico *.png *.jpg *.jpeg *.svg *.webp *.gif
header @static Cache-Control "public, max-age=31536000, immutable"
@html path *.html /
header @html Cache-Control "no-cache, must-revalidate"
}
}- Канонический Caddyfile кабинета от Егора — с
import geoip_blockи теми же кеш-правилами 12.02.26; вебсокет кабинета работает сам (cabinet/ws сноси в caddy) пруф; wss-фикс пруф:handle /cabinet/ws* { reverse_proxy remnawave_bot:8080 }. - Selfsteal-заглушка Caddy 27.02.26:
{
https_port {$SELF_STEAL_PORT}
default_bind 127.0.0.1
servers { listener_wrappers { proxy_protocol { allow 127.0.0.1/32 } tls } }
auto_https disable_redirects
}
http://{$SELF_STEAL_DOMAIN} { bind 0.0.0.0; redir https://{$SELF_STEAL_DOMAIN}{uri} permanent }
https://{$SELF_STEAL_DOMAIN} { root * /var/www/html; try_files {path} /index.html; file_server }
:{$SELF_STEAL_PORT} { tls internal; respond 204 }
:80 { bind 0.0.0.0; respond 204 }.env: SELF_STEAL_DOMAIN=subdomen.domen.ru, SELF_STEAL_PORT=9443; docker-compose с network_mode: "host"; target ноды = 127.0.0.1:9443, SNI = СВОЙ домен
08.06.26.
- Caddy на два порта для xhttp+TCP selfsteal:
https_port {$SELF_STEAL_PORT_TCP}+https_port {$SELF_STEAL_PORT_XHTTP}08.12.25. - Caddy DNS-challenge Cloudflare (wildcard без перетасовки) 27.09.25:
FROM caddy:2.9.1-builder AS builder
RUN xcaddy build \
--with github.com/caddy-dns/cloudflare
FROM caddy:2
COPY --from=builder /usr/bin/caddy /usr/bin/caddy{
email {$EMAIL}
acme_dns cloudflare {$CF_API_TOKEN}
https_port {$SELF_STEAL_PORT}
default_bind 127.0.0.1
servers {
listener_wrappers {
proxy_protocol { allow 127.0.0.1/32 }
tls
}
}
auto_https disable_redirects
log {
output file /var/log/caddy/access.log {
roll_size 10MB
roll_keep 5
roll_keep_for 720h
roll_compression gzip
}
level ERROR
format json
}
}yourdomain.com {
handle /api* {
reverse_proxy 127.0.0.1:8443 {
flush_interval -1
transport http { versions h2c 1.1 }
}
}
respond "OK" 200
}- Caddy для кабинета из README:
handle /api/* { uri strip_prefix /api; reverse_proxy remnawave_bot:8080 }пруф. - Пересборка caddy-security (защита/ratelimit/geolocation) 01.03.26:
FROM caddy:2.10.2-builder AS builder
RUN xcaddy build \
--with github.com/greenpau/caddy-security@v1.1.27 \
--with github.com/caddy-dns/cloudflare \
--with github.com/mholt/caddy-ratelimit \
--with github.com/caddyserver/cache-handler@v0.16.0 \
--with pkg.jsn.cam/caddy-defender \
--with github.com/porech/caddy-maxmind-geolocation
FROM caddy:2.10.2
COPY --from=builder /usr/bin/caddy /usr/bin/caddy- Caddy reload ошибка
dial tcp 127.0.0.1:2019: connection refused— рестартовать контейнер пруф; в докере caddy слушает только localhost:9443 при селфстиле пруф.
4.2 Nginx
- Полный конфиг hooks+miniapp (дословно) 06.12.25:
# Hooks + API domain - hooks.domain.com
server {
listen 80;
listen 443 ssl http2;
server_name hooks.domain.com;
ssl_certificate /etc/ssl/private/hooks.fullchain.pem;
ssl_certificate_key /etc/ssl/private/hooks.privkey.pem;
client_max_body_size 32m;
location = /yookassa-webhook {
proxy_pass http://remnawave_bot_unified;
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_read_timeout 120s; proxy_send_timeout 120s;
proxy_buffering off; proxy_request_buffering off;
}
# аналогично: /platega-webhook, /cryptobot-webhook, /wata-webhook,
# /heleket-webhook, /tribute-webhook, /pal24-webhook, /mulenpay-webhook
location = /app-config.json {
add_header Access-Control-Allow-Origin "*";
proxy_pass http://remnawave_bot_unified;
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;
}
location / {
proxy_pass http://remnawave_bot_unified;
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_read_timeout 120s; proxy_send_timeout 120s;
proxy_buffering off; proxy_request_buffering off;
}
}
# Miniapp domain
server {
listen 80; listen 443 ssl http2;
server_name miniapp.domain.com;
ssl_certificate /etc/ssl/private/miniapp.fullchain.pem;
ssl_certificate_key /etc/ssl/private/miniapp.privkey.pem;
client_max_body_size 32m;
location /miniapp/ {
proxy_pass http://remnawave_bot_unified;
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_read_timeout 120s; proxy_send_timeout 120s;
proxy_buffering off; proxy_request_buffering off;
}
location = /app-config.json {
add_header Access-Control-Allow-Origin "*";
proxy_pass http://remnawave_bot_unified;
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;
}
location / {
root /var/www/remnawave-miniapp;
try_files $uri $uri/ /index.html;
expires 1h;
add_header Cache-Control "public, immutable";
}
}
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;
ssl_reject_handshake on;
}- Upstream — в основной nginx.conf, НЕ в conf.d (
events {} и http {} в default.conf запрещены) пруф:
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events { worker_connections 1024; }
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
upstream remnawave_bot { server 127.0.0.1:8080; }
include /etc/nginx/conf.d/*.conf;
}- Компактный конфиг вебхуков одной локацией пруф:
client_max_body_size 32m;
location ~ ^/(webhook|.*-webhook|app-config\.json) {
if ($request_uri ~* "/app-config.json") { add_header Access-Control-Allow-Origin "*"; }
proxy_pass http://remnawave-bot;
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_read_timeout 120s; proxy_send_timeout 120s;
proxy_buffering off; proxy_request_buffering off;
}- API-локация ремны с websocket-апгрейдом пруф:
location ^~ /api/ {
proxy_http_version 1.1;
proxy_pass http://remnawave;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
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 X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}- Кабинет за nginx: upstream обязателен вверху conf.d;
location /api/ { rewrite ^/api/(.*) /$1 break; proxy_pass http://remnawave_bot:8080; ... }; фронт —proxy_pass http://cabinet_frontend:80c error_page 404 → /index.html пруф. - Блок-кэш кабинета + статики пруф:
location ~* \.html?$ {
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
expires -1;
add_header Pragma "no-cache";
}
location ~* \.(?:ico|css|js|gif|jpe?g|png|woff2?|eot|ttf|svg)$ {
expires 1y;
access_log off;
add_header Cache-Control "public";
}- allowlist IP ЮKassa на реверсе пруф:
allow 185.71.76.0/27;
allow 185.71.77.0/27;
allow 77.75.153.0/25;
allow 77.75.154.128/25;
allow 77.75.156.11;
allow 77.75.156.35;
allow 2a02:5180::/32;
deny all;(бот должен видеть реальный IP через X-Forwarded-For; в .env YOOKASSA_TRUSTED_PROXY_NETWORKS=172.20.0.0/16 или подсети юкассы 185.71.76.0/24,185.71.77.0/24
пруф.)
- http2:
listen ... http2deprecated →http2 on;пруф;server_names_hash_bucket_size 64;приcould not build server_names_hashпруф;ssl_reject_handshake onна default_server для пустых SNI пруф. - Один
upstream remnawave_bot_unified { server remnawave_bot:8080; }на все хуки (после единого webhook-сервера 2.6) пруф; локейшеныlocation = /yookassa-webhookсproxy_buffering offпруф. - nginx для xhttp (grpc_pass на unix-сокет) пруф:
location /xhttppath/ {
client_max_body_size 0;
grpc_set_header X-Forwarded-For;
grpc_read_timeout 315;
grpc_send_timeout 5m;
grpc_pass unix:/dev/shm/xrxh.socket;
}- nginx поток с
ssl_prereadдля нескольких SNI на 443 пруф: вход 443 → по SNI на сайт (127.0.0.1:4443) или в Xray (127.0.0.1:18443). - nginx перезаписывает OPTIONS→POST (Tw CDN режет POST)
пруф:
map $request_method $proxy_method_override { default $request_method; OPTIONS POST; } - nginx переписывает poll-путь для старого ядра (26.7.11 добавил слеш) пруф:
location /api/v4/media/session/poll {
error_page 418 = @xhttp_poll_legacy;
if ($arg_sid = "") { return 418; }
rewrite ^/api/v4/media/session/poll$ /api/v4/media/session/poll/ break;
proxy_pass http://127.0.0.1:10085;
}- vhost для сабки (двумя строками) пруф:
location / { proxy_pass http://remnawave-subscription-page:3010; include /etc/nginx/conf.d/proxy.conf; }healthcheck на localhost (иначе Reverse proxy and HTTPS are required)
пруф.
server {
listen 443 ssl;
server_name yourdomain.com;
ssl_certificate /path/to/cert.crt;
ssl_certificate_key /path/to/private.key;
location / {
root /var/www/html;
index index.html;
}
location /ray {
if ($http_upgrade != "websocket") { return 404; }
proxy_redirect off;
proxy_pass http://127.0.0.1:12345;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}- Локальный nginx.conf после обновления (уязвимость nginx 14.05.2026, Caddy не пострадал) пруф; обновление — github.com/tagashi666/nginx-updater пруф.
- ЦДН-лучше-nginx-нарратив: nginx — для маскировки под сайт и сложных роутов, HAProxy — для SNI-роутинга, Caddy — по стандарту для панель/бот/кабинет пруф.
4.3 HAProxy
frontend front0
mode tcp
bind *:443 tfo
tcp-request inspect-delay 5s
tcp-request content accept if { req_ssl_hello_type 1 }
use_backend back0 if { req.ssl_sni -i end ads.x5.ru }
use_backend back1 if { req.ssl_sni -i end web.max.ru }
use_backend back2 if { req.ssl_sni -i end cloud.mail.ru }
use_backend back3 if { req.ssl_sni -i end rutube.ru }
use_backend back4 if { req.ssl_sni -i end preview.rutube.ru }
use_backend back5 if { req.ssl_sni -i end api.ok.ru }
default_backend back0
backend back0
server srv1 127.0.0.1:10000 send-proxy-v2 tfo
backend back1
server srv1 127.0.0.1:10001 send-proxy-v2 tfo- HAProxy vs iptables TCP-relay: iptables проще, HAProxy распихивает исходящий трафик (в iptables — только в 1 ноду) пруф; мосты можно делать через haproxy/nginx/iptables пруф; обход маршрутизации по SNI — через haproxy пруф.
- Прокидывание ноды через haproxy 443 по SNI → 8443/8444 (селфсни) пруф.
4.4 Traefik
services:
bot:
networks: [remnawave-network, bot_network]
labels:
- "traefik.enable=true"
- "traefik.http.routers.bot-api.rule=Host(`cabinet.домен.ru`) && PathPrefix(`/api`)"
- "traefik.http.routers.bot-api.entrypoints=https"
- "traefik.http.routers.bot-api.tls=true"
- "traefik.http.routers.bot-api.tls.certresolver=letsencrypt"
- "traefik.http.middlewares.bot-strip.stripprefix.prefixes=/api"
- "traefik.http.routers.bot-api.middlewares=bot-strip"
- "traefik.http.routers.bot-api.priority=100"
- "traefik.http.services.bot-api.loadbalancer.server.port=8080"
- "traefik.docker.network=remnawave-network"Кабинет: Host(cabinet) → порт 80; ws — PathPrefix(/cabinet/ws) priority 110
пруф.
- Traefik-проблема 2.6.0: webhook-режим не стартует с одним маршрутом в traefik пруф.