A safe, update-resistant Linux traffic-control sidecar for 3x-ui / Xray, with an adapter architecture for Marzban and other Xray panels.
BW Guard applies strict per-client/IP/inbound bandwidth queues without patching the panel binary, database, frontend, or generated Xray configuration. That is the central design decision: panel updates can change UI/backend internals without overwriting the shaper.
v8 is a ground-up Go refactor of the original Bash implementation. It defaults to dry-run and refuses root-qdisc takeover until an administrator explicitly enables it.
- Auto-discovers Xray inbounds and their ports.
- First-class 3x-ui adapter using the panel API when configured, with runtime Xray fallback.
- Per-client IP attribution using aggregate 3x-ui data when available, with a documented online-client/IP API fallback.
- Generic Xray mode, plus an experimental Marzban adapter.
- Per-inbound, per-client and per-IP policies with precedence and inheritance.
- Independent download/upload rates, optional HTB peak ceilings/token-bucket sizing, priorities, FQ-CoDel leaves and optional strict quantum.
- Optional hard
max_connectionsadmission control using a private nftables table. - IPv4 + IPv6 traffic filters.
- Fast flow scans with slower cached panel/config discovery to keep API load low.
- Automatic system-listener protection and Xray outbound discovery. Queue filters are scoped to client IP and the selected inbound port, so panel/SSH/outbound traffic does not enter client classes.
- Automatic physical link-rate detection from sysfs/
ethtoolfor safe HTB parent sizing, with an explicit override when needed. - Ownership-aware cleanup; does not flush nftables/firewall tables or blindly remove foreign
tcobjects. - Embedded responsive web UI + JSON HTTP API + Doctor diagnostics.
- Safe migration helper for the legacy
/etc/3xui-bw-guard/3xui-bw-guard.confformat. - Static Go binary; no Python/Node runtime required.
3x-ui API ───────┐
Marzban API ─────┼─> discovery adapters ─┐
Xray config ─────┘ │
ss/ip listeners ─────────────────────────┼─> reconciler ─> policy resolver
│ │
│ ├─> tc/HTB + FQ-CoDel
│ └─> nft connection guard
└─> status/API/UI
The data plane is Linux-native. BW Guard does not modify 3x-ui files, React assets, SQLite/PostgreSQL state, or the Xray configuration generated by a panel.
- Linux with
iproute2(ip,tc,ss) - Kernel support for IFB, HTB, FQ-CoDel, flower classification and mirred actions
nftablesfor hard connection caps (speed shaping works without it)kmod/modprobewhere the networking classifiers are modulesethtoolfor NIC-speed fallback detection and offload control (sysfs is tried first forautolink rates)
Installer support is included for apt, dnf/yum, pacman and zypper systems, plus systemd and OpenRC service layouts.
git clone https://github.com/nimix3/XUI-Bandwidth-Guard
cd xui-bw-guard
make test vet build
sudo ./scripts/install.sh ./bin/xui-bw-guardAfter release assets are published, the convenience installer is:
curl -fsSL https://raw.githubusercontent.com/nimix3/XUI-Bandwidth-Guard/main/scripts/install-release.sh | sudo bashThe installer starts the daemon in dry-run. Open the UI safely through SSH:
ssh -L 6178:127.0.0.1:6178 root@your-serverThen browse to http://127.0.0.1:6178.
Run:
sudo xui-bw-guard doctor
sudo tc qdisc show dev YOUR_INTERFACE rootReview the discovered inbounds, protected ports and current root qdisc in the UI. Only then:
- Set
engine.takeover_qdisc=trueif this host's root qdisc may safely be replaced by BW Guard. - Keep
dry_run=true, save and run one reconcile/Doctor pass. - Set
engine.dry_run=falseto enforce.
If you use another QoS agent, hosting panel queue system, CNI, custom tc, DDoS appliance agent or provider qdisc on the same root interface, do not enable takeover until you have designed coexistence.
BW Guard's recommended 3x-ui integration is companion mode, not a source-code patch. Configure the panel URL and either a panel API token or a username + password file. Example:
sudo install -m 0600 /dev/null /etc/xui-bw-guard/3xui.password
sudo sh -c 'printf "%s" "YOUR_PASSWORD" > /etc/xui-bw-guard/3xui.password'Then set in config.json:
"panel": {
"base_url": "https://127.0.0.1:2053/your-web-base-path",
"username": "admin",
"password_file": "/etc/xui-bw-guard/3xui.password",
"timeout": "5s"
}You can also use api_token_file and omit password credentials. Secrets are read from files and are not stored in the BW Guard JSON config.
See docs/3XUI_INTEGRATION.md. Upgrading from the original Bash release is covered in docs/MIGRATION_V7.md.
Global default:
"default": {
"download": "4mbit",
"upload": "4mbit",
"burst_download": "8mbit",
"burst_upload": "8mbit",
"burst_time_ms": 120,
"priority": 4,
"max_connections": 30
}Client override; omitted fields inherit the less-specific policy:
{
"name": "vip-alice",
"match": {"client": "alice@example.com"},
"limits": {
"download": "30mbit",
"upload": "15mbit",
"priority": 2,
"max_connections": 80
}
}Explicit max_connections: 0 disables the connection ceiling for that profile. enabled:false exempts a matching client/IP/inbound from shaping.
Policy specificity is additive: IP +100, client +80, inbound +40, global 0. Matching policies layer from broader to more specific, so a rule matching both client and inbound overrides a client-only or inbound-only rule; equal-specificity ties use configuration order. Omitted fields inherit from the broader policy.
auto: manage all discovered enabled Xray inbounds exceptexclude_inbounds.denylist: same behavior, intended for explicit operator-managed exclusions.allowlist: only identifiers listed ininclude_inboundsare managed.
Identifiers may be an inbound ID, tag, remark or port string.
xui-bw-guard run daemon + web UI
xui-bw-guard doctor prerequisites/safety checks
xui-bw-guard status read daemon status (no shaping side effects)
xui-bw-guard reconcile request immediate reconcile
xui-bw-guard cleanup remove only BW Guard-owned kernel objects
xui-bw-guard configtest validate config
xui-bw-guard migrate-legacy convert the old Bash config to safe v8 config
xui-bw-guard version
Set BWGUARD_DEBUG=1 on the daemon to log the kernel commands generated by reconciliation.
Because BW Guard is a sidecar, updating 3x-ui does not overwrite BW Guard. After a panel update, run doctor and inspect the UI. If a 3x-ui API endpoint changes, local Xray discovery remains the fallback for inbound/outbound discovery.
BW Guard itself can be replaced atomically:
sudo systemctl stop xui-bw-guard
sudo xui-bw-guard cleanup
sudo install -m 0755 ./xui-bw-guard /usr/local/sbin/xui-bw-guard
sudo systemctl start xui-bw-guardTraffic classification is currently based on client IP + inbound port. This is robust and panel-independent, but if multiple Xray identities share the same public NAT IP on the same inbound, the Linux network layer cannot tell their encrypted streams apart. They will share the IP-level policy identity. True same-IP/per-Xray-user isolation requires a deeper Xray dataplane mark/hook; see the roadmap.
Connection caps reject new TCP connections after the observed IP exceeds its threshold. Existing TCP sessions remain alive; UDP is rate-shaped but is not rejected by the connection guard. Because admission is reconciled from observed sockets, a burst of new connections can briefly overshoot the configured number between scans; kernel-native exact connlimit is a planned backend improvement.
For the tightest hard rate ceiling, leave burst_download / burst_upload empty (or equal to the base rate); a higher burst value is an HTB peak ceiling and can intentionally allow borrowing above the base rate. Full MikroTik-style average-window burst/threshold semantics are listed on the roadmap.
disable_offloads=true can improve strictness on some NIC/virtual NIC combinations but costs CPU and may reduce maximum throughput. Leave it off unless measurements show GRO/GSO/TSO is affecting queue accuracy. BW Guard records the original GRO/GSO/TSO state and restores it during ownership-aware cleanup/service stop.
make fmt
make test
make vet
make buildSee docs/ARCHITECTURE.md, docs/SECURITY.md, docs/TROUBLESHOOTING.md, and docs/ROADMAP.md.