Skip to content

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.

FieldTypeDefaultAllowed valuesDescription
namestring(required)<string>Unique proxy name.
ipstring(unset)<IPv4 CIDR>Local tunnel IPv4 address (e.g. 10.0.0.2/32).
ipv6string(unset)<IPv6 CIDR>Local tunnel IPv6 address.
private-keystring(required)<base64 key>Local private key.
workersint(CPU-based)<int>Encryption-pipeline worker count.
mtuint1408<bytes>Tunnel MTU.
udpboolfalsetrue | falseEnable UDP relay.
persistent-keepaliveint0<seconds>Persistent-keepalive interval. 0 disables keepalives.
ip-stackIPStackOption(auto)IPStackOptionUserspace IP stack that carries the tunnel's TCP/UDP traffic. See the ip-stack table below.
amnezia-wg-option*AmneziaWGOption(unset)AmneziaWGOptionAmneziaWG 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-resolveboolfalsetrue | falseResolve 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-intervalint0<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 ​

FieldTypeDefaultAllowed valuesDescription
serverstring(required)<host>Peer hostname or IP.
portint(required)<port>Peer UDP port.
public-keystring(required)<base64 key>Peer public key.
pre-shared-keystring(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 ​

FieldTypeDefaultAllowed valuesDescription
modestringautoauto | gvisor | mipsgvisor 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-controllerstring(stack default)cubic | reno | bbr | bbr3TCP 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 ​

FieldTypeDefaultAllowed valuesDescription
versionint03 | <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.
jcint0<int>Junk-packet count per handshake.
jminint0<bytes>Minimum junk-packet size.
jmaxint0<bytes>Maximum junk-packet size.
s1int0<bytes>Pre-init-packet padding length.
s2int0<bytes>Pre-response-packet padding length.
s3int0<bytes>AmneziaWG v1.5+ — pre-cookie-packet padding length.
s4int0<bytes>AmneziaWG v1.5+ — pre-data-packet padding length.
h1string(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.
h2string(unset)<uint32> | <min-max>Header magic for the response packet. Same format as h1.
h3string(unset)<uint32> | <min-max>Header magic for the cookie packet. Same format as h1.
h4string(unset)<uint32> | <min-max>Header magic for the data packet. Same format as h1.
i1string(unset)<tag chain>AmneziaWG v1.5+ — special packet 1, written as a tag chain such as <b 0xf6ab3267fa><r 100>.
i2string(unset)<tag chain>Special packet 2 (same format as i1).
i3string(unset)<tag chain>Special packet 3 (same format as i1).
i4string(unset)<tag chain>Special packet 4 (same format as i1).
i5string(unset)<tag chain>Special packet 5 (same format as i1).
j1string(unset)<tag chain>AmneziaWG v1.5 only — junk packet 1 (tag chain). Rejected when version is 3.
j2string(unset)<tag chain>AmneziaWG v1.5 only — junk packet 2 (tag chain). Rejected when version is 3.
j3string(unset)<tag chain>AmneziaWG v1.5 only — junk packet 3 (tag chain). Rejected when version is 3.
itimeint640<seconds>AmneziaWG v1.5 only — junk-packet emission cadence. Rejected when version is 3.
header-protection-keystring(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-additionstring(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-timestring(unset)<seconds> | <a-b>AmneziaWG v3 — seconds after which the client starts a new handshake (a or a-b).
rekey-timeoutstring(unset)<seconds> | <a-b>AmneziaWG v3 — seconds to wait before repeating an unanswered handshake.
reject-after-timestring(unset)<seconds> | <a-b>AmneziaWG v3 — seconds after which the client forces a handshake and declines incoming data on the old session.
keepalive-timeoutstring(unset)<seconds> | <a-b>AmneziaWG v3 — seconds since the last sent data after which a keepalive is sent.
max-handshake-attemptsstring(unset)<int> | <a-b>AmneziaWG v3 — maximum number of handshake repetitions.
random-trailersboolfalsetrue | falseAmneziaWG v3.1 — append random-length trailers to packets (and accept longer-than-expected packets from the peer).
disable-cookiesboolfalsetrue | falseAmneziaWG 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:

yaml
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: 25

Multi-peer outbound (e.g., a hub-and-spoke setup):

yaml
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:

yaml
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: 32345

AmneziaWG v3 outbound:

yaml
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-32

Notes ​

  • mihomo accepts both the simplified shape (top-level server/port/public-key/allowed-ips) and the verbose peers: list. When peers is set, the embedded simplified fields are ignored.
  • remote-dns-resolve: true makes WireGuard tunnel DNS queries to the resolvers listed in dns: (instead of using the local resolver). Useful when the local DNS cannot reach the destination.
  • refresh-server-ip-interval only matters when a peer's server is a hostname; the field re-resolves it on a fixed interval, useful for dynamic-DNS endpoints.
  • amnezia-wg-option fields are versioned: s3/s4/i1-i5 are AmneziaWG v1.5+; j1/j2/j3/itime are v1.5-only (not used by v2 and later); header-protection-key, content-padding-addition and the five timing fields are v3-only, and random-trailers / disable-cookies need v3.1. The v3 fields require version: 3 and cannot be combined with the v1.5-only fields — each implementation rejects the other's parameters. See source comments at adapter/outbound/wireguard.go:103-140.
  • ip-stack.mode: auto picks gVisor in builds that include it and mihomo's own mips stack otherwise; congestion-controller only affects mips. The same ip-stack block exists on mihomo's OpenVPN and MASQUE outbounds.

Cross-core notes ​

  • Xray-core uses peers: always (no simplified shape), exposes a noKernelTun flag for the Linux fast path, and uses field names like secretKey, address, publicKey (camelCase). See WireGuard — Xray-core.
  • sing-box configures WireGuard as an endpoint under endpoints[], not under outbounds[]. Field names use snake_case (private_key, allowed_ips). See WireGuard — sing-box.

Source: adapter/outbound/wireguard.go:69-141 · v1.19.31 (ab405ba)

Core Tutorial by Argsment