Outbound proxy¶
Every TCP connection the firmware initiates — Mempool/Kraken WebSockets, the Coinbase / CoinGecko / Bitaxe HTTP polls, mining-pool API calls, Nostr relay sockets, OTA firmware downloads, mining-pool logo fetches — can be routed through an HTTP CONNECT or SOCKS proxy. There is no per-source override; once enabled, the proxy applies to all outbound traffic.
The intended use cases are: running the device on a network that blocks direct egress (corporate / hotel / Tor-only setups), tunnelling through a known-good upstream when the local DNS / TLS path is unreliable, or funnelling traffic through a logging / inspection box for debugging.
Supported types¶
proxyType |
Protocol | Remote DNS | Auth | RFC |
|---|---|---|---|---|
0 |
none (direct) | n/a | n/a | — |
1 |
HTTP CONNECT | yes | Basic | RFC 7231 |
2 |
SOCKS4 | no | none | — |
3 |
SOCKS4a | yes | none | — |
4 |
SOCKS5 | yes | user/pass | RFC 1928 / 1929 |
"Remote DNS" means the proxy resolves the destination hostname. Plain
SOCKS4 forces the device to resolve first and only forwards an IPv4
literal to the proxy — pick SOCKS4a or SOCKS5 if you want the proxy to
do the DNS work (typical for Tor SOCKS, where the device cannot resolve
.onion itself).
Auth is only meaningful on SOCKS5 and HTTP CONNECT. SOCKS4/4a have no
authentication frame in the protocol; proxyUser and proxyPass are
ignored when proxyType is 2 or 3. If proxyUser is empty on a SOCKS5
connection the firmware sends method 0x00 (no-auth); a non-empty
username triggers 0x02 (user/pass, RFC 1929).
TLS terminates on the device, not on the proxy. The proxy sees only an opaque TLS stream — it doesn't decrypt or inspect the application-layer data. The device verifies the destination certificate against the IDF certificate bundle exactly the same as a direct connection.
Configuration fields¶
All seven fields are persisted under the settings NVS namespace and
exposed via GET/PATCH /api/settings. None are reboot-only — a PATCH
takes effect on the next outbound connection, and existing WebSocket
sessions reconnect through the new path automatically.
| Field | Type | Bounds | Default |
|---|---|---|---|
proxyEnabled |
bool | — | false |
proxyType |
u8 | 0..4 | 0 (none) |
proxyHost |
string | non-empty when enabled | (empty) |
proxyPort |
u16 | 1..65535 | 1080 |
proxyUser |
string | — | (empty) |
proxyPass |
string | — | (empty) |
proxyBypass |
string | comma-separated globs | *.local,192.168.*,10.*,127.0.0.1 |
proxyPass is suppressed in GET /api/settings responses (mirrors
httpAuthPass) — proxyPassSet: true|false indicates whether a password
is stored without leaking the value. Set with PATCH; clear by PATCHing
an empty string.
Example PATCH (SOCKS5 with auth):
curl -X PATCH http://<ip>/api/settings \
-H 'Content-Type: application/json' \
-d '{
"proxyEnabled": true,
"proxyType": 4,
"proxyHost": "192.168.1.10",
"proxyPort": 1080,
"proxyUser": "alice",
"proxyPass": "secret",
"proxyBypass": "*.local,192.168.*"
}'
Bypass list¶
proxyBypass is a comma-separated list of host patterns that are
contacted directly, skipping the proxy. Matching is case-insensitive
and runs against the destination hostname (not the resolved IP), in
this order:
| Pattern | Matches |
|---|---|
example.com |
exact host (example.com only, not api.example.com) |
*.local |
leading-star — any host ending in .local |
192.168.* |
trailing-star — any host starting with 192.168. |
* |
match all (effectively disables the proxy) |
Whitespace around commas is trimmed. The default
(*.local,192.168.*,10.*,127.0.0.1) keeps LAN / mDNS / loopback traffic
off the proxy path so a misconfigured public proxy can't black-hole
local services.
There is no negation syntax — bypass is allow-only. If you want "everything via the proxy except X", list X. If you want "only X via the proxy", that is not expressible today; disable the proxy globally and enable it from a network that already routes through the proxy upstream.
Known limitations¶
- No UDP: the proxy is TCP-only. NTP runs over UDP and is unaffected by these settings — it always goes direct. If your network blocks outbound UDP/123, configure NTP to use a server reachable from inside the network, or rely on the on-device RTC drift between successful syncs.
- No GSSAPI / Kerberos / NTLM: HTTP CONNECT only sends RFC 7617
Basic auth (base64 of
user:pass). Enterprise proxies that require integrated Windows auth aren't supported. - No PAC / WPAD: there is no proxy auto-config support. Set host and port explicitly.
- No SOCKS5 GSSAPI auth: the SOCKS5 implementation negotiates only
method
0x00(no-auth) and0x02(user/pass). Method0x01(GSSAPI) is not implemented — proxies that require it will reject the greeting. - No upstream chaining: a single hop. There is no built-in way to
proxy-through-a-proxy from the device side; if you need that, do it
on the proxy host (e.g. 3proxy
parentdirective). - Boot order: settings are loaded before the network stack, so a freshly written proxy config takes effect immediately on the next connection attempt. The first connection after a reboot still has to wait for WiFi association — measured floor is ~15 s on Rev A/B from cold boot, with the proxy adding under 100 ms of handshake overhead after that.
Troubleshooting¶
Confirm the device is actually using the proxy:
connectionStatus.price, .V2, .blocks, .nostr flip to true as
each upstream WebSocket parses its first frame. If they stay false for
more than ~30 s after enabling the proxy, the handshake is failing —
check the proxy server's log for SOCKS / HTTP CONNECT errors.
The serial console (idf.py monitor) prints one line per proxy event
under tag proxy_socket_io. Common failures:
connect(host:0) failed: 113— port resolution lost between the WebSocket client and the transport. This was a real bug on early builds and is fixed; if you see it on a current image, please file an issue.proxy auth required— the upstream returned407 Proxy Authentication Required(HTTP CONNECT) or rejected the SOCKS5 method list. CheckproxyUser/proxyPass.SOCKS5 reply 0x05— connection refused by the proxy's upstream policy. The destination is allowed by the proxy ACL but the upstream refused; not a device-side problem.
Local test rig¶
A docker-compose rig at tools/proxy_test/
in the firmware repo brings up a 3proxy container with four listeners
(SOCKS anon / SOCKS authed / HTTP CONNECT anon / HTTP CONNECT authed).
Useful for verifying device behaviour without hammering a public proxy:
The README inside that directory documents listener ports, smoke-test
curls, and how to expose 0.0.0.0 for hardware-in-the-loop testing
(the default binds to 127.0.0.1 so the listeners aren't reachable from
the LAN).