A home gateway can make the initial routing decision while a VPS performs an additional IP-based check. When the VPS resolves a destination to an address covered by geoip:cn, it sends the connection back through a reverse tunnel. Xray on the home gateway then connects to the destination through the home Internet connection.

This arrangement handles traffic that the initial routing rules did not identify. It adds a round trip through the VPS to the forwarding path, so destinations already known to require home egress should preferably be routed locally.

The examples use Xray v26.11.9 as their reference version. All hostnames, account labels, UUID placeholders, ports, and certificate paths are illustrative. The public DNS address is a service endpoint, not an address belonging to the deployment. See the Xray release and the official VLESS reverse proxy guide.

Architecture

The home gateway runs sing-box for client routing and a separate Xray instance for the reverse tunnel. The home Xray instance initiates the tunnel, so the home router does not need a public inbound port for this service.

ConnectionInitiatorDestinationAccount or routing tag
Reverse tunnelHome XrayVPS TCP port 8443REVERSE_UUID
Ordinary proxy trafficsing-boxVPS TCP port 8444CLIENT_UUID
Returned trafficVPS XrayEstablished reverse tunnelreturn-to-home
Destination connectionHome XrayTarget servicereturned-traffic to direct

On the VPS, clients[].reverse.tag registers the outbound used to return connections. On the home gateway, settings.reverse.tag assigns an inbound tag to returned traffic so that local routing can select direct. These tags belong to their respective Xray instances and do not need to be identical. See the VLESS inbound and VLESS outbound documentation.

Prepare the deployment

Generate two different UUIDs with xray uuid. Use one for ordinary proxy traffic and the other exclusively for the reverse tunnel.

Replace reverse.example.com with the actual VPS hostname. Install a TLS certificate covering that hostname and make its certificate and private key readable by the Xray service account. Allow inbound TCP traffic on the two configured VPS ports and ensure Xray can load geoip.dat.

The examples use VLESS over TCP with TLS. The business entry and the reverse entry share a certificate but use separate ports and accounts.

Configure Xray on the VPS

The VPS accepts ordinary connections on port 8444 and reverse tunnel connections on port 8443. Ordinary traffic matching geoip:cn is returned to the home gateway. Other traffic exits through the VPS.

{
  "log": {
    "loglevel": "info"
  },
  "dns": {
    "servers": [
      "localhost"
    ]
  },
  "inbounds": [
    {
      "tag": "from-sing-box",
      "listen": "0.0.0.0",
      "port": 8444,
      "protocol": "vless",
      "settings": {
        "decryption": "none",
        "clients": [
          {
            "id": "CLIENT_UUID",
            "email": "sing-box"
          }
        ]
      },
      "streamSettings": {
        "security": "tls",
        "tlsSettings": {
          "certificates": [
            {
              "certificateFile": "/path/to/fullchain.pem",
              "keyFile": "/path/to/privkey.pem"
            }
          ]
        }
      }
    },
    {
      "tag": "reverse-tunnel-in",
      "listen": "0.0.0.0",
      "port": 8443,
      "protocol": "vless",
      "settings": {
        "decryption": "none",
        "clients": [
          {
            "id": "REVERSE_UUID",
            "email": "home-reverse",
            "reverse": {
              "tag": "return-to-home"
            }
          }
        ]
      },
      "streamSettings": {
        "security": "tls",
        "tlsSettings": {
          "certificates": [
            {
              "certificateFile": "/path/to/fullchain.pem",
              "keyFile": "/path/to/privkey.pem"
            }
          ]
        }
      }
    }
  ],
  "outbounds": [
    {
      "tag": "vps-egress",
      "protocol": "freedom"
    }
  ],
  "routing": {
    "domainStrategy": "IPIfNonMatch",
    "rules": [
      {
        "type": "field",
        "ruleTag": "cn-return",
        "inboundTag": [
          "from-sing-box"
        ],
        "ip": [
          "geoip:cn"
        ],
        "outboundTag": "return-to-home"
      },
      {
        "type": "field",
        "ruleTag": "vps-egress-fallback",
        "inboundTag": [
          "from-sing-box"
        ],
        "ip": [
          "0.0.0.0/0",
          "::/0"
        ],
        "outboundTag": "vps-egress"
      }
    ]
  }
}

