WireGuard — mihomo
mihomo's WireGuard outbound runs in userspace on a selectable IP stack (gVisor, or mihomo's own mips stack — see ip-stack). The schema offers both a simplified single-peer shape (where peer fields sit at the proxy root) and a verbose multi-peer shape via peers:. The optional amnezia-wg-option block enables interoperation with AmneziaWG-flavored servers.
Outbound
Entry under proxies: with type: wireguard. Embeds BasicOption plus the simplified-peer fields from WireGuardPeerOption.
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
name | string | (required) | <string> | Unique proxy name. |
ip | string | (unset) | <IPv4 CIDR> | Local tunnel IPv4 address (e.g. 10.0.0.2/32). |
ipv6 | string | (unset) | <IPv6 CIDR> | Local tunnel IPv6 address. |
private-key | string | (required) | <base64 key> | Local private key. |
workers | int | (CPU-based) | <int> | Encryption-pipeline worker count. |
mtu | int | 1408 | <bytes> | Tunnel MTU. |
udp | bool | false | true | false | Enable UDP relay. |
persistent-keepalive | int | 0 | <seconds> | Persistent-keepalive interval. 0 disables keepalives. |
ip-stack | IPStackOption | (auto) | IPStackOption | Userspace IP stack that carries the tunnel's TCP/UDP traffic. See the ip-stack table below. |
amnezia-wg-option | *AmneziaWGOption | (unset) | AmneziaWGOption | AmneziaWG obfuscation parameters (junk packets, header masking). |
peers | []WireGuardPeerOption | (use simplified shape) | [WireGuardPeerOption] | Verbose multi-peer list. When set, the embedded simplified fields are ignored. |
remote-dns-resolve | bool | false | true | false | Resolve DNS queries through the WireGuard tunnel using the peer's resolvers. |
dns | []string | [] | [<DNS server>] | Resolvers to use inside the tunnel when remote-dns-resolve is true. |
refresh-server-ip-interval | int | 0 | <seconds> | Re-resolve peer hostnames every N seconds. 0 disables periodic re-resolution. |
Source: adapter/outbound/wireguard.go:69-91 · pinned at v1.19.31 (ab405ba)
peers[] — multi-peer shape
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
server | string | (required) | <host> | Peer hostname or IP. |
port | int | (required) | <port> | Peer UDP port. |
public-key | string | (required) | <base64 key> | Peer public key. |
pre-shared-key | string | (unset) | <base64 key> | Optional PSK. |
reserved | []uint8 | (empty) | <3 bytes> | 3-byte reserved-field override. |
allowed-ips | []string | [] | [<CIDR>] | Allowed-IPs for this peer. |
Source: adapter/outbound/wireguard.go:93-100 · pinned at v1.19.31 (ab405ba)
ip-stack
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
mode | string | auto | auto | gvisor | mips | gvisor uses the gVisor network stack (only in builds with the with_gvisor tag — otherwise startup fails); mips uses mihomo's own IP stack (MIPS = mihomo IP stack, not the CPU architecture); auto picks gVisor when it is compiled in, else mips. |
congestion-controller | string | (stack default) | cubic | reno | bbr | bbr3 | TCP congestion controller for the mips stack. Ignored by gVisor. Any other value is rejected at startup. |
Source: adapter/outbound/wireguard.go:143-146 · pinned at v1.19.31 (ab405ba)
amnezia-wg-option
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
version | int | 0 | 3 | <other> | Implementation selector. 3 switches to the AmneziaWG v3 implementation, which the v3-only fields below require; any other value (including unset) uses the legacy v1.x/v2 implementation. |
jc | int | 0 | <int> | Junk-packet count per handshake. |
jmin | int | 0 | <bytes> | Minimum junk-packet size. |
jmax | int | 0 | <bytes> | Maximum junk-packet size. |
s1 | int | 0 | <bytes> | Pre-init-packet padding length. |
s2 | int | 0 | <bytes> | Pre-response-packet padding length. |
s3 | int | 0 | <bytes> | AmneziaWG v1.5+ — pre-cookie-packet padding length. |
s4 | int | 0 | <bytes> | AmneziaWG v1.5+ — pre-data-packet padding length. |
h1 | string | (unset) | <uint32> | <min-max> | Header magic for the init packet. Decimal only — a single uint32 in v1.x; v2+ also accepts a min-max range. |
h2 | string | (unset) | <uint32> | <min-max> | Header magic for the response packet. Same format as h1. |
h3 | string | (unset) | <uint32> | <min-max> | Header magic for the cookie packet. Same format as h1. |
h4 | string | (unset) | <uint32> | <min-max> | Header magic for the data packet. Same format as h1. |
i1 | string | (unset) | <tag chain> | AmneziaWG v1.5+ — special packet 1, written as a tag chain such as <b 0xf6ab3267fa><r 100>. |
i2 | string | (unset) | <tag chain> | Special packet 2 (same format as i1). |
i3 | string | (unset) | <tag chain> | Special packet 3 (same format as i1). |
i4 | string | (unset) | <tag chain> | Special packet 4 (same format as i1). |
i5 | string | (unset) | <tag chain> | Special packet 5 (same format as i1). |
j1 | string | (unset) | <tag chain> | AmneziaWG v1.5 only — junk packet 1 (tag chain). Rejected when version is 3. |
j2 | string | (unset) | <tag chain> | AmneziaWG v1.5 only — junk packet 2 (tag chain). Rejected when version is 3. |
j3 | string | (unset) | <tag chain> | AmneziaWG v1.5 only — junk packet 3 (tag chain). Rejected when version is 3. |
itime | int64 | 0 | <seconds> | AmneziaWG v1.5 only — junk-packet emission cadence. Rejected when version is 3. |
header-protection-key | string | (unset) | <base64 key> | AmneziaWG v3 — key (base64, e.g. from awg genkey) used to encrypt low-entropy header fields. Requires s1–s4 of at least 12. |
content-padding-addition | string | (unset) | <bytes> | <a-b> | AmneziaWG v3 — extra random padding range for packet content, in bytes (a or a-b). Best set identically on both sides. |
rekey-after-time | string | (unset) | <seconds> | <a-b> | AmneziaWG v3 — seconds after which the client starts a new handshake (a or a-b). |
rekey-timeout | string | (unset) | <seconds> | <a-b> | AmneziaWG v3 — seconds to wait before repeating an unanswered handshake. |
reject-after-time | string | (unset) | <seconds> | <a-b> | AmneziaWG v3 — seconds after which the client forces a handshake and declines incoming data on the old session. |
keepalive-timeout | string | (unset) | <seconds> | <a-b> | AmneziaWG v3 — seconds since the last sent data after which a keepalive is sent. |
max-handshake-attempts | string | (unset) | <int> | <a-b> | AmneziaWG v3 — maximum number of handshake repetitions. |
random-trailers | bool | false | true | false | AmneziaWG v3.1 — append random-length trailers to packets (and accept longer-than-expected packets from the peer). |
disable-cookies | bool | false | true | false | AmneziaWG v3.1 — never answer with cookie replies under load (skips WireGuard's MAC2 DoS check). |
Source: adapter/outbound/wireguard.go:102-141 · pinned at v1.19.31 (ab405ba)
Examples
Simplified single-peer outbound:
proxies:
- name: wg-simple
type: wireguard
server: wg.example.com
port: 51820
private-key: <base64>
public-key: <base64 peer key>
ip: 10.0.0.2/32
ipv6: fd00::2/128
allowed-ips: ['0.0.0.0/0', '::/0']
udp: true
persistent-keepalive: 25Multi-peer outbound (e.g., a hub-and-spoke setup):
proxies:
- name: wg-multi
type: wireguard
private-key: <base64>
ip: 10.0.0.2/32
udp: true
peers:
- server: spoke1.example.com
port: 51820
public-key: <base64-spoke1>
allowed-ips: ['10.0.1.0/24']
- server: spoke2.example.com
port: 51820
public-key: <base64-spoke2>
allowed-ips: ['10.0.2.0/24']AmneziaWG-compatible outbound:
proxies:
- name: awg
type: wireguard
server: awg.example.com
port: 12345
private-key: <base64>
public-key: <base64>
ip: 10.13.13.2/32
allowed-ips: ['0.0.0.0/0']
udp: true
amnezia-wg-option:
jc: 4
jmin: 40
jmax: 80
s1: 0
s2: 0
h1: 123456
h2: 67543
h3: 123123
h4: 32345AmneziaWG v3 outbound:
proxies:
- name: awg3
type: wireguard
server: awg.example.com
port: 12345
private-key: <base64>
public-key: <base64>
ip: 10.13.13.2/32
allowed-ips: ['0.0.0.0/0']
udp: true
amnezia-wg-option:
version: 3
jc: 4
jmin: 40
jmax: 80
s1: 16
s2: 16
s3: 16
s4: 16
h1: 100000-199999
h2: 200000-299999
h3: 300000-399999
h4: 400000-499999
header-protection-key: <base64 key>
content-padding-addition: 0-32Notes
- mihomo accepts both the simplified shape (top-level
server/port/public-key/allowed-ips) and the verbosepeers:list. Whenpeersis set, the embedded simplified fields are ignored. remote-dns-resolve: truemakes WireGuard tunnel DNS queries to the resolvers listed indns:(instead of using the local resolver). Useful when the local DNS cannot reach the destination.refresh-server-ip-intervalonly matters when a peer'sserveris a hostname; the field re-resolves it on a fixed interval, useful for dynamic-DNS endpoints.amnezia-wg-optionfields are versioned:s3/s4/i1-i5are AmneziaWG v1.5+;j1/j2/j3/itimeare v1.5-only (not used by v2 and later);header-protection-key,content-padding-additionand the five timing fields are v3-only, andrandom-trailers/disable-cookiesneed v3.1. The v3 fields requireversion: 3and cannot be combined with the v1.5-only fields — each implementation rejects the other's parameters. See source comments atadapter/outbound/wireguard.go:103-140.ip-stack.mode: autopicks gVisor in builds that include it and mihomo's ownmipsstack otherwise;congestion-controlleronly affectsmips. The sameip-stackblock exists on mihomo's OpenVPN and MASQUE outbounds.
Cross-core notes
- Xray-core uses
peers:always (no simplified shape), exposes anoKernelTunflag for the Linux fast path, and uses field names likesecretKey,address,publicKey(camelCase). See WireGuard — Xray-core. - sing-box configures WireGuard as an endpoint under
endpoints[], not underoutbounds[]. Field names use snake_case (private_key,allowed_ips). See WireGuard — sing-box.
Source: adapter/outbound/wireguard.go:69-141 · v1.19.31 (ab405ba)
