telemt/docs/Setup_examples/XRAY_DOUBLE_HOP.en.md

8.0 KiB

Concept

  • Server A (e.g., RU):
    Entry point, accepts Telegram proxy user traffic via HAProxy (port 443\tcp)
    and sends it through the local Xray client (port 10443\tcp) to Server B.
    Public port for HAProxy clients — 443\tcp
  • Server B (e.g., NL):
    Exit point, runs the Xray server (to terminate the tunnel entry point) and telemt.
    The server must have unrestricted access to Telegram Data Centers.
    Public port for VLESS/REALITY (incoming) — 443\tcp
    Internal telemt port (where decrypted Xray traffic ends up) — 8443\tcp

The tunnel works over the VLESS-XTLS-Reality (or VLESS/xhttp/reality) protocol. The original client IP address is preserved thanks to the PROXYv2 protocol, which HAProxy prepends before passing to Xray, and which transparently reaches telemt.


Step 1. Setup Xray Tunnel (A <-> B)

You must install Xray-core (version 1.8.4 or newer recommended) on both servers. Official installation script (run on both servers):

bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install

Key and Parameter Generation (Run Once)

For configuration, you need a unique UUID and Xray Reality keys. Run on any server with Xray installed:

  1. Client UUID:
xray uuid
# Save the output (e.g.: 12345678-abcd-1234-abcd-1234567890ab) — this is <XRAY_UUID>
  1. X25519 Keypair (Private & Public) for Reality:
xray x25519
# Save the Private key (<SERVER_B_PRIVATE_KEY>) and Public key (<SERVER_B_PUBLIC_KEY>)
  1. Short ID (Reality identifier):
openssl rand -hex 16
# Save the output (e.g.: 0123456789abcdef0123456789abcdef) — this is <SHORT_ID>

Configuration for Server B (EU):

Create or edit the file /usr/local/etc/xray/config.json. This Xray instance will listen on the public 443 port and proxy valid Reality traffic, while routing "disguised" traffic (e.g., direct web browser scans) to yahoo.com.

nano /usr/local/etc/xray/config.json

File content:

{
  "log": {
    "loglevel": "error",
    "access": "none"
  },
  "inbounds": [
    {
      "port": 443,
      "protocol": "vless",
      "settings": {
        "clients": [
          {
            "id": "<XRAY_UUID>"
          }
        ],
        "decryption": "none"
      },
      "streamSettings": {
        "network": "tcp",
        "security": "reality",
        "realitySettings": {
          "dest": "yahoo.com:443",
          "serverNames": [
            "yahoo.com"
          ],
          "privateKey": "<SERVER_B_PRIVATE_KEY>",
          "shortIds": [
            "<SHORT_ID>"
          ]
        }
      },
      "sockopt": {
        "tcpFastOpen": true,
        "tcpNoDelay": true,
        "tcpKeepAliveIdle": 60,
        "tcpKeepAliveInterval": 15
      }
    }
  ],
  "outbounds": [
    {
      "protocol": "freedom",
      "tag": "direct",
      "settings": {
        "destination": "127.0.0.1:8443"
      }
    }
  ],
  "routing": {
    "rules": [
      {
        "type": "field",
        "inboundTag": ["all-in"],
        "outboundTag": "direct"
      }
    ]
  }
}

Open the firewall port (if enabled):

sudo ufw allow 443/tcp

Restart and setup Xray to run at boot:

sudo systemctl restart xray
sudo systemctl enable xray

Configuration for Server A (RU):

Similarly, edit /usr/local/etc/xray/config.json. Here Xray acts as a local client: it listens on 10443\tcp (for traffic from HAProxy), encapsulates it via Reality to Server B, and instructs Server B to deliver it to its local 127.0.0.1:8443 port (where telemt will listen).

nano /usr/local/etc/xray/config.json

File content:

