Skip to content

About

Home LAN Xray gateway with VLESS subscription pools, hot standby failover, split DNS, direct RU routing, diagnostics, and local status UI.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

35 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

proxy-xray

Telegram

Dockerized Xray gateway for a home LAN.

Русская версия: README.ru.md.

The project runs Xray-Core behind stable local ports and supervises a pool of VLESS servers from a subscription plus an optional private server list. It keeps a hot standby connection ready, checks degradation, switches away from bad paths, exposes a simple status page, and routes Russian domains/IP ranges directly.

What It Provides

  • SOCKS proxy on 1080.
  • HTTP proxy on 8123.
  • Optional plain LAN VLESS inbound on 10086.
  • Local status UI on 18080.
  • VLESS subscription refresh every two hours by default.
  • Extra private VLESS links from vless-extra.txt.
  • Two active Xray slots: current path plus hot standby.
  • Liveness, latency, small quality download, throughput, and random candidate checks.
  • RU split DNS and direct routing for .ru, .su, .рф, geosite:category-ru, and geoip:ru.
  • LoyalSoldier geoip.dat / geosite.dat asset management.
  • Telegram notification after successful failover recovery.
  • SSH deploy script for a home server.

Repository Safety

Real connection data is intentionally not tracked.

Ignored local files:

  • .env - subscription URL, Telegram token/chat id, LAN VLESS UUID.
  • vless-extra.txt - private VLESS links.
  • state.json - cached subscription candidates and measured server state.
  • vless-lan-qr.png - local QR code.
  • assets/ - downloaded geo assets.

Use the example files as templates:

cp .env.example .env
cp vless-extra.example.txt vless-extra.txt
cp state.example.json state.json
mkdir -p assets

Then edit .env and vless-extra.txt.

Configuration

.env:

# VLESS subscription URL from your provider.
XRAY_SUB_URL=https://example.com/subscription

# Non-RU DNS transport: socks (recommended), http, or direct.
# direct completely disables DNS encapsulation through VLESS.
DNS_GLOBAL_ROUTE=socks

# UUID for local LAN clients connecting to this gateway on port 10086.
# Generate once with: python3 -c 'import uuid; print(uuid.uuid4())'
# Use the same value in V2RayTun / VLESS client config.
INBOUND_VLESS_ID=00000000-0000-0000-0000-000000000000

# Optional Telegram notification settings.
# TELEGRAM_BOT_TOKEN: create a bot with @BotFather and copy its HTTP API token.
# TELEGRAM_CHAT_ID: send any message to the bot, then run:
# curl "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/getUpdates"
# Use message.chat.id from the response.
TELEGRAM_BOT_TOKEN=
TELEGRAM_CHAT_ID=

TZ=Europe/Moscow

INBOUND_VLESS_ID is not issued by the subscription provider. It is your own local client UUID for the inbound VLESS listener on the home gateway. Generate it once, keep it in .env, and use the same UUID when creating the LAN VLESS client profile.

Telegram notifications are optional. Create a bot through @BotFather, put the returned API token into TELEGRAM_BOT_TOKEN, send any message to that bot from your Telegram account, then call getUpdates and copy message.chat.id into TELEGRAM_CHAT_ID.

vless-extra.txt contains one private VLESS URI per line. These servers are not refreshed from the subscription and are sampled more often by the candidate checker.

Run Locally

docker compose build proxy-xray
docker compose up -d --force-recreate

Check status:

curl http://127.0.0.1:18080/json
docker logs proxy-xray --tail 80

Open the UI:

http://127.0.0.1:18080/

Ports

Port Purpose
1080/tcp SOCKS proxy
1080/udp SOCKS UDP
8123/tcp HTTP proxy
10086/tcp LAN VLESS inbound
18080/tcp Status web UI

The DNS relay exists inside the container, but compose does not publish port 53 by default.

LAN VLESS Client

When --inbound-vless is enabled, LAN clients can connect to the gateway through the local VLESS inbound on port 10086.

The easiest way to get the client profile is through the status UI:

  1. Open the status UI through the server LAN address, not 127.0.0.1:

    http://HOME_SERVER_IP:18080/
    
  2. Click the Q button in the top-right toolbar.

  3. Open the generated /client page.

  4. Scan the QR code from V2RayTun or copy the connection string manually.

The page builds the VLESS URL from the address used to open the UI. For example, if the UI is opened as http://192.168.2.200:18080/, the generated client URL will point to 192.168.2.200:10086.

Manual format:

vless://INBOUND_VLESS_ID@HOME_SERVER_IP:10086?security=none&type=tcp#home-proxy

Replace:

  • INBOUND_VLESS_ID with your generated value from .env;
  • HOME_SERVER_IP with the Docker host LAN IP.