Routing order and DNS

IPIfNonMatch first evaluates a domain destination without proactively resolving it for IP matching. If no rule matches, Xray resolves the domain and retries the applicable IP rules. Place any desired domain rules before cn-return.

The final rule uses both IPv4 and IPv6 catch-all ranges. A generic network: "tcp,udp" catch-all would match domain requests during the initial pass and prevent the DNS-dependent second pass. If no routing rule matches, Xray uses the first outbound, which is vps-egress in this example.

Different fields within a rule must match together. For example, cn-return requires both the from-sing-box inbound and a matching destination IP. Separate rules are evaluated in order, with the first matching rule selecting the outbound. See Xray routing.

The VPS DNS setting uses the system resolver through localhost. Resolving a domain for routing does not guarantee that the forwarded destination is rewritten as an IP address. The home gateway must therefore have a working DNS path of its own. See Xray DNS.

If an ordinary proxy entry already exists, retain it and add the reverse entry. Update the routing rules to match the existing business inbound tag.

Configure Xray on the home gateway

The home instance opens the reverse tunnel and routes returned connections directly to the destination. It requires no conventional listening inbound for returned traffic.

{
  "log": {
    "loglevel": "info"
  },
  "dns": {
    "servers": [
      "https+local://223.5.5.5/dns-query"
    ]
  },
  "outbounds": [
    {
      "tag": "direct",
      "protocol": "freedom",
      "settings": {
        "finalRules": [
          {
            "action": "block",
            "ip": [
              "geoip:private"
            ]
          },
          {
            "action": "allow",
            "network": "tcp,udp"
          }
        ]
      },
      "streamSettings": {
        "sockopt": {
          "domainStrategy": "ForceIP"
        }
      }
    },
    {
      "tag": "reverse-to-vps",
      "protocol": "vless",
      "settings": {
        "address": "reverse.example.com",
        "port": 8443,
        "id": "REVERSE_UUID",
        "encryption": "none",
        "reverse": {
          "tag": "returned-traffic"
        }
      },
      "streamSettings": {
        "security": "tls",
        "tlsSettings": {
          "serverName": "reverse.example.com"
        }
      }
    }
  ],
  "routing": {
    "rules": [
      {
        "type": "field",
        "inboundTag": [
          "returned-traffic"
        ],
        "outboundTag": "direct"
      }
    ]
  }
}

Resolve real destination addresses

A gateway that uses FakeIP locally may receive synthetic addresses from its system resolver. Those addresses are meaningful only inside the proxy component that maintains the FakeIP mapping. Passing them to a normal direct socket can make the connection fail.

The direct outbound uses sockopt.domainStrategy: "ForceIP" to resolve domain destinations through Xray’s built-in DNS. The example uses the AliDNS DoH IP endpoint, avoiding a separate lookup of the DNS server’s hostname. Resolution failure produces an error instead of falling back to the system resolver.

This only helps when Xray receives a domain destination. If the returned target is already a FakeIP address, ForceIP cannot recover the original domain. Correct the mapping or forwarding behavior earlier in the path. See socket options and Xray DNS.

Explicitly allow reverse traffic to leave

Routing a connection to direct selects the outbound. The Freedom outbound still applies its own finalRules before connecting.

For VLESS reverse traffic, an unmatched destination is blocked by default. The configuration therefore blocks geoip:private first, then explicitly allows other TCP and UDP traffic. Keep this ordering so that the general allow rule does not bypass the private-address restriction. See Freedom finalRules.

Connect sing-box to the business entry

Add the following object to the sing-box outbounds array and route the desired traffic to vps. Use CLIENT_UUID, keeping the reverse account reserved for the home Xray instance.

{
  "type": "vless",
  "tag": "vps",
  "server": "reverse.example.com",
  "server_port": 8444,
  "uuid": "CLIENT_UUID",
  "packet_encoding": "xudp",
  "tls": {
    "enabled": true,
    "server_name": "reverse.example.com"
  }
}

The example enables XUDP encoding for UDP traffic. The existing sing-box DNS, inbound, and routing configuration still determines which requests reach this outbound. See the sing-box VLESS outbound documentation.