{
  "log": {
    "loglevel": "error",
    "access": "none"
  },
  "inbounds": [
    {
      "port": 10443,
      "listen": "127.0.0.1",
      "protocol": "dokodemo-door",
      "settings": {
        "address": "127.0.0.1",
        "port": 8443,
        "network": "tcp"
      }
    }
  ],
  "outbounds": [
    {
      "protocol": "vless",
      "settings": {
        "vnext": [
          {
            "address": "<PUBLIC_IP_SERVER_B>",
            "port": 443,
            "users": [
              {
                "id": "<XRAY_UUID>",
                "encryption": "none"
              }
            ]
          }
        ]
      },
      "streamSettings": {
        "network": "tcp",
        "security": "reality",
        "realitySettings": {
          "serverName": "yahoo.com",
          "publicKey": "<SERVER_B_PUBLIC_KEY>",
          "shortId": "<SHORT_ID>",
          "spiderX": "",
          "fingerprint": "chrome"
        },
        "sockopt": {
          "tcpFastOpen": true,
          "tcpNoDelay": true,
          "tcpKeepAliveIdle": 60,
          "tcpKeepAliveInterval": 15
        }
      },
      "mux": {
        "enabled": true,
        "concurrency": 256,
        "xudpConcurrency": 16,
        "xudpProxyUDP443": "reject"
      }
    }
  ]
}

Replace <PUBLIC_IP_SERVER_B> with the public IP address of Server B.

Restart and setup Xray to run at boot:

sudo systemctl restart xray
sudo systemctl enable xray

Step 2. Setup HAProxy on Server A (RU)

HAProxy will run on the public port 443 of Server A, receive incoming connections from Telegram users, attach a PROXYv2 header (to forward the true user IP) and send the stream to the local Xray client. Docker installation is like the AmneziaWG instructions.

[!WARNING] If you don't run as root or have issues with binding to port 443 (cannot bind socket), allow unprivileged usage:

echo "net.ipv4.ip_unprivileged_port_start = 0" | sudo tee -a /etc/sysctl.conf && sudo sysctl -p

Create HAProxy Directory:

mkdir -p /opt/docker-compose/haproxy && cd $_

Create docker-compose.yaml

services:
  haproxy:
    image: haproxy:latest
    container_name: haproxy
    restart: unless-stopped
    # user: "root"
    network_mode: "host"
    volumes:
      - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
    logging:
      driver: "json-file"
      options:
        max-size: "1m"
        max-file: "1"

Create HAProxy Config haproxy.cfg

global
    log stdout format raw local0
    maxconn 10000

defaults
    log     global
    mode    tcp
    option  tcplog
    option  clitcpka
    option  srvtcpka
    timeout connect 5s
    timeout client  2h
    timeout server  2h
    timeout check   5s

frontend tcp_in_443
    bind *:443
    maxconn 8000
    option tcp-smart-accept
    default_backend telemt_nodes

backend telemt_nodes
    option tcp-smart-connect
    server telemt_core 127.0.0.1:10443 check inter 5s rise 2 fall 3 maxconn 250000 send-proxy-v2

[!WARNING] The configuration file must end with an empty newline, otherwise HAProxy fails to start!

Start the HAProxy Container

Allow port 443\tcp in your firewall and launch Docker compose:

sudo ufw allow 443/tcp
docker compose up -d

Step 3. Install telemt on Server B (EU)

telemt installation is heavily covered in the Quick Start Guide. By contrast to standard setups, telemt must listen strictly locally (since Xray occupies the public 443 interface) and must expect PROXYv2 packets.

Edit the configuration file (config.toml) on Server B accordingly:

[server]
port = 8443
listen_addr_ipv4 = "127.0.0.1"
proxy_protocol = true

[general.links]
show = "*"
public_host = "<FQDN_OR_IP_SERVER_A>"
public_port = 443
  • Address 127.0.0.1 and port = 8443 instructs the core proxy router to process connections unpacked locally via Xray-server.
  • proxy_protocol = true commands telemt to parse the injected PROXY header (from Server A's HAProxy) and log genuine end-user IPs.
  • Under public_host, place Server A's public IP address or FQDN to ensure working links are generated for Telegram users.

Restart telemt. Your server is now robust against DPI scanners, passing traffic optimally.