Anonymous transport (Tor / VPN)¶
The agent makes one outbound connection to the server and carries everything — the control channel, every tunnel, every command result — over it. Anything that carries that single TCP connection therefore carries the whole agent, so routing it through Tor or a VPN needs no special protocol support: it needs a proxy and a policy.
The transports key is that policy. It is an ordered list of egress
paths. The agent tries them strictly in order and keeps the first that
connects.
When to use it¶
- The agent runs on a host whose address should not be visible to the network between it and the server.
- The server is published as a Tor onion service and has no clearnet address at all.
- The agent sits behind a VPN and must not fall back to the local connection when the VPN drops — or must, but visibly.
If none of these apply, leave it unset. An agent with no transports
behaves exactly as it always has.
The list is the policy¶
There is no allow_fallback switch, and nothing is ever appended to the
list for you. The agent dials the open internet if and only if you wrote
the word direct into the list yourself.
[client]
# Tor, falling back to the clear internet if the tor daemon is down.
transports = ["socks5h://127.0.0.1:9050", "direct"]
[client]
# A VPN, then Tor, and never the clear internet. If both are down the
# agent retries them forever and does not connect.
transports = ["socks5h://gluetun:1080", "socks5h://127.0.0.1:9050"]
Removing direct is how you fail closed. An agent that cannot reach its
server is a recoverable operational problem; an agent that quietly
revealed the address you were hiding is not.
Accepted entries:
| Entry | Meaning |
|---|---|
direct |
Dial the server with no proxy. Must be the last entry. |
socks5h://host:port |
SOCKS5. The proxy resolves the hostname, which is what makes .onion work. |
socks://, socks5:// |
Accepted spellings of the same thing. |
http://host:port |
HTTP CONNECT. |
Credentials may be embedded (socks5h://user:pass@host:1080). They are
masked in every log line and are never sent to the server.
Anything else — an unknown scheme, a typo, https:// — stops the agent
at startup with an error naming the entry and its position. An unusable
entry is never skipped, because skipping it would silently shorten the
policy you wrote.
Order of attempts¶
Entries are tried transport-major: every server over the first transport before any server over the second.
transports = ["socks5h://127.0.0.1:9050", "direct"]
server = "primary.example.com:80"
fallback_servers = ["secondary.example.com:80"]
gives the order: primary over Tor, secondary over Tor, primary direct, secondary direct. Server redundancy never buys itself with anonymity — a dead primary alone cannot drop you to the clear internet while the secondary is still reachable over Tor.
transport_dial_timeout (default 45s) bounds each attempt. A whole
sweep therefore costs at most
len(transports) × (1 + len(fallback_servers)) × transport_dial_timeout;
keep that inside your watchdog window if you run one.
After a sweep fails the agent backs off exactly as before (5s to 10m)
and sweeps again. server_switchback_interval (default 2m) retries
the preferred combination — first transport, main server — while
connected to any other, so a transient Tor or VPN outage heals itself.
Set it to 0 to stay put.
What advances the chain, and what does not¶
Only a failure of the path advances to the next entry: a refused connection, a SOCKS or CONNECT failure, a timeout, a broken handshake.
A credential rejection does not. The agent uses one credential for
every server and every transport, so a rejection at one entry is a
rejection at all of them, and walking the list could only end in dialing
the clear internet because auth has a typo in it. The sweep stops and
the agent backs off instead.
Running over Tor¶
Run a tor daemon — the system package or a sidecar container — and
point the agent at its SOCKS port:
[client]
server = "yourserveraddress.onion:80"
transports = ["socks5h://127.0.0.1:9050"]
fingerprint = "SHA256:..."
Two constraints are enforced for you:
- A
.onionserver is never dialed directly. Handing a.onionname to the host resolver would disclose it to whoever runs that resolver, so such a candidate is skipped rather than attempted. - A
.onionserver requires a pinnedfingerprint. No CA issues certificates for.onionnames, so the connection isws://with no TLS and the SSH host-key pin is the only thing authenticating the server. The agent refuses to start without it.
Publish the onion service with a standard torrc mapping in front of
the server's existing listener — the server needs no configuration
change and no code change:
HiddenServiceDir /var/lib/tor/proxiport/
HiddenServicePort 80 127.0.0.1:8080
Add the onion to the server's url list so it is advertised alongside
the clearnet address.
Running over a VPN¶
There are two shapes, and they are not equivalent.
A proxy the VPN exposes (for example gluetun with its SOCKS5 or HTTP proxy enabled) is a normal chain entry and can fail over:
transports = ["socks5h://gluetun:1080", "socks5h://127.0.0.1:9050"]
A network-namespace VPN — running the agent inside the VPN
container's namespace, or behind a host WireGuard interface — is
invisible to the agent. Every dial it makes is already inside the
tunnel, so the correct configuration is simply direct (or no
transports at all). This is a robust and common deployment, but the
agent cannot fail over out of it, because the namespace is fixed
before the process starts. If you need failover, use a proxy the VPN
exposes.
Note that gluetun's Shadowsocks listener is not a SOCKS5 proxy despite the name — it speaks the Shadowsocks protocol and this agent cannot use it. Use gluetun's SOCKS5 or HTTP proxy.
Installing with the pairing script¶
The one-shot installer the pairing service renders (see Install) can write the policy for you. Two switches build the chain, in the order given, and the installer follows the same chain for its own connection test and downloads:
| Switch | Effect |
|---|---|
-o |
Reach the server through the tor daemon on the agent host, socks5h://127.0.0.1:9050. Shorthand for -e socks5h://127.0.0.1:9050. |
-e <entry> |
Add one entry: a proxy URL (socks5h://host:port, http://host:port) or direct. Repeatable; direct must be last. |
# Tor, fail closed.
curl --proxy socks5h://127.0.0.1:9050 https://pairing.proxiport.net/<code> -o proxiport-installer.sh
sudo sh proxiport-installer.sh -o
# A VPN sidecar's proxy, then Tor, then — visibly — the open internet.
sudo sh proxiport-installer.sh -e socks5h://gluetun:1080 -o -e direct
Alongside transports the installer sets require_fingerprint = true
and updates_interval = 0, comments out an ip_api_url it finds in an
existing config, skips the geolocation lookup that would otherwise label
the new client with a city, and waits up to 90 s for the first
"Connected to" line before it judges the agent log, since a Tor circuit
takes a moment. A server address that ends in .onion implies -o.
In the web UI, the dialog that creates a client credential offers the
same switches as toggles: connect through Tor, a proxy to try before
Tor, and whether to allow the open internet as the last resort. With a
proxy chosen, the line that fetches the installer gets --proxy as
well, so the pairing request stays off the open internet too.
The Windows installer has no equivalent yet; set transports in
proxiport.conf by hand there.
What this does and does not cover¶
The chain covers the agent's connection to the server. It is not a whole-host anonymiser, and the following are outside it:
- Package-manager refreshes.
updates_interval(default 4h) shells out toapt-get update,dnf check-update,zypper refreshor the Windows Update client. These are subprocesses; no setting in this file can route them. Setupdates_interval = 0on an agent that must not originate direct traffic. ip_api_url. The external-IP lookup builds its own connection and cannot traverse the chain. The agent refuses to start with both set.- Server-issued commands and scripts. If
[remote-commands]or[remote-scripts]is enabled, the server can run programs on the host, and those programs make their own network calls. The server is inside your trust boundary; the chain hides the agent from the network in between, not from the server. - Tunnel traffic to local targets. A tunnel to
127.0.0.1:5432or a LAN host is dialed on the local stack, deliberately — reaching those is what the agent is for.
Hardening checklist¶
- [ ]
directomitted fromtransportsunless a clear-internet fallback is genuinely acceptable. - [ ]
fingerprintpinned, andrequire_fingerprint = true. - [ ]
ip_api_urlunset. - [ ]
updates_interval = 0. - [ ]
[remote-commands]and[remote-scripts]disabled unless needed. - [ ] Alerting on the literal string
ANONYMITY DOWNGRADE:in the agent log. That line is emitted on every connection that usesdirectafter an earlier transport failed — a successful clearnet connection on an agent configured for Tor is itself the incident. - [ ]
transport_dial_timeout × entries × serversinside your watchdog window.
See also: IP-address determination for
the external-IP lookup this feature disables, and
client authentication for the fingerprint
pin that a .onion endpoint depends on.