ZeroTier — mihomo
mihomo can join a ZeroTier virtual network as a userspace node and route traffic into it from the proxy config. The node runs entirely in userspace — no system ZeroTier service or kernel TUN device — and carries TCP/UDP on the ip-stack it is given. It supports private planets, moons (orbit) and ZeroTier's TCP relay fallback.
Outbound
Entry under proxies: with type: zerotier. Embeds BasicOption (common outbound fields such as dialer-proxy, interface-name, routing-mark and ip-version).
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
name | string | (required) | <string> | Unique proxy name. Also seeds the default state-dir, so renaming the proxy gives the node a fresh identity unless state-dir is set. |
network | string | (required) | <16-hex-digit network ID> | ZeroTier network to join, as the 16-hex-digit network ID. |
state-dir | string | zerotier/<network>-<hash of name> | <directory path> | Directory holding the node's persistent state and its default identity. Resolved against the mihomo working directory and must stay inside the safe-path roots. |
identity-secret | string | (from state-dir) | <identity.secret contents> | Complete identity.secret contents (address:0:public-key:private-key). Must include the private key. Overrides the identity stored in state-dir and is never written there. |
planet | string | (built-in Earth planet) | <planet file path> | Path to a private planet file that replaces the built-in root servers (self-hosted roots). Read and validated at startup. |
mtu | int | (controller MTU) | 1280-10000 | Local MTU override for the virtual network. It can only lower the MTU the controller assigns (2800 when the controller sends none). |
ip-stack | IPStackOption | (auto) | IPStackOption | Userspace IP stack that carries the network's TCP/UDP traffic. Same block as on the WireGuard page (mode, congestion-controller). |
physical-mtu | int | 1432 | 510-10324 | Maximum UDP payload ZeroTier puts on the physical network. |
udp | bool | false | true | false | Allow UDP traffic through this outbound. |
remote-dns-resolve | bool | false | true | false | Resolve destination hostnames inside the ZeroTier network, using the DNS servers the controller pushes (or dns when set). Off means names resolve with mihomo's normal resolver. |
dns | []string | (controller-provided) | <nameserver> | … | Override for the controller-provided DNS servers. Only used when remote-dns-resolve is true; queries travel through this outbound. |
low-bandwidth | bool | false | true | false | Reduce background traffic and how often the network configuration is refreshed. |
encrypted-hello | bool | false | true | false | Send outgoing HELLO packets with ZeroTier's extended (protocol 13) encryption. |
primary-port | int | 0 | 0-65535 | Primary local UDP port. 0 picks any available port. |
secondary-port | int | 0 | -1 | 0-65535 | Second local UDP port. 0 picks an available port (re-picked after about two minutes offline); -1 disables it. Must differ from primary-port. |
tcp-fallback-mode | string | auto | auto | force | disable | TCP relay fallback. auto keeps UDP and switches to the relay only after direct UDP fails; force sends everything through the relay; disable never uses it. |
tcp-fallback-relay | string | 204.80.128.1:443 | <host:port> | TCP fallback relay address. The default is ZeroTier's public relay. |
orbit | []ZeroTierOrbitOption | [] | [ZeroTierOrbitOption] | Federated root worlds (moons) to orbit in addition to the planet. Each world ID may appear only once. |
remote-trace-target | string | (unset) | <10-hex-digit node ID> | Node ID that receives this node's diagnostic traces. |
remote-trace-level | uint64 | 0 | 0 | 10 | 15 | 20 | 30 | Trace detail sent to remote-trace-target: 0 normal, 10 verbose, 15 rules, 20 debug, 30 insane. Values above 30 are rejected. |
Source: adapter/outbound/zerotier.go:108-130 · pinned at v1.19.31 (ab405ba)
orbit[]
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
world | string | (required) | <16-hex-digit world ID> | Moon world ID. |
seed | string | (required) | <10-hex-digit node ID> | Node ID of one root server of that moon, used to fetch the world definition. |
Source: adapter/outbound/zerotier.go:132-135 · pinned at v1.19.31 (ab405ba)
The ip-stack block (mode, congestion-controller) is documented on the WireGuard page.
Examples
Minimal outbound joining a network (authorize the node in your controller after its first start):
proxies:
- name: zt
type: zerotier
network: "0123456789abcdef"
udp: trueFixed identity, controller DNS for hostnames, and wire traffic carried through another proxy:
proxies:
- name: zt-home
type: zerotier
network: "0123456789abcdef"
identity-secret: "0123456789:0:<public-key>:<private-key>"
remote-dns-resolve: true
udp: true
dialer-proxy: ss1Self-hosted roots with a moon and forced TCP relay:
proxies:
- name: zt-private
type: zerotier
network: "0123456789abcdef"
planet: ./zerotier/planet
orbit:
- world: "0123456789abcdef"
seed: "0123456789"
tcp-fallback-mode: forceNotes
- Only destinations inside routes the network controller assigns are reachable; anything else fails with "outside ZeroTier managed routes". To send general traffic through the network, the controller must push a matching route via a member acting as gateway.
state-dirdefaults tozerotier/<network>-<first 12 hex digits of SHA-256(name)>under the mihomo working directory, so each outbound gets an isolated identity. Keep the directory (or setidentity-secret) to keep the same node ID across restarts; a new ID has to be authorized again on a private network.- With a configured
identity-secret, an address collision with another node cannot be fixed by rotating the identity; mihomo retries at most every 30 seconds until the other node goes away. mtuonly lowers the MTU: the effective value is the controller MTU (clamped to 1280–10000, 2800 if none is sent) ormtu, whichever is smaller.dnsis ignored unlessremote-dns-resolve: true. With it, hostname resolution for this outbound goes to the controller's DNS servers (ordns) through the ZeroTier network itself.- Setting
dialer-proxycarries ZeroTier's wire traffic through another outbound; it then uses per-peer UDP sockets instead of shared ones. - ZeroTier support is compiled in by default. A build with the
no_zerotiertag keeps the config shape but fails at startup with "ZeroTier support is disabled".
Cross-core notes
- Xray-core and sing-box have no ZeroTier support. The closest analogues on this site are the mesh-VPN endpoints: Tailscale — sing-box (an endpoint under
endpoints[]) and WireGuard — sing-box. - Within mihomo, Tailscale and EasyTier are the other userspace overlay-network outbounds; WireGuard covers plain point-to-point tunnels.
Source: adapter/outbound/zerotier.go:108-130 · v1.19.31 (ab405ba)
