There are three places to decide that an application's traffic goes through a proxy. System proxy writes the operating system's proxy setting and trusts each app to read it. TUN mode adds a virtual network adapter, so every app's traffic reaches a proxy client whether it cooperates or not. Per-app rules pick traffic by program, either inside the app itself or as rules in a client. The short version: use an app's own proxy setting when it has one, system proxy for browsers and well-behaved desktop apps, and TUN mode for programs that ignore both. Whichever captures the traffic, rules then decide what uses the proxy, and one common rule quietly sends traffic around it.
The worked example is Clash Verge Rev, a desktop routing client for Windows, macOS and Linux built on the Mihomo core. It supplies no proxies; you add your own HTTP or SOCKS5 proxy as a node. We ran every config below on Mihomo v1.19.32 against local proxies that require a username and password. Clash Verge's TUN and service-mode behaviour comes from the project's documentation, not from our runs, and we mark it.
Where each one catches traffic
An app's own proxy setting. curl's -x, the HTTPS_PROXY variable, a browser's proxy field, a library's proxy argument. Only that app is affected, which makes it the most predictable option and the one most of our integration guides use. Exceptions go in NO_PROXY. The limit is obvious: a program with no proxy setting cannot use it.
System proxy. The operating system holds one proxy setting, and apps that read it send their traffic there. Clash Verge's system proxy switch points that setting at its local mixed port, which accepts HTTP and SOCKS5 on one port. The project's documentation is blunt about the limits: following the setting is a convention that not every program honours, and it carries no UDP.
TUN mode. A TUN device is a virtual network interface whose packets go to a program instead of a wire. With TUN mode on, the operating system routes traffic into the proxy client, which then sees every TCP and UDP connection from every app, including ones that never heard of proxies. Per the documentation, Clash Verge's TUN mode uses the gVisor stack by default, needs its cores (verge-mihomo and verge-mihomo-alpha) allowed through the firewall, and has a service mode, installed once by an administrator, so TUN can start without an administrator prompt.
Compared side by side
| App's own setting | System proxy | TUN mode | |
|---|---|---|---|
| What it catches | That one app | Apps that read the OS setting | Every app on the machine |
| Apps with no proxy support | Not possible | Missed | Caught |
| UDP | Only if the app speaks SOCKS5 UDP itself | Not carried | Captured; carried only by a node that relays UDP |
| Extra setup | None | A switch in the client | Firewall allowance for the core, plus administrator rights or service mode |
| Choosing per app | Built in | PROCESS-NAME rules in the client | PROCESS-NAME rules in the client |
| Rules by domain | NO_PROXY or a PAC file | Full client rules | Full client rules |
System proxy, TUN mode and an app pointed at the client's port all feed the same rules. That is the point of a rule-based client: capture decides what the client sees, rules decide what the proxy sees.
Per-app rules, and what "per app" means
"Route this one program through the proxy" can be done at three levels:
- In the program. Its own setting, if it has one.
- Point the program at the client. Set its proxy to Clash's mixed port,
127.0.0.1and the port from Clash Verge's settings, and leave system proxy off. Only that program reaches the client, and rules still apply. - Match it by name. With system proxy or TUN feeding the client, a
PROCESS-NAMErule picks the program's connections.
We tested the third on Linux. With PROCESS-NAME,curl,PROXY above MATCH,DIRECT, the core logged 127.0.0.1:57824(curl, uid=1000) --> other-site.com:28100 match ProcessName(curl) and sent it to the node, while Python and wget went direct. Two details from that run:
- Use the name the log prints. Python appeared as
python3.12, notpython3. Clash Verge's documentation gives Windows examples with the.exesuffix, such asTelegram.exe. - Treat it as routing, not as a guarantee. When we ran curl 80 times back to back, the process lookup failed on 12 of the connections. The debug log read
find process error ... no such file or directory, and those connections fell through toMATCH,DIRECT. If a target must always use the proxy, give it a domain rule as well, as the example below does.
Outside Clash, the same job has older tools: Proxifier writes rules per app on Windows and macOS, ProxyChains wraps one Linux command, and Redsocks redirects a whole Linux machine at the firewall.
A worked example in Clash Verge
The goal: send one scraping tool and one target site through a proxy, leave everything else on your own connection. Two nodes, the HTTP and SOCKS5 ports of one IP, sit in a group called PROXY, and the rules read:
rules:
- PROCESS-NAME,curl,PROXY
- DOMAIN-SUFFIX,target-site.com,PROXY
- IP-CIDR,10.0.0.0/8,DIRECT,no-resolve
- MATCH,DIRECT
Run Clash Verge in Rule mode with system proxy on. In our run, Python to target-site.com went through the node because of the domain, Python to anything else went direct, and curl went through the node by name, apart from the lookups that missed:
[TCP] 127.0.0.1:56668(python3.12, uid=1000) --> target-site.com:28100 match DomainSuffix(target-site.com) using PROXY[hive-http]
[TCP] 127.0.0.1:56652(python3.12, uid=1000) --> other-site.com:28100 match Match using DIRECT
Domains matched this way are handed to the node by name, so the proxy resolves them; our local resolver received no query for target-site.com. The full profile, node by node, is in the Clash Verge setup guide.
The IP-CIDR rule that takes traffic off the proxy
Rule lists often start with "private ranges go direct". Written without no-resolve, that rule changes what happens to every domain that reaches it. We put it first:
rules:
- IP-CIDR,10.0.0.0/8,DIRECT
- DOMAIN-SUFFIX,target-site.com,PROXY
- MATCH,DIRECT
and had our local resolver answer target-site.com with 10.1.2.3, the kind of answer a VPN's split DNS or an office resolver can give. To test an IP rule, the core needs an IP, so it looked the name up locally, matched the range and went direct:
[TCP] dial DIRECT (match IPCIDR/10.0.0.0/8) 127.0.0.1:43226 --> target-site.com:28100 error: dial tcp 10.1.2.3:28100: i/o timeout
The client got a 502 after the timeout. The proxy never saw the request, although a rule naming the domain sat one line lower. Adding ,no-resolve to the IP rule fixed it: the domain fell through to DOMAIN-SUFFIX, went to the node by name, and the proxy resolved it to the right address. GEOIP,LAN,DIRECT behaved the same way, logging match GeoIP/lan.
There is a quieter cost even when the answer is outside the range. With IP-CIDR,192.168.0.0/16,DIRECT first, the request did reach the proxy, but our local resolver logged a query for target-site.com first. With no-resolve, it logged none. An IP rule above your domain rules sends every name that reaches it through your own DNS: a DNS leak on a route you believed went through the proxy.
To check your own setup, search the core's log for IPCIDR or GeoIP on a line whose target is a hostname. Each one is a domain resolved locally and routed by its address. Then either move domain rules above IP rules or add no-resolve to the IP rules.
UDP in TUN mode: the rule you wrote may not apply
TUN mode hands UDP to the client, and rules match it like TCP. What carries it is another matter. An HTTP proxy cannot relay UDP, and Mihomo treats a SOCKS5 node as UDP-capable only with udp: true. In our test, UDP to a domain whose rule pointed at either kind of node skipped that rule and fell through to MATCH,DIRECT; the log read match Match using DIRECT and the reply came back over our own connection. With udp: true on the SOCKS5 node, the same packets went through the node, and the core looked the name up locally and passed the node an address.
Two ways to keep it predictable:
- Reject UDP for proxied domains.
AND,((NETWORK,UDP),(DOMAIN-SUFFIX,target-site.com)),REJECTabove the proxy rule loggedusing REJECTfor UDP while TCP to the same domain still used the node. - Turn on
udp: trueonly for a provider that documents UDP relay over SOCKS5.
Which one to use
| You want to | Use |
|---|---|
| Proxy a script, CLI tool or library | Its own proxy setting, as in the curl guide |
| Proxy browsers and desktop apps that follow the OS | System proxy, with rules in a client |
| Proxy a program that ignores proxy settings | TUN mode, or Redsocks on a Linux server |
| Proxy one Linux command | ProxyChains |
| Choose per program on Windows or macOS without TUN | Proxifier, or a client's PROCESS-NAME rules |
| Protect the whole device on an untrusted network | A VPN; see proxy vs VPN |
Whichever you pick, finish with the same test: send one request you expect to go through the proxy and one you expect to go direct, and compare the exit IPs.
With ProxyHive
A datacenter IP is a fixed exit for this kind of setup: HTTP, HTTPS and SOCKS5 with username and password authentication, each IP its own endpoint and its own node. The specs say nothing about UDP relay, so plan on TCP and keep the UDP reject rule. Datacenter IPs start at $1.90/IP (dedicated) on datacenter pricing.