Skip to content

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).

FieldTypeDefaultAllowed valuesDescription
namestring(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.
networkstring(required)<16-hex-digit network ID>ZeroTier network to join, as the 16-hex-digit network ID.
state-dirstringzerotier/<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-secretstring(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.
planetstring(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.
mtuint(controller MTU)1280-10000Local MTU override for the virtual network. It can only lower the MTU the controller assigns (2800 when the controller sends none).
ip-stackIPStackOption(auto)IPStackOptionUserspace IP stack that carries the network's TCP/UDP traffic. Same block as on the WireGuard page (mode, congestion-controller).
physical-mtuint1432510-10324Maximum UDP payload ZeroTier puts on the physical network.
udpboolfalsetrue | falseAllow UDP traffic through this outbound.
remote-dns-resolveboolfalsetrue | falseResolve 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-bandwidthboolfalsetrue | falseReduce background traffic and how often the network configuration is refreshed.
encrypted-helloboolfalsetrue | falseSend outgoing HELLO packets with ZeroTier's extended (protocol 13) encryption.
primary-portint00-65535Primary local UDP port. 0 picks any available port.
secondary-portint0-1 | 0-65535Second 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-modestringautoauto | force | disableTCP 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-relaystring204.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-targetstring(unset)<10-hex-digit node ID>Node ID that receives this node's diagnostic traces.
remote-trace-leveluint6400 | 10 | 15 | 20 | 30Trace 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[] ​

FieldTypeDefaultAllowed valuesDescription
worldstring(required)<16-hex-digit world ID>Moon world ID.
seedstring(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):

yaml
proxies:
  - name: zt
    type: zerotier
    network: "0123456789abcdef"
    udp: true

Fixed identity, controller DNS for hostnames, and wire traffic carried through another proxy:

yaml
proxies:
  - name: zt-home
    type: zerotier
    network: "0123456789abcdef"
    identity-secret: "0123456789:0:<public-key>:<private-key>"
    remote-dns-resolve: true
    udp: true
    dialer-proxy: ss1

Self-hosted roots with a moon and forced TCP relay:

yaml
proxies:
  - name: zt-private
    type: zerotier
    network: "0123456789abcdef"
    planet: ./zerotier/planet
    orbit:
      - world: "0123456789abcdef"
        seed: "0123456789"
    tcp-fallback-mode: force

Notes ​

  • 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-dir defaults to zerotier/<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 set identity-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.
  • mtu only lowers the MTU: the effective value is the controller MTU (clamped to 1280–10000, 2800 if none is sent) or mtu, whichever is smaller.
  • dns is ignored unless remote-dns-resolve: true. With it, hostname resolution for this outbound goes to the controller's DNS servers (or dns) through the ZeroTier network itself.
  • Setting dialer-proxy carries 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_zerotier tag keeps the config shape but fails at startup with "ZeroTier support is disabled".

Cross-core notes ​

Source: adapter/outbound/zerotier.go:108-130 · v1.19.31 (ab405ba)

Core Tutorial by Argsment