Clash Gateway — Transparent Proxy Guide

Qubes Forum 2026-05-15 [Source]

Intro

This guide explains how to set up a transparent proxy gateway on Qubes OS using qubes-clash-gateway, powered by mihomo (formerly Clash Meta).

Unlike per-AppVM proxy configurations, this approach turns a single NetVM into a proxy gateway. All downstream AppVMs get proxied automatically with zero configuration — just set the NetVM and go.

What it does:

How it works:

AppVM → vif* interface → nftables redirect → mihomo
                                           ├→ DNS :53 → :1053 (fake-ip)
                                           ├→ TCP  → :7892 (redir-port)
                                           └→ UDP  → :7893 (tproxy-port)

mihomo routing:
  ├→ CN domains/IPs → direct
  └→ Foreign → proxy nodes → internet

Prerequisites

Setup

NetVM Creation

Create a dedicated NetVM to serve as the proxy gateway:

  1. In Qubes Manager, create a new qube:

    • Name: sys-proxy (or whatever you prefer)
    • Type: Standalone (recommended) or AppVM based on Fedora/Debian template
    • Networking: sys-firewall (or your upstream NetVM)
    • Check: “Provides network to other qubes” ← Important
  2. In qube settings:

    • Memory: 512 MB minimum (800 MB recommended for Web UI)
    • Services: Add qubes-firewall
  3. Start the qube and open a terminal

Installation

[[SSH]] into the NetVM (or use Qubes terminal):

# Clone the project
git clone https://github.com/iasds/qubes-clash-gateway.git
cd qubes-clash-gateway

# One-click install
sudo bash setup.sh

The installer will:

After installation, clashctl is ready to use.

Subscription Configuration

Add your proxy subscription:

# Add subscription URL
clashctl /sub add <your-subscription-url>

# Update subscription to fetch nodes
clashctl /sub update

# Select rule-based routing (recommended)
clashctl /mode rule

Assign AppVMs

In dom0, set any AppVM to use the proxy gateway:

# Check current NetVM
qvm-prefs <appvm-name> netvm

# Set to proxy gateway
qvm-prefs <appvm-name> netvm sys-proxy

That’s it. The AppVM now routes all traffic through the proxy — no configuration needed inside the AppVM.

Usage

Terminal Commands

clashctl /status              # Show status, traffic, node count
clashctl /mode rule           # Rule-based routing (CN direct, foreign proxy)
clashctl /mode global         # Global proxy (all traffic through proxy)
clashctl /mode direct         # Direct connection (bypass proxy)

clashctl /sub add <url>       # Add subscription
clashctl /sub update          # Update all subscriptions
clashctl /sub list            # List subscriptions

clashctl /node                # List all nodes
clashctl /node <name>         # Select specific node

clashctl /test                # Speed test all nodes
clashctl /test <name>         # Speed test single node

clashctl /dns fake-ip         # Switch DNS mode (fake-ip recommended)
clashctl /restart             # Restart mihomo

clashctl /web                 # Start Web UI (default port 9091)

Aliases: /s=/status, /m=/mode, /n=/node, /t=/test

Web UI

clashctl /web                 # Start with auto-generated token
clashctl /web --secret <pass> # Start with custom token

Open http://<netvm-ip>:9091 in any AppVM browser, enter the token.

The Web UI provides:

Verify Connectivity

# On the AppVM
curl -s https://api.ipify.org          # Should show proxy server IP
curl -s https://www.baidu.com          # CN sites should work (direct)
curl -s https://www.google.com         # Foreign sites should work (proxy)

Architecture

┌─────────────────────────────────────────────────────────┐
│                     AppVM (zero config)                 │
│   Browser, Telegram, etc. → default gateway = sys-proxy │
└─────────────────────┬───────────────────────────────────┘
                      │ vif* interface