How Failover Works

The supervisor starts two Xray instances:

  • active slot: receives public 1080, 8123, and 10086 through a local TCP switch and runs a small Xray balancer pool;
  • standby slot: already running with a separate hot standby balancer pool and checked in the background.

The default compose uses:

  • active pool: 3 candidates;
  • standby pool: 3 candidates.
  • extra reserve: 1 live private extra URI per active/standby slot when available;
  • hot standby fast switch: 1 full active-path failure when standby is already healthy.
  • liveness check: every 20 seconds, failover after 2 failures;
  • quality download: every 60 seconds, 512 KB, failover after 2 slow checks;
  • heavy throughput: every 300 seconds, used as a quality metric by default.

Each slot has a score-ordered pool. Xray can choose between several outbounds inside the slot, and the first outbound remains the native Xray fallback if observatory cannot choose one.

Important connection behavior:

  • Xray chooses an outbound from the active pool for new connections.
  • Xray does not migrate an already opened TCP connection to another outbound.
  • If the currently selected outbound dies or degrades, new browser/app requests can move to another outbound inside the same active pool.
  • Existing downloads, media streams, or image requests can still stall or break for a short time because they were opened through the old outbound.
  • If the whole active slot is unhealthy or degraded, the supervisor switches the public ports to the hot standby slot.

This means short interruptions are expected during a bad-server event. The goal is to make new connections recover quickly, not to make every existing TCP session survive a server failure.

Failover can be triggered by:

  • repeated health-check failures;
  • repeated high latency;
  • repeated low quality-download speed;
  • unhealthy current slot with a healthy hot standby available.

On successful switch:

  1. Public ports are pointed to the standby slot.
  2. The previous active pool head is soft-quarantined.
  3. A new standby is built.
  4. state.json is updated.
  5. Telegram notification is sent if configured.

Candidate checks are intentionally sequential. One random candidate is tested every 2-5 minutes by default, with extra-list servers weighted higher. This avoids hammering a subscription with many concurrent VLESS connections.

At startup the active slot must pass a preflight health check before public ports are attached to it. If the first outbound in the pool is dead, it is soft-quarantined and another pool is tried.

Manual Extra Pool Override

The primary dashboard has an Extra pool button in the top toolbar. It is an emergency one-shot override for cases where subscription servers are globally degraded but private extra links still work better.

When clicked and confirmed, the supervisor tests each configured extra VLESS link from vless-extra.txt separately, sequentially: two consecutive successful health requests and a small quality download are required. A transient failure is retried. Only verified transports enter the pool. gRPC and XHTTP are preferred as startup/fallback transports; native leastPing still chooses among the pool's live transports by latency. This intentionally ignores the normal active pool: 3 size and same-host limits for that one rebuild.

The verified pool starts on a temporary, loopback-only Xray slot while the current active path and hot standby stay running. After a sequential observatory warmup, repeated pool health checks and a quality download must pass before public ports switch. If balancing fails, the preferred verified transport is tried alone. The original hot standby is retained; the temporary slot becomes the active slot on success and is cleaned up on rejection. Preparation can take a minute or more, especially when transports time out.

While manual mode is active, failed health requests are retried, and the usual max_failures threshold applies instead of the one-cycle hot-standby shortcut. Recovery rechecks transports individually. A failed pooled request or an API selection snapshot alone never quarantines a transport. Only confirmed individual failures do; if no staged extra path passes, the supervisor returns to the preserved regular standby. Degradation respects the switch cooldown, but total failure does not. Existing TCP sessions can still be interrupted during a slot switch.

State File

state.json uses schema v2 and stores candidate cache plus a bounded quality history:

  • last OK/fail timestamps;
  • last latency and throughput;
  • last 50 recent checks per candidate;
  • rolling success rate, failure streak, latency EWMA, and throughput EWMA.

The file is written atomically. If it is corrupted, startup moves it to state.json.corrupt.<timestamp> and continues with an empty state instead of crashing.

Routing And DNS

The default compose routes Russian resources directly:

  • geosite:category-ru
  • regexp:.*\.ru$
  • regexp:.*\.su$
  • regexp:.*\.xn--p1ai$
  • geoip:ru

Split DNS is enabled:

  • RU domains use 77.88.8.8,77.88.8.1;
  • other domains use 8.8.8.8,1.1.1.1 through the active VLESS pool;
  • DNS_GLOBAL_ROUTE=socks sends DNS-over-TCP through the SOCKS proxy on 1080;
  • DNS_GLOBAL_ROUTE=http uses HTTP CONNECT through the HTTP proxy on 8123;
  • DNS_GLOBAL_ROUTE=direct disables encapsulation and restores direct DNS-over-TCP;
  • direct fallback for normal global domains is disabled by default, preventing silent DNS bypass around VLESS;
  • positive responses are cached in memory and stale cache entries cover short failover windows.

