Ansible workspace for creating a single-user Hetzner Cloud VM and routing normal outbound traffic through a user-provided SOCKS5 or HTTP proxy.
The default target is a small personal development box:
- Hetzner Cloud Debian 12 VM
- default server type:
cx22(2 vCPU, 4 GB RAM) - root SSH login with the user's public key
- one sudo-capable Linux user with the same public key
- SSH-only public ingress with UFW and fail2ban
- optional developer tooling preinstall
- transparent outbound proxy with sing-box TUN
There are two different public IPs:
[laptop] -> ssh -> [Hetzner VM IPv4]
|
+-> outbound traffic -> [proxy provider] -> [proxy egress IP]
Hetzner VM IPv4: created automatically by Hetzner and used for SSH/bootstrap.proxy_expected_egress_ip: fixed outbound IP from the proxy provider.
This project does not buy proxy IPs. First version expects you to provide a working SOCKS5 or HTTP proxy endpoint.
Install dependencies:
cd /path/to/hetzner-proxy-devbox
make installCreate host vars:
cp inventory/host_vars/example-devbox.yml.example inventory/host_vars/example-devbox.ymlEdit inventory/host_vars/example-devbox.yml:
hcloud_server_name: my-proxy-devbox
hcloud_location: ash
hcloud_server_type: cx22
devbox_username: alice
devbox_ssh_public_key: "ssh-ed25519 AAAA... alice@example"
proxy_expected_egress_ip: "203.0.113.10"
proxy_url: "socks5://USER:PASS@proxy.example.com:1080"For a proxy URL like:
socks5://USER:PASS@PROXY_HOST:1080
you can use it directly:
proxy_url: "socks5://USER:PASS@PROXY_HOST:1080"
proxy_expected_egress_ip: "<provider fixed egress IP>"Advanced split-field equivalent:
proxy_mode: socks5
proxy_host: PROXY_HOST
proxy_port: 1080
proxy_username: USER
proxy_password: PASS
proxy_expected_egress_ip: "<provider fixed egress IP>"Run:
export HETZNER_TOKEN="<your Hetzner API token>"
make syntax
make apply HOST=example-devboxBefore transparent routing is enabled, Ansible runs:
test-transparent-proxy-endpoint /etc/sing-box/transparent-proxy.env --expected-ip <proxy_expected_egress_ip>That command uses the proxy endpoint directly and checks:
observed egress IP == proxy_expected_egress_ip
If the proxy endpoint is wrong, credentials fail, or the egress IP does not match, sing-box transparent routing is not enabled.
On a configured VM:
verify-global-proxy
verify-transparent-proxy
check-dns-proxy
sudo update-transparent-proxy-endpoint \
--mode socks5 \
--host <proxy-host> \
--port <proxy-port> \
--expected-ip <proxy-egress-ip> \
--username <proxy-user> \
--password <proxy-password>
sudo rollback-transparent-proxySSH management traffic on TCP/22 bypasses the TUN/proxy path so a broken proxy does not normally cut off SSH access.
Run this after bootstrap:
verify-global-proxyverify-global-proxy is the strict compatibility command from the original
runbook. It points to the same verifier as:
verify-transparent-proxyAny failed required check exits non-zero. It checks:
sing-box.serviceis activesing-box.serviceis enabled and not failedssh-mgmt-bypass.serviceis active and enabledverify-transparent-proxy.timeris active and enabledsing-box check -c /etc/sing-box/config.jsonpasses- proxy host resolves and matches the rendered sing-box outbound
- direct proxy endpoint egress equals
proxy_expected_egress_ip - multiple transparent egress sources equal
proxy_expected_egress_ip - DNS resolves and the DNS proxy path is confirmed from sing-box logs
- rendered sing-box config has strict TUN, DNS hijack, SSH bypass, and proxy-IP bypass
- policy route table
2022sends default traffic tosingtun0 ip rulereferences table2022- TUN interface exists
- sing-box nftables redirect and DNS hijack rules exist
- SSH TCP/22 ingress and reply traffic bypass the TUN/proxy path in config and nftables
- UDP proxying is strictly checked when
proxy_allow_udp=true
For manual debugging, run the same command directly on the VM. It prints every
PASS and FAIL before returning its final exit code, so the status report is
visible even when the command exits non-zero.
For DNS-specific validation:
check-dns-proxy
check-dns-proxy /etc/sing-box/transparent-proxy.env example.comFor scheduled health checks:
systemctl status verify-transparent-proxy.timer
journalctl -u verify-transparent-proxy.service --no-pager -n 100Key defaults live in inventory/group_vars/devboxes.yml:
hcloud_location: ashhcloud_server_type: cx22hcloud_image: debian-12transparent_proxy_enabled: trueproxy_mode: socks5- DoT DNS upstream:
1.1.1.1:853with SNIcloudflare-dns.com - IPv6 egress disabled by default to reduce leak paths
- Proxy credentials are stored in the host vars and rendered to
/etc/sing-box/transparent-proxy.envon the VM. Do not commit real credentials to a public repo. - The Hetzner VM's own IPv4 is not intended to be the fixed proxy egress IP.
- The project currently does not manage proxy-provider APIs or buy IPs.