Accounts & delivery
Routing & blocklist
A scope pill at the top of Routing switches between the fleet-wide default and a specific node. An amber dot on a node pill means that node already carries an override. Global saves write the panel setting and fan out to every node; per-node saves store the override locally and then explicitly sync to that node’s agent.
Overrides are additive in spirit, not always in code
What it does
Rejects outbound traffic to specific TCP/UDP ports for tunnelled users — the standard way to keep torrenting, spam relaying and IRC abuse off your IP ranges.
How it is enforced
- Lists are parsed with range support (
6881-6889, 1–65535) and stored infirewall.blocked_ports_tcp/firewall.blocked_ports_udp. - A dedicated iptables chain
GRVPN_PORTBLOCKis created and flushed on each apply, hooked intoFORWARDandOUTPUT. - Rules
REJECTwithtcp-resetfor TCP andicmp-port-unreachablefor UDP, added in chunks of 15 ports because of the iptables multiport limit.
How to use
- 1Routing → Blocked ports, choose the scope.
- 2Either click a quick preset — BitTorrent, SMTP (spam), IRC, Napster/P2P — which merges its port list into the current sets, or type ports and ranges manually.
- 3Save. A global save also pushes the policy to every node.
GET /api/routing/ports
PUT /api/routing/ports {tcp[], udp[]} or {tcp_raw, udp_raw}What it does
Sets the resolvers the VPS itself uses, which is also what tunnelled clients get when they do not carry their own resolver.
| Preset | Servers |
|---|---|
| cloudflare | 1.1.1.1 / 1.0.0.1 |
| 8.8.8.8 / 8.8.4.4 | |
| quad9 | 9.9.9.9 / 149.112.112.112 |
| adguard | 94.140.14.14 / 94.140.15.15 |
The UI also offers an OpenDNS chip; server-side presets are the four above.
- Applying DNS writes
/etc/systemd/resolved.conf.d/grvpn.confand restartssystemd-resolved, falling back to writing/etc/resolv.confdirectly when the stub resolver is not in use. - Block DoH reuses the blocklist writer to null-route well-known DoH endpoints (
cloudflare-dns.com,dns.google,dns.quad9.net,doh.opendns.com,mozilla.cloudflare-dns.com,chrome.cloudflare-dns.com,dns.nextdns.io) via/etc/hosts.
GET /api/routing/dns
PUT /api/routing/dns {primary, secondary, block_doh, preset}An outbound is a named egress path an account can be pinned to instead of the node’s own IP.
| Kind | What it is |
|---|---|
| direct | The implicit default: the server’s native IP. |
| warp | Cloudflare WARP over WireGuard. Exactly one profile per node. |
| xray_custom | A pasted vless://, vmess://, trojan:// (or ss://) URI parsed into an outbound config. |
How to set one up
- 1Routing → Outbounds.
- 2Select a node in the scope picker first if you are installing WARP — WARP is node-bound. Leave it on Global default for a custom Xray outbound every node can reference.
- 3Click Cloudflare WARP → Install (runs the wgcf-based registration on the selected node), or Custom Xray and paste the URI with a name.
- 4Under Routing policy set the default outbound for the scope, and optionally override per protocol (vmess / vless / trojan / ssh).
- 5Pin individual accounts from Per-user assignments or from the account detail page’s Settings tab. A pinned account overrides the policy default.
- 6To rotate a WARP egress IP, use New IP on the existing WARP row — it re-registers the same WireGuard identity for a fresh Cloudflare address.
GET /api/outbounds secrets scrubbed from config
POST /api/outbounds {name, kind, config?, uri?}
PATCH /api/outbounds/{id} name / config
DELETE /api/outbounds/{id}
POST /api/outbounds/apply push config to the fleet
GET /api/outbounds/policy
PATCH /api/outbounds/policy {default, byProtocol:{vmess,vless,trojan,ssh}}
POST /api/outbounds/{id}/install-warp node comes from config.nodeId
PATCH /api/accounts/{id}/outbound pin one account (null = policy default)What the node does with them
- The node builds
outbounds[]withdirect(freedom) andblocked(blackhole) first, then one entry per policy outbound taggedob-<id>. - WARP kinds become a real Xray
wireguardoutbound, parsed from the stored wg-quick profile or explicit key/endpoint fields. Cloudflare’s reserved 3-byte field is derived from the wgcf client id when not supplied. - If a node has no usable WARP profile yet, the tag silently falls back to
directwith a warning rather than breaking the user’s internet. - Speed-capped accounts get cloned, mark-tagged outbounds plus routing rules, inserted after API and blocking rules so blocking always wins.
- ZIVPN has no per-user routing: the plane’s chosen outbound becomes the whole node’s ZIVPN egress.
Deletion cleanup is best-effort
- One WARP identity per node; adding a second to the same node is blocked in the UI. Use New IP instead.
- The SSH per-protocol outbound policy is stored and visible but not enforced at the OS level — it does not change SSH egress IP.
What it does
Null-routes domains at the hosts/DNS level on the panel host and on nodes, affecting both Xray’s own resolution and any tunnelled client using the VPS resolver. It applies to SSH and Xray traffic alike.
Mechanism
- Domains live in
blocklist.domains(global) andblocklist.node.<node_id>(per-node extra list, additive). - A managed block in
/etc/hostsbetween# BEGIN GRVPN BLOCKLISTand# END GRVPN BLOCKLISTis rewritten with0.0.0.0 domain,0.0.0.0 www.domainand:: domain. - Input is sanitised: lowercased, leading dots stripped, must contain a dot, only
a-z 0-9 . -allowed, de-duplicated.
How to use
- 1Open
Blocklistand pick the scope: all nodes (global) or one node chip. - 2Paste one or many domains (space or comma separated; scheme and paths are stripped automatically) and press Block.
- 3Or click a quick preset — Adult 18+, Torrents, Ads/trackers, Social — to bulk-add a curated list to the current scope.
- 4Remove single domains with the trash icon, or filter the list with the search box.
With a node scope active the UI shows “+N inherited from global”, because node domains stack on top of the global list.
GET /api/blocklist {domains[]}
PUT /api/blocklist full replace
POST /api/blocklist append {domain?} or {domains[]}
DELETE /api/blocklist/{domain}
GET /api/blocklist/node/{node_id} {nodeId, domains, global}
PUT /api/blocklist/node/{node_id} replace that node's extra listHosts-level blocking is bypassable
