Watching for a tor process and then starting the VPN is the most common way to build this stack wrong. The daemon appears long before it can carry a client stream, and a fixed sleep is a guess dressed as a dependency. The gate has to be the state the next component actually needs. Walk the four steps below in order, and treat each one as a separate condition rather than as a delay to tune.

Step 1: pick the transport you actually have

Start with the protocol, because the rest of the design depends on it. Tor's SOCKS interface does not implement UDP ASSOCIATE, so anything carried over it has to be a TCP stream. An OpenVPN profile that uses UDP will not work through this path no matter how the supervisor is written. Switch the profile to TCP and confirm the remote peer accepts it before you touch startup logic.

Then confirm that your client can reach a SOCKS proxy at all. OpenVPN 2.6 documents a socks-proxy option for TCP profiles, and different clients expose different proxy support. A launcher that silently ignores the proxy option will connect directly and look like it works, which is worse than failing.

Write the chosen transport into the runbook, because every later check depends on it.

Step 2: read the bootstrap phase, not the process list

The Tor control protocol answers GETINFO status/bootstrap-phase with the most recent bootstrap state, including a progress percentage and a summary tag. That answer is useful precisely because a controller may attach after the daemon has already finished bootstrapping. A supervisor that only listens for events will miss the case where Tor was ready before the supervisor started.

Authenticate the control connection properly. A control port is an administrative interface, and anyone who can talk to it can reconfigure the daemon. Keep it on localhost, keep cookie or password authentication enabled, and do not remove either one to make a startup script simpler.

Read the phase once at startup, then subscribe to progress events for the live view.

printf 'AUTHENTICATE\r\nGETINFO status/bootstrap-phase\r\nQUIT\r\n' | nc 127.0.0.1 9051
ss -ltn | grep -E '9050|9051'
tor --verify-config -f /etc/tor/torrc

A response with progress 100 and the done tag means the daemon finished its own startup. It does not yet mean a usable circuit exists for your destination.

Step 3: wait for a client circuit event

The Tor event protocol defines CIRCUIT_ESTABLISHED, which reports that the client has a circuit it can use. That is the readiness signal a carrier gate wants, because it is closer to the capability you need than a percentage is. Subscribe to the event and record the transition, so the log shows when the carrier became usable rather than when the process appeared.

Treat the event as permission to try, not as a guarantee about any particular destination. An exit policy can still refuse the remote peer, and the VPN's own handshake can still fail for reasons that have nothing to do with Tor.

Keep the two conditions separate in code. A single boolean named ready will collapse them and make the next incident unreadable.

Step 4: gate the VPN on its own handshake

The VPN has its own readiness condition, and it is the only one that proves the tunnel came up. Wait for the client to report a connected state with an assigned address, then check that the proxy requirement stayed in force. A retry loop that drops the socks-proxy option after one failure will connect directly and quietly defeat the whole design.

Decide what happens when the peer is unreachable before you need to know. A bounded retry with a visible failure is easier to operate than an infinite loop, and it gives the monitoring system something to alert on. The same decision applies to the carrier: if Tor restarts, the VPN has to be torn down and re-gated rather than left running on a dead circuit.

Finally, keep the application's own policy in place while the VPN is down. A supervisor that waits correctly does nothing to prevent an unrelated process from sending traffic directly.

Check the four transitions you must survive

Four cases cover the design: a cold start where Tor comes up first, a supervisor that attaches to an already-ready Tor, an unavailable VPN peer, and loss of the carrier while the tunnel is up. Each one has a different expected transition, and each one produces a different log line if the gate is written properly.

Run them deliberately, one at a time, and read the state changes rather than the process list.

journalctl -u tor@default -n 30 --no-pager
systemctl status [email protected]
curl -sS --socks5-hostname 127.0.0.1:9050 https://check.torproject.org/api/ip

When the carrier drops, the VPN should follow it down within one retry interval rather than lingering with a stale tunnel. When Tor returns, the gate should reopen only after the circuit event, not after the process start. Log which condition was missing at each failure, and the next person on call will not have to reconstruct the ordering from timestamps.

ဆက်စပ်ဆောင်းပါးများ