#6573·3x-ui

[Feature]: Use Xray routing rules instead of iptables for real IP-limit enforcement behind CDN proxies (e.g. Cloudflare)

Author: mjbe71Created Sep 16, 2026Updated Sep 16, 2026
Labelsenhancement

Is your feature request related to a problem?

Configs served behind a CDN proxy (e.g. Cloudflare) break IP-limit enforcement. With "Real Client IP" set to the Cloudflare preset on the inbound, the panel correctly attributes and displays each client's real IP, and fail2ban's 3x-ipl jail correctly extracts that real IP and tries to ban it. But the default action bans via iptables on the origin server — and packets from that client never actually arrive with that IP as their source, since Cloudflare's edge IP is always the real network-layer source. The ban is created but never matches any traffic, so nothing is actually blocked. One "single user" account can end up shared by hundreds of concurrent real IPs with no enforcement kicking in.

Describe the solution you'd like

Instead of (or in addition to) the iptables action, let IP-limit enforce bans inside Xray's own routing layer, which already sees the correct attributed IP regardless of CDN proxying.

Concretely, we built and tested a working version of this ourselves:

  • A dedicated field routing rule is created per node, tagged distinctly (we used ruleTag "fail2ban-auto-block"), with outboundTag pointing at a blackhole outbound, and source holding the list of currently-banned IPs.
  • This rule is managed through the panel's existing REST API (POST /panel/api/xray/ to read the config, POST /panel/api/xray/update to write it back) — no new endpoints were strictly needed.
  • A custom fail2ban action (actionban/actionunban) calls a small script that adds/removes the banned IP from this rule's source array on each panel (main + each remote node has its own panel/API token).
  • Critical detail: the rule must be re-inserted at the very TOP of routing.rules every time it's updated, not appended at the end. If it sits after other user-defined routing rules (e.g. tunnel rules routing specific inbounds to specific outbounds), those rules match first and the block is silently bypassed for any inbound that has its own routing rule — only inbounds falling through to the default outbound were actually blocked when the rule was appended at the end.
  • If the last banned IP is removed, the rule should be deleted entirely rather than left with an empty source — Xray refuses to start if any field rule has no effective fields at all ("this rule has no effective fields").

Ideally this becomes a built-in IP-limit action option: "enforce via routingrule" alongside the current iptables/fail2ban action, automatically keeping the managed rule pinned at top priority and cleaning it up when empty — per node, since each node has its own independent Xray config.

Alternatives you've considered

  • Nginx/HAProxy reverse proxy in front of Xray with PROXY protocol or X-Forwarded-For, so the origin's real packet source matches the client IP. Works for the attribution problem but adds a whole extra service to deploy and maintain per node, and still needs something to actually act on bans.
  • Banning at Cloudflare's edge via their API (Custom IP Lists + WAF rule), triggered by a fail2ban action. Works, but requires a Cloudflare account/API token per zone and doesn't help anyone not using Cloudflare specifically.
  • Restricting the origin firewall to Cloudflare's published IP ranges only. Necessary as a complementary hardening step either way (to stop someone connecting directly to the origin and bypassing the CDN), but doesn't by itself make IP-limit enforcement work. The routing-rule approach was the only one that worked using only what the panel already exposes, with no extra services and no third-party account.

Concrete use case

We run 1 main panel + 4 remote nodes, all serving configs behind Cloudflare (proxied) using XHTTP inbounds. Before this fix, a single "1 device" client account was effectively usable by an unlimited number of concurrent real users, since IP-limit detected the violation but never actually disconnected anyone. After switching enforcement to a pinned top-priority blackhole routing rule managed via the API, abusive clients are actually cut off — verified end-to-end (tail on the client's live connection dropped immediately after the rule updated), including on nodes that also have unrelated tunnel routing rules defined for other inbounds.

Mockups, screenshots, or examples

Full script: https://gist.github.com/mjbe71/c8f8f33adf9904c2e535b1da2357b887

Which parts of the panel would this affect?

Xray config generation

Are you willing to help?

  • I'd like to implement this feature myself and open a PR.
  • I can help test once a PR is open.

Before submitting

  • I searched existing issues and this feature has not been requested.
  • This feature request is written in English.