Skip to content

Snell — sing-box ​

Snell is Surge's lightweight proxy protocol. sing-box implements both sides: a snell inbound (server) and a snell outbound (client). The implementation covers every Snell feature except the v5 QUIC proxy mode, so the versions differ per side — the inbound serves version 5 or 6, the outbound speaks version 4 or 6. No build tag is needed.

Inbound ​

type: "snell" under inbounds[], plus the usual listen fields (listen, listen_port, …):

FieldTypeDefaultAllowed valuesDescription
versionint(required)5 | 6Snell version this inbound serves. 5 accepts Surge v4 and v5 clients (the v5 QUIC proxy mode is not implemented, so the TCP wire format is the same as v4); 6 accepts v6 clients. A missing or other value fails at startup.

Source: option/snell.go:12-17 · pinned at v1.14.2 (af6e64c)

FieldTypeDefaultAllowed valuesDescription
pskstring(required)<string>Server pre-shared key. Version 6 requires 12 to 255 bytes.
users[]SnellUser[][SnellUser]Enables multi-user mode: each client authenticates with its own userkey, while psk stays the server key.

Source: option/snell.go:19-23 · pinned at v1.14.2 (af6e64c)

users[] ​

FieldTypeDefaultAllowed valuesDescription
namestring(unset)<string>Optional label used in logs.
userkeystring(required)<string>The user's key. Clients send it as the outbound userkey.

Source: option/snell.go:135-138 · pinned at v1.14.2 (af6e64c)

Outbound ​

type: "snell" under outbounds[], plus server / server_port and the dial fields:

FieldTypeDefaultAllowed valuesDescription
versionint(required)4 | 6Snell version to speak. 4 talks to v4 and v5 servers (v5 without its QUIC proxy mode is wire-compatible with v4); 6 talks to v6 servers. A missing or other value fails at startup.

Source: option/snell.go:70-75 · pinned at v1.14.2 (af6e64c)

FieldTypeDefaultAllowed valuesDescription
pskstring(required)<string>Server pre-shared key; must match the server.
userkeystring(unset)<string>User key for a multi-user server. Leave empty for a single-user server. Version 6 rejects keys longer than 255 bytes.
reuseboolfalsetrue | falseReuse server connections through the Snell v2 CONNECT command instead of opening a new TCP connection per request.
networkNetworkList(tcp and udp)tcp | udpNetworks this outbound handles. UDP is carried inside a TCP connection to the server.

Source: option/snell.go:77-84 · pinned at v1.14.2 (af6e64c)

Version-specific fields ​

These keys sit at the top level of the inbound or outbound object, next to version. sing-box reads only the keys that belong to the selected version; a key from another version is rejected as unknown at startup.

Obfuscation — inbound version 5 ​

FieldTypeDefaultAllowed valuesDescription
obfs_modestringnonenone | http | tlsVersion 5 only. Obfuscation the server expects: none, an HTTP-request disguise, or a TLS-record disguise. Must match the clients.

Source: option/snell.go:131-133 · pinned at v1.14.2 (af6e64c)

Obfuscation — outbound version 4 ​

FieldTypeDefaultAllowed valuesDescription
obfs_modestringnonenone | http | tlsVersion 4 only. Obfuscation wrapped around the connection; must match the server.
obfs_hoststringbing.com (http) / cloudfront.net (tls)<hostname>Version 4 only. Host shown by the obfuscation: the HTTP Host header in http mode, the server name of the fake ClientHello in tls mode.

Source: option/snell.go:140-143 · pinned at v1.14.2 (af6e64c)

Traffic shaping — version 6 (both sides) ​

FieldTypeDefaultAllowed valuesDescription
modestringdefaultdefault | unshaped | unsafe-rawVersion 6 only. default shapes traffic with a PSK-derived record and padding profile; unshaped keeps the AEAD encryption but drops the shaping; unsafe-raw sends records without encryption. Use the same mode on both sides.

Source: option/snell.go:145-147 · pinned at v1.14.2 (af6e64c)

Examples ​

Version 6 server with two users:

json
{
  "inbounds": [
    {
      "type": "snell",
      "tag": "snell-in",
      "listen": "::",
      "listen_port": 8443,
      "version": 6,
      "psk": "<at-least-12-byte-psk>",
      "users": [
        { "name": "alice", "userkey": "<alice-key>" },
        { "name": "bob", "userkey": "<bob-key>" }
      ]
    }
  ]
}

Version 6 client for that server:

json
{
  "outbounds": [
    {
      "type": "snell",
      "tag": "snell-out",
      "server": "snell.example.com",
      "server_port": 8443,
      "version": 6,
      "psk": "<at-least-12-byte-psk>",
      "userkey": "<alice-key>"
    }
  ]
}

Version 4 client with HTTP obfuscation (works against v4 and v5 servers):

json
{
  "outbounds": [
    {
      "type": "snell",
      "tag": "snell-v4",
      "server": "snell.example.com",
      "server_port": 8388,
      "version": 4,
      "psk": "<psk>",
      "obfs_mode": "http",
      "obfs_host": "www.bing.com"
    }
  ]
}

Notes ​

  • The version pairs are deliberate: without the v5 QUIC proxy mode, the v5 TCP wire protocol is identical to v4, so sing-box ships a v5 server (which v4 and v5 clients reach) and a v4 client (which reaches v4 and v5 servers), but no v4 server or v5 client.
  • obfs_mode: "tls" is accepted by the parser and implemented by the Snell library, although the upstream option reference only lists none and http.
  • The inbound listens on TCP only. UDP from clients is relayed inside the TCP stream, and the outbound does the same, so no UDP port has to be opened on the server.
  • unsafe-raw removes the encryption layer entirely. Only use it where the path is already protected (for example inside another tunnel).
  • In multi-user mode the top-level psk is still required and shared by all users; each user is identified by its userkey.

Cross-core notes ​

  • mihomo has Snell on both sides as well, with kebab-case keys: the outbound accepts version 1–5 (it dials v5 servers as v4) and nests obfuscation under obfs-opts (http / tls, plus the shadow-tls / restls / jls camouflage layers); the inbound uses obfs-opts with http / tls. mihomo has no Snell v6. See Snell — mihomo.
  • Xray-core does not support Snell.

Source: option/snell.go:12-147 · v1.14.2 (af6e64c)

Core Tutorial by Argsment