ProxyChains-NG does not create a tunnel. It preloads a library that replaces network-related libc calls inside the one program it launches, so the boundary is a call in a process, not a device-wide route. That distinction decides what a successful test actually proves. A working curl through the chain confirms one hooked call, not that name resolution, UDP or every child process followed the same path. The runbook below verifies coverage in three steps, then lists what the hook cannot reach.

How the preload reaches a connection

At startup the launcher puts libproxychains into LD_PRELOAD and then executes the target program. The library resolves the real libc symbols and substitutes its own versions of functions such as connect and getaddrinfo. Calls that pass through those symbols are rewritten to the SOCKS or HTTP proxy from the configuration file. Calls that take another route never meet the hook, which is why the guarantee is per call and not per machine.

Only dynamically linked programs participate, and setuid binaries under secure execution ignore the preload entirely. Children inherit the environment, but only while the launcher keeps it in place.

Confirm the library is loaded in that process

Start the request and inspect the live process rather than the shell that launched it. The maps file shows every shared object currently mapped into the process.

proxychains4 -q curl -sS --max-time 8 https://example.com/ -o /dev/null &
pgrep -a curl
grep libproxychains /proc/$(pgrep -n curl)/maps

A match confirms the hooked library is present, which is necessary but not sufficient. An empty result usually means you inspected a different process, for example a browser that handed the URL to an instance started before the launcher. Check that the binary is dynamically linked with ldd before chasing anything else, because a static build cannot load the preload at all. Then read the proxy list in the config the launcher actually used, since a stale path or a closed port produces a working hook with no working chain.

Trace the connections that leave the process

Presence of the library does not prove each call was rewritten, so follow the system calls. Run one request under the tracer and read where the sockets actually went.

strace -f -e trace=connect -o /tmp/pc.trace proxychains4 -q curl -sS https://example.com/ -o /dev/null
grep -o "sin_port=htons([0-9]*)" /tmp/pc.trace | sort -u

The trace should show connections to your proxy endpoint only, with the port matching the entry in the config file. Destination addresses for the target site appearing directly in the trace mean that call bypassed the hook, which is the finding you were looking for. Compare the port numbers with the proxy list instead of trusting the file name, because a leftover configuration from another project is a common source of silent direct access. Keep the trace for the acceptance record; it takes seconds and settles arguments about coverage.

Decide what the hook cannot cover

The proxychains-ng project limits redirection to TCP and warns that scripts, background processes and loaded modules can behave differently from the main program. UDP and ICMP sit outside the mechanism, so anything relying on them needs its own supported path. DNS follows the proxy_dns setting and the application's own resolver behaviour, which means one successful HTTPS request tells you nothing about name resolution. Keep certificate validation where it belongs as well: reaching a server through a proxy is not a reason to disable identity checks.

If direct fallback must be impossible, use an enforcement boundary that controls the traffic itself, such as a network namespace, firewall rules or a VPN, and keep it separate from the library hook. The hook is a compatibility tool for a named program, not a kill switch. Record the program, the exact command, the proxy port seen in the trace and whether DNS was resolved remotely; those four facts are the difference between a verified scope and a hopeful one.

Barruulee wal qabatan