┌─────────────────────────────────────────────────────────┐
│  sys-proxy (NetVM)                                      │
│  ┌───────────────────────────────────────────────────┐  │
│  │ nftables (qcg_proxy table)                        │  │
│  │   DNS :53    → redirect to :1053                  │  │
│  │   TCP *:port → redirect to :7892 (redir-port)    │  │
│  │   UDP *:port → redirect to :7893 (tproxy-port)   │  │
│  └─────────────────────┬─────────────────────────────┘  │
│                        ▼                                │
│  ┌───────────────────────────────────────────────────┐  │
│  │ mihomo                                            │  │
│  │   DNS server (fake-ip mode, :1053)                │  │
│  │   Redir server (TCP transparent proxy, :7892)     │  │
│  │   Tproxy server (UDP transparent proxy, :7893)    │  │
│  │                                                   │  │
│  │   Routing rules:                                  │  │
│  │     CN domains/IPs → direct                       │  │
│  │     Foreign → proxy nodes → internet              │  │
│  └───────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘

Persistence

Qubes AppVMs lose /etc/ on reboot. These paths persist on /rw/config/:

PathContent
/rw/config/clash/config.yamlmihomo configuration
/rw/config/clash/clashctl-preferences.jsonclashctl preferences
/rw/config/clash/clashctl-subscriptions.jsonSubscription URLs
/rw/config/clash/nftables-proxy.shnftables rules (auto-loaded)
/rw/config/qubes-firewall-user-scriptAuto-reload on VM connect
/rw/config/rc.localBoot-time startup script

Multiple NetVMs

You can run multiple proxy gateways for different purposes:

# Create separate NetVMs
sys-proxy-cn    # China-optimized nodes
sys-proxy-us    # US nodes
sys-proxy-uk    # UK nodes

# Assign AppVMs to different gateways
qvm-prefs work-appvm netvm sys-proxy-us
qvm-prefs personal-appvm netvm sys-proxy-cn

Each NetVM has independent subscriptions, nodes, and routing rules.

Troubleshooting

AppVM can’t connect

  1. Check if mihomo is running:

    ssh user@sys-proxy "systemctl status mihomo"
    
  2. Check nftables rules:

    ssh user@sys-proxy "sudo nft list set inet qcg_proxy vif_interfaces"
    

    Should list all connected VM interfaces (e.g., vif8.0, vif11.0).

  3. If a new VM can’t connect, reload nftables:

    ssh user@sys-proxy "sudo /rw/config/clash/nftables-proxy.sh"
    

DNS not resolving

  1. Check DNS mode:

    clashctl /status
    

    Should show fake-ip mode.

  2. Flush DNS cache:

    clashctl /dns flush
    

Slow speed / high latency

  1. Speed test nodes:

    clashctl /test
    
  2. Switch to a faster node:

    clashctl /node <faster-node-name>
    
  3. Check exit IP:

    curl -s https://api.ipify.org
    

Web UI not accessible

  1. Check if Web UI is running:

    clashctl /web
    
  2. The token is printed when starting. If lost, restart:

    clashctl /web --secret <your-password>
    

Advanced: Killswitch

To block non-proxy traffic (prevent leaks if mihomo fails):

# Add to /rw/config/qubes-firewall-user-script
nft add rule ip qubes forward iifname "vif*" oifname != "vif*" drop

⚠️ Warning: This blocks all direct traffic. If mihomo is down, AppVMs lose connectivity entirely.

Advanced: Custom Rules

Add custom routing rules in /rw/config/clash/config.yaml:

rules:
  # Direct CN traffic
  - DOMAIN-SUFFIX,baidu.com,DIRECT
  - DOMAIN-SUFFIX,qq.com,DIRECT
  - GEOIP,CN,DIRECT

  # Proxy specific sites
  - DOMAIN-SUFFIX,google.com,auto
  - DOMAIN-SUFFIX,youtube.com,auto
  - DOMAIN-SUFFIX,twitter.com,auto

  # Fallback
  - MATCH,auto

After editing, restart mihomo:

clashctl /restart

This guide is maintained by the qubes-clash-gateway community. For questions or contributions, open an issue on GitHub.