Validate the forwarding path

Check the installed version and the configuration actually loaded by the service on both hosts. Adjust the example configuration path to match the installation.

xray version
systemctl show xray -p ExecStart
xray run -test -c /usr/local/etc/xray/config.json

Start the VPS service, then the home service. Follow the logs on both hosts.

journalctl -u xray -f

Test a destination that the client sends to the VPS and that the VPS classifies under geoip:cn.

  1. Confirm that the VPS selects return-to-home for the destination.
  2. Confirm that the home Xray instance receives the returned connection and selects direct.
  3. Confirm that the client receives a successful response.

To verify the actual public egress address, use a controlled public endpoint that records its peer address. Ensure that this test connection matches the return rule. A successful request to an unrelated IP-checking service may simply have used the VPS fallback.

Troubleshooting

TLS certificate hostname mismatch

A certificate for an unrelated hostname usually indicates that the connection reached the wrong TLS endpoint. Check the destination address, port, DNS records, port forwarding, and SNI.

host=reverse.example.com
openssl s_client -connect "$host:8443" -servername "$host" \
  -verify_hostname "$host" -verify_return_error -brief </dev/null

For this ordinary TLS setup, the certificate must cover the configured serverName. See Xray TLS configuration and OpenSSL s_client.

A reverse account receives an ordinary proxy request

The message not allowed to use forward proxy indicates that a user configured for reverse operation submitted an ordinary forwarding request.

Check that sing-box uses CLIENT_UUID on the business port. The home Xray reverse outbound must use REVERSE_UUID on the reverse port and include settings.reverse.tag. If the error occurs only when the home service starts, inspect the configuration that service actually loaded.

Direct connections resolve to FakeIP

Compare the destination received by the home Xray instance with the address selected during dialing. Temporarily enable log.dnsLog if necessary.

If the received destination is a domain but dialing selects a FakeIP, check the ForceIP setting and Xray’s DNS configuration. If the received destination is already a FakeIP, inspect the earlier sing-box mapping and the target submitted to the VPS. See Xray logging.

Freedom blocks a connection after routing selects direct

A taking detour [direct] message followed by target blocked by a finalrule means that routing selected the intended outbound, but Freedom rejected the destination afterward.

Check the loaded finalRules, their order, and the resolved destination address. The example blocks private addresses and explicitly allows the remaining TCP and UDP traffic. Selecting a direct outbound alone does not override this final check.

HTTP/3 over the reverse tunnel

HTTP/3 uses QUIC over UDP between the application and the destination. The reverse tunnel can carry those UDP packets inside its TCP/TLS transport, and the home gateway can send them onward as UDP. The outer tunnel does not need to use HTTP/3 for the application to use HTTP/3.

This preserves UDP forwarding, but it does not preserve all the performance characteristics of a native UDP path. Loss on the outer TCP connection can delay delivery of the encapsulated traffic. The implementation paths are documented in the VLESS inbound source and Mux client source.

Check curl -V for the HTTP3 feature before using an HTTP/3 test. On OpenWrt, the installed curl build may lack that capability. With a compatible build, use a known HTTP/3 endpoint that also matches the return rule.

# Replace this placeholder with a suitable HTTP/3 test endpoint.
url=https://h3.example.com/

curl -V
curl --noproxy '*' --http3-only -sS -o /dev/null \
  -w 'HTTP/%{http_version}\n' --max-time 10 "$url"

This command assumes transparent routing through the same forwarding path. --noproxy '*' disables explicit curl proxy settings. The --http3-only option prevents fallback to another HTTP version, while --http3 permits fallback. See the curl manual.

Alternatively, use Chrome through the same routing path. Enable the Protocol column in the Network panel and check for h3. Correlate the request with the VPS return route and a matching UDP destination on the home gateway. Browser protocol information alone does not establish which egress path was used. See the Chrome DevTools Network reference.

Capture traffic on the home gateway’s actual egress interface when packet-level confirmation is needed.

# Replace eth0 with the actual egress interface.
sudo tcpdump -ni eth0 'udp port 443'

Check for both outgoing packets and replies. Combine the capture with routing logs to distinguish returned traffic from unrelated UDP connections.