On macOS 27.0.1 (26A434), a SOCKS5 proxy is applied to TCP connections but never to QUIC. This happens whether the proxy comes from a PAC file in the system proxy settings or from an explicit ProxyConfiguration. The system SOCKS5 client only ever sends CONNECT; it never sends UDP ASSOCIATE (RFC 1928, CMD 0x03), so it has no way to carry UDP. URLSession handles this by staying on TCP through the proxy. A QUIC NWConnection instead connects directly to the origin, as if no proxy were configured.
Setup. The system proxy settings use a PAC URL (ProxyAutoConfigURLString in scutil --proxy; in my test it was installed through NEProxySettings.proxyAutoConfigurationURL). For the test host, a public site that serves HTTP/3, the PAC returns SOCKS5 127.0.0.1:<port>. For the explicit cases I used a local SOCKS5 server that logs every command it receives.
With the system PAC, default parameters, no explicit proxy:
URLSession, assumesHTTP3Capable = true: goes through the proxy over TCP (transaction metrics: h2, isProxyConnection == true, remote address 127.0.0.1:<port>). HTTP/3 isn't used, which is consistent: the proxy can't carry it.
NWConnection, TLS: currentPath.remoteEndpoint is 127.0.0.1:<port>. The PAC is honored.
NWConnection, QUIC (ALPN h3): .ready in about 170 ms, and currentPath.remoteEndpoint is the origin's own address. The PAC is ignored.
With an explicit proxy, set via NWParameters.PrivacyContext.proxyConfigurations:
QUIC with a working SOCKS5 server: .ready, and the server receives nothing — no CONNECT, no UDP ASSOCIATE.
QUIC with the proxy pointing at a port nothing listens on: still .ready, still connected to the origin's own address.
TLS with that same dead proxy: stays in .waiting with ECONNREFUSED. The proxy is honored for TCP.
At no point did the SOCKS5 server receive a UDP ASSOCIATE request.
Cases 3–5 are the concerning part. An app that configures a proxy, or a user or administrator who installs a PAC, reasonably expects traffic to go through the proxy or fail. Instead, QUIC traffic goes straight to the origin, which then sees the device's own address, and nothing in the API signals that the proxy was skipped.
Minimal snippet:
import Network
let host: NWEndpoint.Host = "YOUR_HTTP3_HOST" // any public host that serves HTTP/3
let parameters = NWParameters(quic: NWProtocolQUIC.Options(alpn: ["h3"]))
// Remove these four lines to test with the system PAC instead.
let privacy = NWParameters.PrivacyContext(description: "socks5-quic")
privacy.proxyConfigurations = [
ProxyConfiguration(socksv5Proxy: .hostPort(host: "127.0.0.1", port: 1099)) // nothing listens on 1099
]
parameters.setPrivacyContext(privacy)
let connection = NWConnection(host: host, port: 443, using: parameters)
connection.stateUpdateHandler = { state in
if case .ready = state {
// Prints the origin's own address. With NWParameters.tls instead of QUIC,
// the same proxy configuration leaves the connection in .waiting (ECONNREFUSED).
print(connection.currentPath?.remoteEndpoint as Any)
}
}
connection.start(queue: .main)
I filed this as FB24760235 on 09/13/2026 and haven't had a response, so I'd like to ask here:
Is it intended that a QUIC NWConnection ignores both the system PAC and proxyConfigurations? If so, how can an app find out that a connection went direct? And shouldn't a connection that can't honor the configured proxy fail rather than bypass it?
Is UDP ASSOCIATE support planned for the system SOCKS5 client, so that QUIC and other UDP traffic can follow a SOCKS5 proxy the way TCP does?
Thanks!