Profile manual
Proxies
On this page
[Proxy] holds the proxies a profile can send traffic through, one line
each.
[Proxy]
Server 1 = trojan, server1.example.net, 443, password=secret
Server 2 = hysteria2, server2.example.net, 443, password=secret, download-bandwidth=200
JP 01 = vless, jp.example.net, 443, username=0233d11c-15a4-47d3-ade3-48ffca0ce119, tls=true, ws=true, ws-path=/ray
SG 01 = ss, sg.example.net, 8388, encrypt-method=2022-blake3-aes-128-gcm, password=MDEyMzQ1Njc4OWFiY2RlZg==
Office = socks5, 10.0.0.5, 1080, alice, secret, underlying-proxy=Server 1
Home = wireguard, section-name=Home
The line#
Name = type, server, port, parameter=value, …
- The name is what rules and groups call the proxy. It may hold spaces. A proxy and a group cannot share a name, and two proxies with one name are an error.
- The type says what the proxy speaks, in any case:
trojan,hysteria2, … - The server and port are where it is: a name or an address, and a
port from 1 to 65535. Every type has them but
wireguard,tailnetand the other names for built-in policies. - Parameters follow as
key=value. A parameter given twice counts once: the last one given. A parameter that is not described here is read past, with a note. Only a component without an unquoted=is taken as a username or password given by position.
A line that cannot be read — no server, a port that is not one, a
component that is not key=value — is an error. A line that can be read
but asks for what Pipe cannot do is a warning: the proxy cannot be used,
groups leave it out, and a rule that names it refuses what it matches,
so that traffic never goes through a proxy other than the one the line
describes.
DIRECT, REJECT and the other built-in names cannot be given to a
proxy: such a line is an error, except a line naming a proxy DIRECT,
which is read past with a note.
Built-in policies#
Every profile has these policies without declaring them. Rules and groups name them as they name proxies.
| Policy | What it does |
|---|---|
DIRECT |
Connects to the destination itself, through no proxy. |
REJECT |
Refuses the connection: a TCP connection is reset, a proxy client is told it is refused, and a plain HTTP request is answered 403 Forbidden. |
REJECT-DROP |
Refuses it without an answer: nothing comes back, and the client waits until it gives up. |
REJECT-NO-DROP |
Refuses it as REJECT does. |
REJECT-TINYGIF |
Answers a plain HTTP request with a transparent 1×1 GIF, and refuses anything else as REJECT does. |
CELLULAR |
Connects directly, out of the cellular interface when there is one, the default way otherwise. |
CELLULAR-ONLY |
Connects directly, out of the cellular interface alone. |
HYBRID |
Connects directly, over Wi-Fi and cellular at once; the first to connect carries the connection. |
NO-HYBRID |
Connects directly, and never tries cellular while on Wi-Fi. |
Leaving the device describes the last four.
A line can give a built-in policy another name, with parameters of its own:
Local = direct, interface=en0
Blocked = reject-drop
The types are direct, reject, reject-drop, reject-no-drop and
reject-tinygif.
Proxy types#
| Type | Protocol | Required |
|---|---|---|
ss |
Shadowsocks | encrypt-method, password |
shadowtls |
ShadowTLS v1, v2 or v3 TCP carrier | password for v2/v3, the camouflage server's sni when different |
vmess |
VMess | username, vmess-aead=true |
trojan |
Trojan | password |
vless |
VLESS | username |
anytls |
AnyTLS | password |
snell |
Snell, versions 1 to 6 | psk |
hysteria2 |
Hysteria 2 | password |
tuic-v5, tuic |
TUIC, versions 5 and 4 | uuid and password; token |
masque |
MASQUE: HTTP/3 CONNECT and CONNECT-UDP, or a CONNECT-IP tunnel (Cloudflare WARP's among them) | — |
http, https |
HTTP proxy, in the clear or inside TLS | — |
h2-connect |
HTTP/2 CONNECT over TLS | — |
naive |
NaiveProxy over native Chromium HTTP/2 or QUIC | — |
socks5, socks5-tls |
SOCKS5, in the clear or inside TLS | — |
ssh |
SSH port forwarding, TCP alone | username, and password or private-key (with private-key-passphrase for a key under one) |
wireguard |
WireGuard | section-name |
tailnet |
A tailnet you are signed in to | — |
external |
A program Pipe runs on a Mac or Linux host, and SOCKS5 to its port | exec, local-port |
Network proxy types also take the parameters for
chains, leaving the device,
ip-version, tests and
QUIC.
Shadowsocks#
SG 01 = ss, sg.example.net, 8388, encrypt-method=chacha20-ietf-poly1305, password=secret
SG 02 = ss, sg.example.net, 8389, encrypt-method=aes-128-gcm, password=secret, obfs=http, obfs-host=bing.com
| Parameter | Values | Default | What it does |
|---|---|---|---|
encrypt-method |
2022-blake3-aes-128-gcm, 2022-blake3-aes-256-gcm, 2022-blake3-chacha20-poly1305, aes-128-gcm, aes-192-gcm, aes-256-gcm, chacha20-ietf-poly1305, xchacha20-ietf-poly1305 |
required | The cipher. Any other makes the proxy unusable. |
password |
text | required | The password. For the 2022-blake3 ciphers, a key in base64: 16 bytes for aes-128-gcm, 32 for the others. |
obfs |
http, tls |
none | simple-obfs around the connection, as an obfs-server expects. Any other value makes the proxy unusable. |
obfs-host |
a host name | bing.com |
The host the obfuscation shows. |
obfs-uri |
a path | / |
The path the http obfuscation asks for. |
udp-port |
a port | — | Not supported yet: noted, and read past. |
UDP goes to the server as Shadowsocks datagrams, outside the obfuscation, which carries TCP alone.
ShadowTLS#
ShadowTLS v3 authenticates a TLS 1.3 camouflage handshake and carries another proxy protocol in authenticated TLS-shaped records. Use it as the underlying proxy of an encrypted protocol such as Shadowsocks:
[Proxy]
Cover = shadowtls, cover.example.net, 443, version=3, password=outer-secret, sni=www.example.net
Private = ss, 127.0.0.1, 8388, encrypt-method=chacha20-ietf-poly1305, password=inner-secret, underlying-proxy=Cover
Native version defaults to 3; importing a sing-box definition with no
version preserves sing-box's default of 1. The camouflage certificate is
checked using the TLS parameters. REALITY cannot be combined
with ShadowTLS, and ECH is available only for v2. Version 3's authenticated records
do not encrypt application data; the inner proxy supplies that encryption and
the destination address. The carrier supports TCP only. With v3, closing its write side
ends the carrier, so applications needing a response after sending EOF should
use another transport.
Legacy versions are available for servers that require them. Version 1 uses TLS 1.2 for camouflage, then sends raw data. It has no password authentication; a configured password has no effect. Version 2 accepts TLS 1.2 or TLS 1.3, authenticates the client's first record with the password and frames later data as TLS application records. It does not authenticate the proxy server or later data records. Both versions still validate the camouflage certificate and require an encrypted inner protocol for application confidentiality and integrity. Only v3 authenticates the proxy server and every data record.
Shadowsocks and Snell also accept the inline form:
Private = snell, cover.example.net, 443, version=4, psk=inner-secret, shadow-tls-password=outer-secret, shadow-tls-version=3, shadow-tls-sni=www.example.net
Inline shadow-tls-version defaults to 2 and accepts 2 or 3.
shadow-tls-sni is required for v3; without it v2 sends no SNI and still
checks the certificate against the server or server-cert-verify-name.
The compiler creates an internal carrier, preserving the selected underlying
proxy, DNS strategy, trust settings and egress. Group-specific proxy chains
keep that carrier too. Imports represent it as two linked entries. ShadowTLS
carriers do not enter automatic proxy groups; a group may name one explicitly.
Inline Shadowsocks with udp-relay=true, and inline wrappers with a separate
udp-port, are refused because a TCP carrier cannot preserve that datagram
path. Use separate TCP and UDP policies for those configurations. Snell's UDP
over its own stream can travel through the carrier.
VMess#
Use VMess in TLS (tls=true, with or without ws=true). In TLS a
connection carries as much as it is given, as other VMess clients'
connections do. Without TLS its 16-bit body nonce would repeat after
65,536 encrypted chunks each way and show an observer two messages under
one key, so the connection stops there instead — after some hundreds of
megabytes, or sooner for many small writes — and says to turn TLS on.
JP 02 = vmess, jp2.example.net, 443, username=0233d11c-15a4-47d3-ade3-48ffca0ce119, vmess-aead=true, tls=true, ws=true, ws-path=/ray
| Parameter | Values | Default | What it does |
|---|---|---|---|
username |
a UUID, or any text | required | The user's ID. Text that is not a UUID is the UUID its name makes, as sing-box and Xray take it. |
vmess-aead |
true |
required | The AEAD handshake. The older handshake is not supported: a line without vmess-aead=true cannot be used. |
encrypt-method |
auto, aes-128-gcm, chacha20-poly1305 |
auto |
The cipher; auto is aes-128-gcm. |
tls |
true, false |
false |
Inside TLS, with the TLS parameters. |
ws and its kin |
A WebSocket transport. |
Trojan#
Server 1 = trojan, server1.example.net, 443, password=secret, sni=cdn.example.com
| Parameter | Values | Default | What it does |
|---|---|---|---|
password |
text | required | The password. |
| TLS parameters | Trojan always rides TLS (TLS parameters). | ||
ws and its kin |
A WebSocket transport inside the TLS. |
VLESS#
JP 01 = vless, jp.example.net, 443, username=0233d11c-15a4-47d3-ade3-48ffca0ce119, tls=true, sni=jp.example.net
| Parameter | Values | Default | What it does |
|---|---|---|---|
username |
a UUID, or any text | required | The user's ID. Text that is not a UUID is the UUID its name makes, as sing-box and Xray take it. |
tls |
true, false |
false |
Inside TLS, with the TLS parameters. Without it, plain TCP. |
ws and its kin |
A WebSocket transport. | ||
flow |
xtls-rprx-vision |
none | Vision padding and directional TLS bypass for inner TLS 1.3. Without VLESS encryption, requires TLS without WebSocket, HTTPUpgrade, gRPC, XHTTP or obfuscation. UDP remains available through XUDP. |
encryption |
none, or Xray's mlkem768x25519plus.… |
none |
VLESS encryption. |
xhttp and its kin |
An XHTTP transport. |
Vision = vless, vision.example.net, 443, username=0233d11c-15a4-47d3-ade3-48ffca0ce119, tls=true, sni=vision.example.net, flow=xtls-rprx-vision
Vision starts inside authenticated outer TLS. Eligible inner TLS 1.3 traffic can then move to the underlying connection independently in each direction; other traffic stays inside outer TLS. It preserves proxy-chain socket protection and routing. It does not require a browser TLS fingerprint.
REALITY uses tls=true, a DNS server name in sni, the server's
reality-public-key (32 bytes in unpadded URL-safe base64), and optional
reality-short-id (up to 16 hexadecimal digits in whole bytes). The server's
public key authenticates the handshake; certificate pins, custom CAs, client
certificates and ECH cannot be combined with it. skip-cert-verify does not
disable REALITY authentication. TLS uses native rustls by default. Set
client-fingerprint=chrome for the Chrome 133 TLS profile used by sing-box's
chrome preset; importing fp=chrome preserves this choice, and an import
naming another browser (fp=firefox, random, …) gets it too, with a note
(Importing). This requires the verified
browser-capable native library for the target.
VLESS encryption
Xray's VLESS encryption seals everything VLESS sends with keys a
post-quantum handshake makes (ML-KEM-768 and X25519), with or without
TLS around it. encryption is what the server's xray vlessenc gives
as the client's, as it is:
Sealed = vless, sealed.example.net, 443, username=0233d11c-15a4-47d3-ade3-48ffca0ce119, encryption=mlkem768x25519plus.native.0rtt.-zL8lfQSFE7pBWmN6CrPTasbxuzpkxOjnAVVm8I9dFQ
It reads mlkem768x25519plus, then native, xorpub (the handshake's
keys look random) or random (every record's header does too), then
1rtt or 0rtt (later connections reuse the ticket the first was given
and send at once), then the padding, if any, and the server's keys:
X25519 or ML-KEM-768, one or more relayed. It goes over any transport.
flow=xtls-rprx-vision goes with it, with or without TLS: Xray's
recommended pairing, Vision over the encrypted stream.
AnyTLS#
US 01 = anytls, us.example.net, 443, password=secret
| Parameter | Values | Default | What it does |
|---|---|---|---|
password |
text | required | The password. |
| TLS parameters | AnyTLS always rides TLS (TLS parameters). | ||
reuse |
true, false |
true |
reuse=false is not supported yet: noted, and read past. |
Snell#
KR 01 = snell, kr.example.net, 44046, psk=secret, version=4, obfs=tls
KR 02 = snell, kr.example.net, 44047, psk=secret, version=6, mode=unshaped, reuse=true
| Parameter | Values | Default | What it does |
|---|---|---|---|
psk |
text | required | The pre-shared key. |
version |
1 to 6 |
1 |
The protocol version the server speaks. 5 is spoken as 4, which a version 5 server takes. Any other value makes the proxy unusable. |
mode |
default, unshaped, unsafe-raw |
default |
Version 6 alone, as the server has it: default shapes records as the PSK says; unshaped does not; unsafe-raw sends in the clear. A mode on an earlier version makes the line unusable. |
obfs |
http, tls, none, off |
none | Obfuscation, versions 1 to 5. A version 6 line with obfs=http or obfs=tls cannot be used. |
obfs-host |
a host name | bing.com for http, cloudfront.net for tls |
The host the obfuscation shows. |
obfs-uri |
a path | / |
The path obfs=http asks for; with tls it is noted as changing nothing. |
reuse |
true, false |
false |
Versions 4 to 6: a connection done with one request carries the next, waiting 30 seconds for it. Version 2 always does this and versions 1 and 3 never do; a line that asks otherwise gets a note. |
udp-port |
a port | the line's port | Where the UDP tunnel's connection goes. Versions 1 and 2 carry no UDP, and a note says so. |
userkey |
text | none | Who this client is, to a server with several users. |
From version 3 on, Snell carries UDP, each session on a connection of its own.
Hysteria 2#
Server 2 = hysteria2, server2.example.net, 443, password=secret, download-bandwidth=200, upload-bandwidth=50
Server 3 = hysteria2, server3.example.net, 443, password=secret, salamander-password=noise, port-hopping="20000-30000;443", port-hopping-interval=20
Server 4 = hysteria2, server4.example.net, 443, password=secret, gecko-password=noise, gecko-min-packet-size=600
| Parameter | Values | Default | What it does |
|---|---|---|---|
password |
text | required | The password. |
download-bandwidth |
Mbit/s | none | The rate the server is asked to send at. |
upload-bandwidth |
Mbit/s | none | The rate Pipe sends at: the lower of this and what the server will take. Hysteria's own congestion control, Brutal, keeps to that rate; without a rate agreed, BBR is used. A sing-box server agrees on one only with a client that gives download-bandwidth too. |
| TLS parameters | Hysteria 2 rides QUIC, which is TLS (TLS parameters); alpn is h3 unless it says. |
||
salamander-password |
text | none | Salamander obfuscation of every packet. |
gecko-password |
text | none | Gecko obfuscation: Salamander, and the handshake's packets cut into 2 to 8 fragments padded to random sizes. A line cannot have both. |
gecko-min-packet-size, gecko-max-packet-size |
bytes, at most 2048 | 512, 1200 | The sizes Gecko pads fragments to on the wire. Pipe's additions (mihomo's obfs-min-packet-size, sing-box's min_packet_size). |
port-hopping |
ports and ranges, separated by ; or ,: "20000-30000;443" |
none | The ports to hop between; the line's own port is then not used. A port that is not one, or a list with none, makes the line unusable. |
port-hopping-interval |
seconds, 5 at least | 30 | How often to hop to another port. |
ecn |
auto, on, off (true, false) |
auto |
Whether packets are marked ECN-capable. auto and on mark them until the path is found not to carry the marks; off never does. |
Port hopping cannot go through another policy: a line with both
port-hopping and underlying-proxy cannot be used.
TUIC#
tuic-v5 is TUIC version 5; tuic is version 4.
SG 03 = tuic-v5, sg3.example.net, 443, uuid=0233d11c-15a4-47d3-ade3-48ffca0ce119, password=secret, congestion-control=bbr
SG 04 = tuic, sg4.example.net, 443, token=secret, udp-relay-mode=quic
| Parameter | Values | Default | What it does |
|---|---|---|---|
uuid, password |
a UUID, text | required (v5) | Who the client is. |
token |
text | required (v4) | The token, in place of uuid and password. |
| TLS parameters | TUIC rides QUIC (TLS parameters); alpn is h3 unless it says. |
||
congestion-control |
cubic, new_reno, bbr |
cubic |
The congestion control. Any other makes the line unusable. |
udp-relay-mode |
native, quic |
native |
How UDP is carried. native: as QUIC datagrams. quic: each datagram on a stream of its own. |
udp-over-stream |
true, false |
false |
Version 5: UDP carried as UoT version 2 on a stream. Not with udp-relay-mode=quic, and not on version 4: either makes the line unusable. |
heartbeat |
seconds | 10 | How often a connection in use says it is still there, so that the server and the paths between keep it. |
port-hopping, port-hopping-interval |
As for Hysteria 2. | ||
ecn |
auto, on, off |
auto |
As for Hysteria 2. |
On version 4, a datagram goes whole: in the native relay mode, one
larger than the path carries cannot go.
MASQUE#
Office = masque, proxy.example.net, 443, username=alice, password=secret
WARP = masque, 162.159.198.1, 443, mode=cloudflare, private-key=MHcCAQEEIKbmk1X1riPhNjwA2hrp/nRb90qhtIr4Q/jzz6VpArbQoAoGCCqGSM49AwEHoUQDQgAEBRmZdz0z2WaCjD/gdbGsaiVun4OdtI8Cu4O+SFz/5d+HIKgbgcakp8zfy+VIUC9NB2/F9voVok6hT3+n8EYJbA==, public-key=MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFkanyq60YX7ymGMjH39nzSfADy5pd/SGgJX80WLB23wULLFfHGruy8m7AMAP5TeQIBVK3ndGAhTUnoCo5LfpuQ==, self-ip=172.16.0.2, self-ip-v6=2606:4700:110:8a36::2
MASQUE proxies over HTTP/3, on one QUIC connection that every TCP connection and UDP session of the policy shares. A plain line is Surge's: each TCP connection is an HTTP/3 CONNECT stream, and each UDP session a CONNECT-UDP request (RFC 9298) whose datagrams ride HTTP Datagrams (RFC 9297). UDP needs nothing on the line, but the server has to take extended CONNECT and HTTP Datagrams, which its HTTP/3 settings say when the connection is made; where they do not, the policy carries TCP alone and its UDP goes as UDP a proxy cannot carry does.
mode=connect-ip is a CONNECT-IP tunnel (RFC 9484) instead: the
connection carries IP packets, and Pipe's own network stack makes TCP
connections and UDP sessions of them, from the addresses the tunnel has.
mode=cloudflare is Cloudflare WARP's MASQUE, as mihomo speaks it: a
CONNECT-IP tunnel in WARP's dialect, the account's key shown in a
certificate Pipe makes of it, the server known by its public key alone.
The keys and addresses come from the account's registration (WARP's
tools and mihomo configs have them); Pipe does not register accounts.
| Parameter | Values | Default | What it does |
|---|---|---|---|
username, password |
text | none | Sent as HTTP Basic credentials (Proxy-Authorization) with each request. Both or neither. |
| TLS parameters | MASQUE rides QUIC (TLS parameters); alpn is h3 unless it says, and client-cert shows a [Keystore] item. |
||
mode |
connect, connect-ip, cloudflare |
connect |
How the connection carries traffic, as above. |
uri |
a URI template, or a path on the server | the RFCs' /.well-known/masque/udp/{target_host}/{target_port}/ and /.well-known/masque/ip/{target}/{ipproto}/; WARP's https://cloudflareaccess.com |
Where the extended CONNECT asks: {target_host} and {target_port} for UDP (both needed), {target} and {ipproto} for a tunnel (* each), or a query of them ({?target_host,target_port}). |
private-key |
base64 DER (SEC1 or PKCS#8), P-256 or P-384 | none | An EC key shown in a certificate of its own: WARP's enrolled key. |
public-key |
base64 DER (SubjectPublicKeyInfo) | none; required by cloudflare unless skip-cert-verify |
The server's key: the one check of its certificate, which skip-cert-verify does not lift. |
self-ip, self-ip-v6 |
an address | the server's assignment | A tunnel's own addresses. Without either, the tunnel waits for the server to assign them. |
mtu |
1280 to 65535 | 1280 | The largest IP packet in a tunnel. |
dns-server |
addresses, system |
the engine's DNS | Where a tunnel looks up the names it dials, asked through it, as for a WireGuard section. |
prefer-ipv6 |
true, false |
false |
A name with addresses of both families is dialled over IPv6 in a tunnel. |
port-hopping, port-hopping-interval |
As for Hysteria 2. | ||
ecn |
auto, on, off |
auto |
As for Hysteria 2. |
self-ip, self-ip-v6, mtu, dns-server and prefer-ipv6 are a
tunnel's: on a connect line they make it unusable. A datagram or a
tunnel's packet too large for the path's QUIC datagrams goes in a
DATAGRAM capsule on its request stream instead, reliably and in order;
a server that reads no capsules loses it, as the path would. A server's
GOAWAY sends what comes next to a new connection. Port hopping cannot
go through another policy.
HTTP and SOCKS5#
Office = http, proxy.example.com, 8080, alice, secret
Secure = https, proxy.example.com, 443, username=alice, password=secret, sni=proxy.example.com
Lab = socks5, 10.0.0.5, 1080
Relay = socks5-tls, relay.example.com, 443, alice, secret
https and socks5-tls are the same proxies as http and socks5,
inside TLS, and take the TLS parameters.
| Parameter | Values | Default | What it does |
|---|---|---|---|
username, password |
text | none | The credentials, written after the port in that order, or named. |
headers |
Name:value;Name:value |
none | http and https: headers sent to the proxy, each in place of the request's own header of that name, Host too. <random-string(16)> or <random-string(16-32)> in a value is a random string of that length, new for each connection. On socks5, noted and read past. |
With socks5-tls, UDP goes to the proxy's relay in the clear, as SOCKS5
carries it.
SSH#
Box = ssh, box.example.net, 22, username=me, password=secret, server-fingerprint="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0bqjWWRpPmLJQ4eA9X4nOszyfZ56ya0QNfDZpK6OJ1"
With a key, the key is a [Keystore] item, and a key
under a passphrase has it beside it:
[Proxy]
Lab = ssh, lab.example.net, 2222, username=me, private-key=lab-key, server-fingerprint=SHA256:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU, idle-timeout=600
Vault = ssh, vault.example.net, 22, username=me, private-key=vault-key, private-key-passphrase="correct horse", server-fingerprint=SHA256:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU
[Keystore]
lab-key = type=openssh-private-key, base64=LS0tLS1CRUdJTiBPUEVOU1NIIFBSSVZBVEUgS0VZLS0tLS0K…
vault-key = type=openssh-private-key, base64=LS0tLS1CRUdJTiBPUEVOU1NIIFBSSVZBVEUgS0VZLS0tLS0K…
Every connection is a channel (direct-tcpip, what ssh -W asks for)
of one SSH session to the server, made for the first and kept while it
carries any. The server must let the user forward connections
(AllowTcpForwarding, OpenSSH's default).
| Parameter | Values | Default | What it does |
|---|---|---|---|
username |
text | required | Who logs in. |
password |
text | none | The user's password, tried after the key, by password and then keyboard-interactive authentication. |
private-key |
a [Keystore] item |
none | The user's key: an openssh-private-key item, the key file as ssh-keygen writes it (OpenSSH's format or PEM), in base64. Ed25519, ECDSA (P-256, P-384, P-521) and RSA keys. |
private-key-passphrase |
text | none | The passphrase the key is encrypted under (mihomo's name; sing-box's private_key_passphrase). Read for a key in OpenSSH's own format, as ssh-keygen encrypts one: bcrypt with AES-CTR, its default, or AES-GCM (-Z aes256-gcm@openssh.com). A wrong one is said to be, on the line. |
server-fingerprint |
host keys as known_hosts writes them (ssh-ed25519 AAAA…), or SHA256:… fingerprints as ssh-keygen -l prints them, separated by commas |
required, unless skip-host-key-verify |
The keys the server's must be one of. ssh-keyscan box.example.net prints a server's; check them against the server itself. |
skip-host-key-verify |
true, false |
false |
With no server-fingerprint, any server key is taken: a server in the middle gets the password and reads what goes through. Without either, the line is unusable. |
idle-timeout |
seconds | 180 | How long a session with no connection on it is kept. |
client-version |
SSH-2.0-… |
SSH-2.0-Pipe |
What Pipe says it is to the server. Pipe's addition (sing-box's client_version). |
- Pipe speaks what OpenSSH 6.5 and later do:
curve25519-sha256with strict key exchange, AES-GCM or ChaCha20-Poly1305, and host keys Ed25519, ECDSA or RSA (SHA-2). A server with none of them cannot be used, and the reason says what it offers. - A session that carries connections asks the server whether it is there after 30 quiet seconds, and is closed when it does not answer in 15; its keys change after each gigabyte. A session made before the network changed takes no new connections, and closes when its last is done.
- SSH forwards no UDP: the proxy carries TCP alone, and its UDP goes
where
udp-policy-not-supported-behavioursends it. - A key under a passphrase is opened each time the profile is checked
and the tunnel starts, at the cost its file asks for:
ssh-keygen's 24 rounds unless-asaid more. A key asking for more than 128 rounds is not read (ssh-keygen -p -a 100 -f key, run on a copy, rewrites it with fewer), and a profile's keys together may ask for the second's work of key derivation[Keystore]allows: some six keys of 24 rounds. An encrypted PEM key is not read:ssh-keygen -p -f key, run on a copy, rewrites it in OpenSSH's format. - Imported from Clash/mihomo (
type: ssh) and sing-box, an SSH proxy comes into the proxy pool with its password, or its key and the key's passphrase, held as the pool holds a password. A key must be in the config itself (mihomo'sprivate-key: |text, sing-box'sprivate_key): one named by its file's path is not imported, as that file is on the machine the config was written for. A converted profile, and the pool's list, write each key as a[Keystore]item of its own, namedssh-key-and its digest.
NaiveProxy#
Browser proxy = naive, proxy.example.net, 443, username=alice, password=secret, sni=proxy.example.net, udp-over-tcp=true
Browser QUIC = naive, proxy.example.net, 443, username=alice, password=secret, quic=true
naive uses the pinned native Chromium network stack, including Naive's
request and stream padding. quic=true selects HTTP/3; the default is
HTTP/2. udp-over-tcp=true enables sing-box UDP-over-TCP version 2 over
the selected transport. headers=Name:value;Name:value adds request headers.
The proxy's authentication, padding and transport control headers are reserved.
TLS certificate verification is required. sni, tls-ca-base64, ech,
ech-config and ech-query-server-name are supported. Naive does not accept
skip-cert-verify, custom ALPN or fingerprint profiles, certificate pins,
REALITY or client certificates. ECH requires a DNS name in sni.
underlying-proxy and the profile's server resolver also apply to native
connections. Resource limits are fixed for mobile use; sing-box's concurrency,
receive-window and congestion-control tuning fields are refused on import.
A build links a verified source archive and receipt from
tools/build-cronet-native.py; the iOS app requires one, for the browser and
source-trust APIs. Cargo verifies native inputs before linking and never
downloads them. Builds without a matching archive report Naive as unavailable.
Protocol interoperability tests run on the engine host. Physical-device VPN memory and energy performance require separate qualification.
HTTP/2 CONNECT#
Office H2 = h2-connect, proxy.example.net, 443, username=alice, password=secret, max-streams=3
h2-connect carries TCP connections as independent HTTP/2 streams over
TLS. The proxy must negotiate h2 with ALPN. Authentication, custom
headers (including random padding), TLS parameters,
and underlying-proxy apply. Connection-specific HTTP headers and a
custom Host header are refused; CONNECT identifies its destination in
the HTTP/2 authority field.
max-streams limits simultaneous tunnels on each connection to 1–32,
default 3. Pipe opens up to eight connections for a policy and refuses
further concurrent tunnels when those are full. Each connection reserves
its bounded buffers from the engine's memory budget and closes when an
idle check finds no tunnels. Cancelling a tunnel resets its stream;
other streams continue. A network change opens a fresh connection for
new tunnels while existing ones can finish.
udp-relay=true enables RFC 9298 CONNECT-UDP. It is off by default.
The server must advertise extended CONNECT support in its HTTP/2
settings; Pipe confirms this before sending a UDP request. It uses the
standard /.well-known/masque/udp/{target_host}/{target_port}/ endpoint,
with authentication and TLS settings shared with TCP. Each UDP
association occupies one stream in the same pool as TCP tunnels.
UDP payloads travel in RFC 9297 capsules, so packet boundaries and empty
datagrams are preserved. The engine sends up to 65,507 bytes per packet;
the reader accepts the RFC's 65,527-byte maximum. Unknown capsule types
and context IDs are skipped incrementally. Malformed capsules end their
stream. Buffers and bounded packet queues use the engine's memory budget.
Because capsules travel over TCP, delivery is reliable and ordered;
this is not native UDP loss behavior, and block-quic=auto still applies.
WebSocket transport (ws=true) is not supported for this policy type.
WireGuard#
Home = wireguard, section-name=Home
section-name names the [WireGuard <name>]
section that holds the tunnel's settings. A line naming a section that is
not there cannot be used.
Tailnet#
Home exit = tailnet, exit-node=mac-mini
Work exit = tailnet, tailnet=example.com, exit-node=auto
A tailnet is not something a profile holds: you sign in to it in the
app (Tailnets), and a tailnet policy names one for rules
and groups.
| Parameter | Values | Default | What it does |
|---|---|---|---|
tailnet |
a tailnet's name | the one signed in | The tailnet, by its name as its admin console shows it (example.com), or its MagicDNS suffix. It may be left out while one tailnet is signed in. |
exit-node |
auto, or a node |
none | Where what the policy is given for the internet goes. auto: the nearest exit node online, kept while it stays online. A node is named by its MagicDNS name (short or full), its stable ID or its address. Without it, the policy reaches only the tailnet. |
- A policy whose tailnet is not signed in, is off or is not running fails as an unreachable proxy does: groups pass over it, and a rule that names it refuses what it matches.
- It cannot go through an
underlying-proxy: the tailnet is reached by its account's own node. - It needs active Pipe Pro. Without it, the policy is left out as a proxy Pipe cannot use ("requires Pipe Pro: tailscale"), and the rest of the profile runs. Nothing else a profile says needs Pro.
- A Surge
tailscaleline and its[Tailscale <name>]section are not read: converting a Surge profile makes such a line atailnetpolicy of the same name, keeping itsexit-node, and offers the tailnet for sign-in with the section's auth key and control server, which never go into the profile. sing-box's Tailscale endpoints come across the same way.
External proxy programs#
A program that serves SOCKS5 on 127.0.0.1, such as ssh -D, used as a
policy. Pipe runs it, on a Mac or a Linux host:
[Proxy]
Bastion = external, exec = "/usr/bin/ssh", args = "user@1.2.3.4", args = "-N", args = "-D", args = "127.0.0.1:1080", local-port = 1080, addresses = 1.2.3.4
| Parameter | What it is |
|---|---|
exec |
The program, by its whole path. Nothing is looked up in PATH, and no shell reads the line. |
args |
One argument. Given once for each, in order; at most 64. |
local-port |
The port it serves SOCKS5 on, on 127.0.0.1. |
addresses |
An address the program itself connects to, given once for each: it is left out of the tunnel. At most 32. |
udp-relay |
true: UDP goes through the program too, if it does SOCKS5 UDP. Off unless said. |
- It is started when its policy is first used, and started again when it stops: after a second, then twice as long each time it stopped within a minute of starting, up to 30 seconds.
- A connection to its port that is refused, while it is coming up, is tried again every 500 ms, six times more.
- It stops when Pipe stops, with what it started. It runs in a process
group of its own, with no terminal, and with
HOME,USER,LOGNAME,PATH,LANG,LC_ALL,TMPDIRandSSH_AUTH_SOCKand no other variable. What it says goes to a log of at most 512 KiB, and one older part of the same size. - What the program and its child processes connect to goes
DIRECT, whatever the mode and the rules say: it is what the policy rides on. A process that left the program's group (a daemon) is not known to be the program's: name what it connects to inaddresses. - On iOS the line is noted, and refuses what it is given, as
REJECTdoes.
Approving a program
A profile can come from anyone, and this line runs a program as you. So a profile never gets to say a program may run:
- Compiling says what the line would run, in a warning on the line and in the profile's summary, whether or not it is approved.
- Nothing runs until you approve that exact command in the app: the path, every argument in order, and the port. Another argument is another command, which waits for a decision of its own. Until then, and after a refusal, the policy fails its connections.
- The decision is kept with Pipe's data, and the app lists every decision with the command it was about. Taking one back stops the program.
- What the app shows is what runs. A path with
.or..in it, a relative path, and a word holding a control or text-direction character make the proxy unusable. - Modules cannot add proxies, and no
policy-pathlist, managed profile or file included from a URL can bring this type: such a line is left out or unusable, with a warning, whatever was approved. Those change without anyone reading them. A linked profile of your own can hold it. - The decision is the app's alone. The HTTP API over the network and
scripts cannot read or make one: a profile chooses
http-api's key. - An engine that runs with more rights than the user who wrote its
profile (
--confine) runs no program at all.
TLS parameters#
Where a protocol rides TLS — trojan, anytls, shadowtls, https, h2-connect, naive, socks5-tls,
and vmess and vless with tls=true — or QUIC — hysteria2, tuic,
tuic-v5 — these parameters say how the server is checked.
| Parameter | Values | Default | What it does |
|---|---|---|---|
sni |
a host name, or off |
the server's host | The name the handshake asks for. sni=off sends no name; the certificate is still checked against the server's host, or against server-cert-verify-name. |
skip-cert-verify |
true, false |
false |
Any certificate is taken. The handshake's signatures are still checked — whoever shows a certificate holds its key — but the certificate is not. |
server-cert-verify-name |
a host name | the name asked for | The certificate is checked against this name, while the name asked for stays the sni's. |
server-cert-fingerprint-sha256 |
64 hexadecimal digits, or their base64, with or without colons between pairs | none | The server's certificate must be this one, by the SHA-256 of its DER encoding. It is the only check, in place of the usual validation, so a self-signed certificate can be trusted; skip-cert-verify does not lift it. A value that is neither makes the line unusable. |
client-cert |
a [Keystore] item |
none | The certificate, with the chain behind it, and the key shown to a server that asks for one. An item that is not there, or that cannot be read, makes the line unusable. |
client-fingerprint |
chrome, chrome-133 |
native rustls | Use the Chrome 133 TLS profile, including its real cipher suites, key shares, shuffled extensions, ECH GREASE and Brotli certificate decompression. Requires a verified source-built native library. TCP TLS only; Naive uses its own native profile and QUIC does not accept this parameter. |
tls-ca-base64 |
base64 of a PEM certificate bundle | none | Replace the default trust roots with these certificates, at most 64 anchors and 256 KiB of PEM. |
ech |
true, false |
false |
Encrypt the inner ClientHello and server name with ECH. Requires TLS 1.3 and SNI. A failed ECH handshake does not retry with a visible inner name. |
ech-config |
base64 of an ECHConfigList | HTTPS DNS lookup | Static ECH configuration, used with ech=true. In sing-box JSON, tls.ech.config holds an ECH CONFIGS PEM block. |
ech-query-server-name |
a DNS name | sni |
Name whose HTTPS DNS record supplies ECH configuration when ech-config is absent. Uses the profile's server bootstrap resolver and DNS cache expiry. |
alpn |
protocols, separated by commas, quoted when there are several: alpn="h2, http/1.1" |
none over TCP; h3 over QUIC |
The protocols the handshake offers. A WebSocket asks for http/1.1. |
tls13 |
true, false |
— | Changes nothing, and a note says so: TLS 1.3 is always offered, and used when the server has it; TLS 1.2 otherwise. |
tls-min-version, tls-max-version |
1.2, 1.3 |
both | The lowest and highest version the handshake may settle on. |
The Chrome option matches the TLS profile selected by sing-box 1.14.3, which
pins uTLS's Chrome 133 preset. It does not turn proxy framing or HTTP headers
into a browser. ALPN defaults to h2,http/1.1; a transport's required ALPN
still applies. Custom ALPN or TLS-version bounds deliberately alter the
profile. Other browser profiles and targets without the required native
library are reported as unavailable; they never fall back silently.
tools/build-cronet-native.py produces the native library and its checked
receipt. Cargo verifies that receipt, including the pinned source, TLS patches
and trust policy, before enabling Naive and the Chrome option.
Apple's tools/build-apple.sh builds missing or stale archives before
packaging, in CARGO_TARGET_DIR and the user's cache directory.
On a proxy without TLS, ECH and custom trust roots make the proxy unusable; the other parameters are noted as changing nothing.
Certificates are checked as the system checks them, unless
tls-ca-base64 replaces its roots: on iPhone and iPad, a root you installed
with a configuration profile and turned on in Settings › General › About ›
Certificate Trust Settings is trusted too, by proxies and by everything Pipe
downloads itself (rule sets, subscriptions, scripts' requests). ECH preserves the configured
certificate checks. Remote certificate_path and ECH config_path files are
not imported; provide their inline contents instead.
WebSocket#
vmess, trojan and vless can carry their protocol inside a
WebSocket. With TLS, the WebSocket is inside it, and the TLS asks for
HTTP/1.1.
CDN = trojan, cdn.example.com, 443, password=secret, ws=true, ws-path=/t?ed=2048, ws-headers=Host:origin.example.com, ws-early-data=2048
| Parameter | Values | Default | What it does |
|---|---|---|---|
ws |
true, false |
false |
Use a WebSocket. Without it, the other ws- parameters are noted and read past. |
ws-path |
a path | / |
The path of the upgrade request, sent as written, query and all. |
ws-headers |
Name:value|Name:value |
Host is the server and its port |
Headers of the upgrade request. |
ws-early-data |
bytes | 0 |
Up to that many bytes of the protocol's first request ride in the upgrade itself, saving a round trip. |
ws-early-data-header |
a header name, or "" |
Sec-WebSocket-Protocol |
The header the early data goes in. With "", it goes after the path instead. Without ws-early-data, noted and read past. |
HTTPUpgrade and gRPC#
vmess, trojan and vless also support HTTPUpgrade and gRPC transports.
Choose one transport per proxy: WebSocket, HTTPUpgrade, gRPC or XHTTP.
Upgrade = vless, cdn.example.com, 443, username=b831381d-6324-4d53-ad4f-8cda48b30811, tls=true, http-upgrade=true, http-upgrade-path=/tunnel, http-upgrade-host=origin.example.com
RPC = trojan, cdn.example.com, 443, password=secret, grpc=true, grpc-service-name=pipe
| Parameter | Default | What it does |
|---|---|---|
http-upgrade |
false |
Start an HTTP/1.1 upgrade, then carry the protocol as an unframed stream. |
http-upgrade-path |
/ |
Request path, including any query. |
http-upgrade-host |
TLS server name, else server and port | Host header sent to the front end. |
http-upgrade-headers |
none | Additional headers, written Name:value|Name:value. |
grpc |
false |
Carry the protocol through an HTTP/2 gRPC stream. |
grpc-service-name |
empty | The server's service name, encoded in /<service>/Tun. |
TLS uses HTTP/1.1 for HTTPUpgrade and HTTP/2 for gRPC. These transports
also carry the proxy protocol's UDP relay when it supports one. Proxy
chains still select how the transport reaches its server. gRPC idle/ping
tuning is not exposed; imports with unsupported active settings report it.
These transports use Pipe's default rustls TLS or the explicitly selected
client-fingerprint=chrome native TLS profile, including REALITY when
configured. Vision cannot be combined with their additional framing.
XHTTP#
vmess, trojan and vless can also go as Xray's XHTTP: the stream
as HTTP requests, through the CDNs and front ends that carry HTTP.
Edge = vless, edge.example.net, 443, username=0233d11c-15a4-47d3-ade3-48ffca0ce119, tls=true, sni=edge.example.net, xhttp=true, xhttp-path=/x, xhttp-mode=auto
Packets = trojan, cdn.example.com, 443, password=secret, xhttp=true, xhttp-path=/up, xhttp-host=origin.example.com, xhttp-mode=packet-up, xhttp-max-connections=4
HTTP/2 carries the requests in TLS, HTTP/1.1 with alpn=http/1.1
(packet-up only) and HTTP/3 with alpn=h3. HTTP/3 goes in QUIC's own
TLS, so without REALITY or a client-fingerprint.
| Parameter | Values | Default | What it does |
|---|---|---|---|
xhttp |
true, false |
false |
Go as XHTTP. Not with ws, http-upgrade or grpc. |
xhttp-path |
a path | / |
The path of every request, query and all. |
xhttp-host |
a host | the sni, else the server |
The host the requests ask for. |
xhttp-mode |
auto, packet-up, stream-up, stream-one |
auto |
One request each way (stream-one), a download and a streaming upload (stream-up), or a download and uploads in pieces (packet-up). auto is stream-one under REALITY and packet-up otherwise. |
xhttp-headers |
Name:value|Name:value |
a browser's | Headers of every request; with a User-Agent of its own, no browser's. |
xhttp-padding |
bytes, or from-to |
100-1000 |
Padding each request carries, which the server checks. |
xhttp-no-grpc-header |
true, false |
false |
No Content-Type: application/grpc on streaming uploads. |
xhttp-max-post-bytes |
bytes, or from-to |
1000000 |
packet-up: the most an upload carries (256 KiB at most on iPhone and iPad). |
xhttp-min-post-interval |
milliseconds, or from-to |
30 |
packet-up: the least time between uploads. |
xhttp-max-connections |
a count, or from-to |
3 |
Connections the requests are spread over. Not with xhttp-max-concurrency. |
xhttp-max-concurrency |
a count, or from-to |
none | Connections on one before another is opened. |
xhttp-max-reuse-times |
a count, or from-to |
none | Connections one carries in its life. |
xhttp-max-request-times |
a count, or from-to |
600-900 |
Requests one carries in its life. |
xhttp-max-reusable-secs |
seconds, or from-to |
1800-3000 |
How long one takes new requests. |
xhttp-download |
server[:port] |
Download from another server (Xray's downloadSettings): the GET goes there, uploads stay here. Not with stream-one or HTTP/1.1. |
|
xhttp-download-path, xhttp-download-host |
the uploads' path; its address | The same for that server. | |
xhttp-download-tls |
true, false |
as the uploads | Its TLS. Set by any parameter below. |
xhttp-download-sni, xhttp-download-alpn, xhttp-download-skip-cert-verify, xhttp-download-fingerprint, xhttp-download-public-key, xhttp-download-short-id |
Its TLS and REALITY. xhttp-download-alpn=h3 downloads over HTTP/3. |
The last five are Xray's XMUX; the defaults are Xray's when none is
given. With TLS the requests go over HTTP/2, or over HTTP/1.1 when
alpn=http/1.1 — which carries packet-up alone; without TLS, over
HTTP/2 straight away, as the server takes it. In packet-up the server
hears that a connection is done only when it closes. Pipe keeps at most
16 connections (6 on iPhone and iPad) of 128 requests each. HTTP/3,
padding in Xray's obfuscation mode, a session or sequence number
elsewhere than the path, an upload method other than POST, and
downloads from another server (downloadSettings) are not supported
yet; an import that asks for them says so.
Multiplexing#
ss, vmess, vless and trojan can carry many connections on a few
to the server: sing-box's multiplex and mihomo's smux, which a
sing-box or mihomo server takes when its inbound enables multiplexing
(and still takes connections that are not multiplexed).
Many = vless, mux.example.net, 443, username=0233d11c-15a4-47d3-ade3-48ffca0ce119, tls=true, mux=h2mux, mux-padding=true
Few = trojan, mux.example.net, 443, password=secret, mux=smux, mux-max-connections=4, mux-min-streams=4
| Parameter | Values | Default | What it does |
|---|---|---|---|
mux |
h2mux, smux, yamux; true is h2mux |
off | How connections share one: HTTP/2, smux or yamux. |
mux-max-connections |
a count | none | Connections to spread over: another is opened while each has mux-min-streams and there are fewer than this. |
mux-min-streams |
a count | none | With mux-max-connections, what each connection carries before another is opened. |
mux-max-streams |
a count | none | What a connection carries before another is opened. Not with the two above. |
mux-padding |
true, false |
false |
Pad the first frames each way, as a server with padding requires. |
mux-only-tcp |
true, false |
false |
UDP goes the protocol's own way instead of on the shared connections. |
Without a count, a connection is shared once it is idle: each connection in use carries one at a time. Pipe keeps at most 32 shared connections (8 on iPhone and iPad) of 128 connections each; past that, the least busy one takes more. A shared connection idle for 30 seconds closes, and all do when the network changes. VLESS with Vision is not multiplexed. TCP Brutal, which a Linux client sets on its own socket, is not asked for.
Proxy chains#
underlying-proxy names a policy — a proxy or a group — that the
connection to the proxy's server goes through.
[Proxy]
Relay = trojan, relay.example.net, 443, password=secret
Exit = ss, exit.example.net, 8388, encrypt-method=aes-256-gcm, password=secret, underlying-proxy=Relay
- The server's name is handed to the underlying policy as it is, to be
looked up at its end.
ip-versiontherefore changes nothing on such a line, and a note says so. - UDP goes through it too. Shadowsocks' and a SOCKS5 relay's datagrams go as the underlying policy's datagrams; protocols that carry datagrams on a stream carry that stream through it; Hysteria 2 and TUIC send their QUIC packets as its datagrams, at the smallest packet size every path carries.
DIRECT— andCELLULAR,CELLULAR-ONLY,HYBRIDandNO-HYBRID, which go directly — is no chain.- A proxy whose underlying policy is not there, refuses everything, cannot
be used, or leads back to it (
A → B → A) cannot be used either. - A chain that comes back through a group — a group that picks the proxy going through it — fails that connection, and says so.
- A proxy's way out of the device is its
underlying policy's, with its own line's
interfaceandhybridbesides — except a tunnel's (wireguard), which reaches its peers its own way whatever its underlying policy says.
A whole group can take its proxies through a policy:
underlying-proxy on a group.
Leaving the device#
How a policy's connections leave the device — every proxy line, and
every line that renames direct:
| Parameter | Values | Default | What it does |
|---|---|---|---|
interface |
an interface name: en0, pdp_ip0 |
the default | Connections go out of that interface. Where it is not there, they fail. |
allow-other-interface |
true, false |
false |
With interface: where that interface is not there, connections go the default way instead of failing. |
hybrid |
on, off, auto (true, false) |
auto |
on: connect over Wi-Fi and cellular at once, the first to connect carrying the connection. off: never try cellular while on Wi-Fi. auto: as all-hybrid says. |
tfo |
true, false |
false |
Open TCP with Fast Open: the first bytes ride the SYN once the server has given the device a cookie. Used for a server with one address, where nothing is raced; whether the server was reached is known at the first write. |
tos |
0 to 255, or hexadecimal after 0x |
0 |
The TOS byte the packets are marked with. A value that is neither is warned about and read past. |
The built-in policies CELLULAR, CELLULAR-ONLY, HYBRID and
NO-HYBRID are DIRECT with one of these set:
| Policy | Is |
|---|---|
CELLULAR |
out of the cellular interface when there is one, the default way otherwise |
CELLULAR-ONLY |
out of the cellular interface alone |
HYBRID |
hybrid=on |
NO-HYBRID |
hybrid=off |
In [General], all-hybrid = true
connects every policy over Wi-Fi and cellular at once, but those that say
hybrid=off. wifi-assist = true tries cellular only when Wi-Fi has not
connected within a second, or has failed. Both work on Wi-Fi with
cellular there, and for connections: UDP goes the policy's one way.
[SSID Setting] can change this on some
networks.
- A proxy's way out is that of its connection to its server: Hysteria
2's and TUIC's QUIC sockets too, each port they hop to included. On
those, QUIC's own ECN marks take the place of
tos's. - Through an
underlying-proxy, it is the underlying proxy's. - WireGuard and
tailnetpolicies reach their peers their own way: on their lines these parameters are noted and read past. - On a network without IPv4 — an IPv6-only one, as some mobile carriers' are, which reaches IPv4 hosts through NAT64 — a server or destination given as an IPv4 address, or a name with IPv4 addresses alone, is dialled at the address the network's NAT64 gives it, the prefix asked of the network's own resolver. So are WireGuard's peers and DNS servers given by address; a tailnet's direct paths are not.
dns-follow-interfaceis not supported yet.
ip-version#
How a proxy's server name is looked up and dialled.
| Value | What it does |
|---|---|
dual (the default) |
Both families, raced a quarter of a second apart, the first to connect winning. |
v4-only, v6-only |
That family alone. |
prefer-v4, prefer-v6 |
That family first, and the other once three seconds have passed without a connection, or the first has failed. |
Any other value makes the proxy unusable. A server given as an address is
dialled as it is. On a wireguard or tailnet line, and on a line
with an underlying-proxy, ip-version changes nothing, and a note says
so. The lookup goes through the profile's DNS.
Tests#
A proxy can say how it is tested, in place of what the profile says.
| Parameter | Values | Default | What it does |
|---|---|---|---|
test-url |
a URL | proxy-test-url |
What the proxy is tested with. |
test-timeout |
seconds | the group's | How long its test may take. |
test-udp |
hostname@server |
proxy-test-udp |
Its UDP test: a lookup of hostname at the DNS server server, through the proxy. |
A value Pipe cannot read is warned about, and the profile's is used. Groups says how a test is made and timed.
UDP through a proxy#
Every protocol that can carry UDP carries it, unless its line says
udp-relay=false: then the proxy carries TCP alone — on ss, vmess,
vless, trojan, anytls, socks5, socks5-tls and snell — and its
UDP goes where udp-policy-not-supported-behaviour
sends UDP a policy cannot carry: refused (REJECT, the default) or out
directly. A group that picks it for UDP does the same. Hysteria 2, TUIC
and WireGuard carry UDP as they are made to, and say so with a note;
h2-connect and naive carry it only when their line asks
(udp-relay=true, udp-over-tcp=true).
QUIC through a proxy#
block-quic on a proxy says whether QUIC goes through it, where
block-quic in [General] is per-policy
(its default).
| Value | QUIC through this proxy |
|---|---|
on (true) |
is refused, so that apps fall back to TCP |
off (false) |
goes through |
auto (the default) |
goes through DIRECT and its kin, Hysteria 2, TUIC in its native relay mode without udp-over-stream, WireGuard and tailnet policies — when neither goes through an underlying-proxy; is refused through every other proxy |
Any other value is warned about, and auto is used.
Parameters that are read past#
| Parameter | Said as |
|---|---|
mptcp, dns-follow-interface, no-error-alert, always-use-connect; idle-timeout except on ssh; max-streams except on h2-connect; ecn except on hysteria2, tuic, tuic-v5 and masque |
a note: not supported yet, and read past |
tls13 |
a note: changes nothing |
udp-relay=false on hysteria2, tuic, tuic-v5, wireguard |
a note: the protocol carries UDP as it is made to |
A parameter many proxies share is said once for the whole profile: line 3: `mptcp` (on 40 proxies) is not supported yet and is ignored.
Not supported yet#
A line asking for one of these cannot be used, and says why:
- the type
trust-tunnel; - VLESS flows other than
xtls-rprx-vision; - inline ShadowTLS on types other than Shadowsocks and Snell, or with a separate UDP path; use explicit carrier and UDP policies instead;
- VMess without
vmess-aead=true; - a type Pipe does not know.
[Keystore]#
Certificates and keys, kept in one place and used by name: a proxy's
client-cert, and the
HTTPS decryption CA.
[Keystore]
office-cert = type=p12, base64=MIIKJwIBAzCCCe0GCSqGSIb3DQEHAaCC…, password=secret
| Parameter | Values | Default | What it does |
|---|---|---|---|
type |
p12, openssh-private-key |
p12 when there is a password, openssh-private-key otherwise |
What the item is. |
base64 |
base64 | required | The item's file, in base64. Line breaks and spaces in it are ignored. |
password |
text | none | The PKCS#12 file's password. |
- A
p12item is a PKCS#12 file. Pipe reads it as OpenSSL 3 encrypts one (PBKDF2 and AES), as OpenSSL 1 and the macOS keychain do (3DES and RC2), and unencrypted, and checks its MAC — PKCS#12's own, or PBMAC1 (openssl pkcs12 -pbmac1_pbkdf2) — so a wrong password is said to be one, on the line of the proxy that uses it. A password that is not ASCII opens the files OpenSSL 1.0 and LibreSSL (macOS'sopenssl) write with it too. - A file larger than 256 KiB, or asking for more than 600,000 rounds of key derivation, is not read. A profile's keys together may ask for a second's work of key derivation, hundreds of files as OpenSSL writes them; a key past that is not read.
- An
openssh-private-keyitem is ansshproxy'sprivate-key: the key file, base64 once more over the base64 already in it, as Surge has it (base64 -i id_ed25519). A key under a passphrase is the file as it is, encrypted; the proxy line has the passphrase (private-key-passphrase). - A line that is not
name = key=value, …, has nobase64, or has a parameter not in the table, is warned about.
[WireGuard <name>]#
The settings of a wireguard proxy.
[Proxy]
WARP = wireguard, section-name=WARP
[WireGuard WARP]
private-key = YNXtAzepDqRv9H52osJVDQnznT5AL11eVlnvHeYk+Xs=
self-ip = 172.16.0.2
self-ip-v6 = 2606:4700:110::2
dns-server = 1.1.1.1, 2606:4700:4700::1111
mtu = 1280
peer = (public-key = bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=, allowed-ips = "0.0.0.0/0, ::/0", endpoint = engage.cloudflareclient.com:2408, keepalive = 25)
| Key | Values | Default | What it does |
|---|---|---|---|
private-key |
base64 | required | This device's private key. |
self-ip |
an IPv4 address | — | The tunnel's own IPv4 address. self-ip or self-ip-v6 is required. |
self-ip-v6 |
an IPv6 address | — | The tunnel's own IPv6 address. |
dns-server |
a list of addresses | the profile's DNS | Servers for the names this proxy dials, asked through the tunnel. system in the list adds the profile's DNS behind them. |
prefer-ipv6 |
true, false |
false |
A name with addresses of both families is dialled over IPv6. |
claim-routes |
true, false |
false |
A WireGuard network: the peers' private allowed IPs go through this tunnel ahead of the rules. |
dns-domains |
a list of domains | — | Names under these domains are asked of dns-server through the tunnel, by Pipe's DNS too. Needs dns-server. |
mtu |
576 to 9000 | 1408 | The largest packet the tunnel carries. |
peer |
(…), see below |
required | A peer. Several peers are separated by commas, or written on several peer lines. |
jc, jmin, jmax, s1–s4, h1–h4, i1–i5 |
AmneziaWG's | — | The server speaks AmneziaWG. |
A peer's fields:
| Field | Values | What it does |
|---|---|---|
public-key |
base64 | The peer's public key. Required. |
allowed-ips |
address ranges, quoted when there are several | What goes to this peer. Required. |
endpoint |
host:port, [v6-address]:port |
Where the peer is. Required. |
preshared-key |
base64 | A pre-shared key. |
keepalive |
seconds | A keepalive packet every so many seconds; 0 sends none. |
client-id |
three bytes: 83/12/235, 530ceb (hexadecimal), or four base64 characters |
The reserved bytes some servers identify a client by. |
A section with a key not in the table has it noted and read past. A section without a private key, an address or a peer, or with a field Pipe cannot read, makes its proxy unusable, and says why.
AmneziaWG#
AmneziaWG is WireGuard whose packets do not look like WireGuard's, for
networks that block it. Its parameters go in the [WireGuard] section by
AmneziaWG's own names, as its app's config files write them, and must be
the server's:
[Proxy]
Hidden = wireguard, section-name=Hidden
[WireGuard Hidden]
private-key = YNXtAzepDqRv9H52osJVDQnznT5AL11eVlnvHeYk+Xs=
self-ip = 10.8.1.2
jc = 4
jmin = 40
jmax = 70
s1 = 15
s2 = 68
h1 = 1020325451-1020325500
h2 = 3288052141
h3 = 1766607858
h4 = 2235336242
i1 = <b 0xc700000001><rc 8><t><r 32>
peer = (public-key = bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=, allowed-ips = "0.0.0.0/0, ::/0", endpoint = awg.example.com:51820)
| Key | Values | What it does |
|---|---|---|
jc |
0 to 128 | Junk packets sent before each handshake. |
jmin, jmax |
0 to 1280 bytes | The least and most bytes of a junk packet. |
s1, s2, s3, s4 |
0 to 1024 bytes | Random bytes before the handshake's first message, its answer, a cookie reply and every data packet. |
h1, h2, h3, h4 |
a number, or a range 100-200 |
Each message's header, picked from its range; the four must not overlap. Without them, WireGuard's 1, 2, 3 and 4. |
i1 to i5 |
tags: <b 0x…> bytes, <r n> random bytes, <rc n> letters, <rd n> digits, <t> the time |
Packets sent before each handshake, after the junk's, as they say. |
These are AmneziaWG 2.0's parameters, which take 1.0's too. AmneziaWG
1.5's j1–j3 and itime, and 3.0's header protection, are not
supported: a section with them makes its proxy unusable, and says why.
A WireGuard config from the AmneziaVPN app, mihomo's
amnezia-wg-option and Amnezia's sing-box (awg endpoints) import
with their parameters.
WireGuard networks#
A WireGuard tunnel to an office or a home network is a network to
reach, not a way to the internet. With claim-routes, what its peers'
allowed IPs hold of the private ranges (10.0.0.0/8, 172.16.0.0/12,
192.168.0.0/16, 100.64.0.0/10, fc00::/7) goes through it ahead
of the rules, in every mode — never a default route, nor a public range,
which stay the rules' to choose. With dns-domains, names under them
are asked of its DNS servers through it, and answered with their real
addresses.
[Proxy]
Office = wireguard, section-name=Office
[WireGuard Office]
private-key = YNXtAzepDqRv9H52osJVDQnznT5AL11eVlnvHeYk+Xs=
self-ip = 10.8.0.2
dns-server = 10.8.0.1
claim-routes = true
dns-domains = corp.example.com
peer = (public-key = bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=, allowed-ips = "10.8.0.0/24, 192.168.50.0/24", endpoint = vpn.example.com:51820)
- A tailnet comes first where both claim an address.
- A server another proxy names on the network — a proxy reached at
10.8.0.5— is dialled through it, with nothing to set. - Importing a WireGuard file whose allowed IPs are all private sets
claim-routes, anddns-domainsto the search domains of itsDNSline. A full-tunnel file (0.0.0.0/0) stays a proxy that rules choose.
Tailnets are accounts in the app, beside every profile: up to three, in an order you set, each on or off.
- You sign in in the app — with Tailscale, with an auth key, or at another control server such as Headscale — before the VPN is ever on. The keys are kept in the device's keychain, never in a profile, a sync or an export.
- While an account is on, its devices' addresses, its MagicDNS names, its split-DNS domains and, with Use subnet routes, the subnets its devices advertise go to it ahead of the profile's rules.
- Its settings — Use Tailscale DNS, Use subnet routes, Allow local network access, Block incoming connections, Use relays only (no direct connections: everything through Tailscale's relays, which your rules can send through a proxy), this device's name — are the account's, the same whatever profile runs.
- The exit node for the whole internet is chosen in the app; a
tailnetpolicy'sexit-nodesends only what rules give it there. Mullvad's exit nodes, where the tailnet has them, are exit nodes like any other, listed by country and city. - On a tailnet with Tailnet Lock, only the devices its signing keys signed are reached; the others are listed as not trusted. This device, until a signing device signs it, reaches nothing of the tailnet: the app shows the command that signs it in.
Tunnels through another policy#
On a wireguard line, underlying-proxy names a policy — a proxy or a
group — that the tunnel's own datagrams go through. DIRECT is none. A
chain that leads back to where it started fails the connection, and says
so.
The proxy pool#
The proxies you import go into the pool, not into a profile: from a QR code, a subscription link, share links, or another app's configuration (Surge, Clash, mihomo, sing-box, a WireGuard file).
A profile takes the pool in as a policy-path
list named pool.list. The default profile's group does:
[Proxy Group]
Proxy = select, policy-path=pool.list
- The pool is written out as
[Proxy]lines — WireGuard proxies with their sections, SSH proxies with their keys'[Keystore]items — each time a profile is compiled. A subscription refreshed in the pool reaches every profile that uses it. - A Tailscale node an import holds is not a proxy of the pool: the tailnet is offered for sign-in instead (Tailnets).
- A proxy's name is its identity: rules, groups and the pick a
selectgroup remembers refer to it. Names are unique across the whole pool. A name that is taken already gets a number —Server 1 2— and so does a proxy named after a built-in policy (REJECT 2). - A subscription is known by its URL. Importing it again, or refreshing it, replaces what it brought last time, keeps its place in the pool, and gives its proxies back their names.
- A proxy added on its own that is the same as one in the pool is not added again.
- A proxy that goes through another of the same import follows it to the name it was given.
- A proxy that cannot be written as a profile line is left out of
pool.list, and the app says which, and why. - A Surge profile's
[Proxy]lines are read exactly as a profile reads them: a line the profile would refuse is not imported, with its reason. A line written for some systems only (#!MACOS-ONLY,#!REQUIREMENT …) and a proxy with a TLS client certificate (client-cert, which lives in the profile's[Keystore]) are not imported either. An SSH proxy's key comes from the profile's[Keystore]into the pool with the proxy. - An SSH key in the pool is kept in the proxy, with its passphrase, as
a password is;
pool.listwrites it as a[Keystore]item, once however many proxies use it. A list's[Keystore]items are its own: one whose name the profile has for another key is taken in under a name of its own.