The subscription URL host and all VLESS endpoint hosts are maintained in an automatic bootstrap list and resolved directly over DNS-over-TCP. This prevents a startup loop where DNS waits for VLESS while VLESS waits for DNS. Only connection-infrastructure names use bootstrap resolution.

Before the proxy switch becomes ready, startup queries temporarily use direct DNS-over-TCP so an initial subscription redirect can complete. Normal global DNS automatically moves to the configured VLESS transport after port 1080 or 8123 is ready.

The user-facing proxies remain independent:

  • SOCKS5 1080: clients must pass the hostname to the proxy. In Firefox enable Proxy DNS when using SOCKS v5;
  • HTTP proxy 8123: HTTPS destinations are passed by hostname through CONNECT;
  • LAN VLESS 10086: destination hostnames pass through the inbound VLESS connection and active outbound.

To disable DNS-over-VLESS without changing any proxy or routing settings, set:

DNS_GLOBAL_ROUTE=direct

and recreate the container.

Xray sniffing is enabled on SOCKS, HTTP, and LAN VLESS inbounds with routeOnly, so TLS SNI / HTTP Host can be used for routing without rewriting the destination.

Geo Assets

LoyalSoldier assets are stored in ./assets:

  • geoip.dat
  • geosite.dat
  • assets-state.json

The image seeds bundled assets. Runtime refresh happens on schedule, but compose uses --no-asset-refresh-on-start so a slow GitHub download does not block container startup.

Status UI

Available endpoints:

  • / - primary operator dashboard.
  • /status - alias for the primary dashboard.
  • /dashboard-v5 - alias for the primary dashboard kept for bookmarked test links.
  • /dashboard-classic - previous dashboard layout kept for comparison.
  • /client - LAN VLESS connection string and QR code.
  • /servers/live - tested live servers.
  • /servers/all - all candidates.
  • /json - machine-readable status.
  • /fragments/dashboard-v5 - small HTML fragments used for primary dashboard updates.
  • /fragments/status - small HTML fragments used by the classic dashboard.
  • /control/force-extra-pool - local POST action used by the Extra pool emergency override button.
  • /diagnostics - live direct/SOCKS/HTTP URL probes and DNS probes.
  • /diagnostics.json - machine-readable sanitized diagnostic output.
  • /diagnostics/bundle - downloadable sanitized diagnostic JSON.
  • /logs - recent supervisor logs.

The dashboards do not use full-page auto-refresh. They update dynamic blocks in place every 15 seconds.

The screenshots below use synthetic demo data. Server names, endpoints, IDs, and operational details are not real.

proxy-xray status dashboard

proxy-xray live servers

The dashboard shows:

  • health indicators;
  • current connection;
  • hot standby;
  • active path selected by Xray balancer API;
  • active and standby observatory snapshots in /json;
  • candidate scores;
  • throughput;
  • subscription state;
  • geo asset state;
  • diagnostic probe entry point;
  • recent logs.

Deploy To A Home Server

The deploy script syncs files over SSH, preserves server runtime state, rebuilds the image, and recreates the container.

DEPLOY_HOST=192.168.1.10 \
DEPLOY_USER=user \
DEPLOY_PATH=/home/user/proxy-xray \
scripts/deploy-server.sh

By default it copies local .env and vless-extra.txt, but keeps server-side state.json and assets/.

See DEPLOY.md for options.

Smoke Tests

Run the client smoke container:

docker compose -f docker-compose.yml -f docker-compose.test.yml run --rm proxy-client-test

It checks:

  • status UI;
  • SOCKS proxy;
  • HTTP proxy;
  • LAN VLESS inbound;
  • basic throughput;
  • small quality download status;
  • split DNS behavior;
  • RU direct-routing smoke access;
  • bundled LoyalSoldier assets.

Useful Commands

Rebuild and restart:

docker compose build proxy-xray
docker compose up -d --force-recreate

Watch logs:

docker logs -f proxy-xray

Check current public HTTP proxy:

curl -x http://127.0.0.1:8123 https://www.gstatic.com/generate_204 -i

Check SOCKS:

curl -x socks5h://127.0.0.1:1080 https://checkip.amazonaws.com

Credits

This project started from the original samuelhbne/proxy-xray Docker wrapper for Xray-Core, then grew into a subscription supervisor and home LAN gateway with hot standby failover, split DNS, status UI, and deployment tooling.

About

Home LAN Xray gateway with VLESS subscription pools, hot standby failover, split DNS, direct RU routing, diagnostics, and local status UI.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages