Free Residential IP for Openclaw — Travel Router Method
If you run browser automation on a VPS, you’ve hit this wall: websites detect your datacenter IP and block you, serve CAPTCHAs, or flag your sessions. Openclaw, Playwright, Puppeteer — doesn’t matter which tool.
Paid proxy services work but cost $5-15/GB. VPNs still look like datacenter traffic.
There’s a simpler approach: use a $100 travel router as your exit point. Plug it into any home internet (or hotel Wi-Fi, or a coworking space), connect it to your VPS over Tailscale, and tunnel your browser traffic through it. Websites see a real residential IP. You control the whole chain. No third-party proxy services, no monthly fees.
TL;DR: GL-iNet travel router + Tailscale + SSH SOCKS5 tunnel = residential IP for your VPS browser traffic. One-time ~$100 cost, $0/month after. Auto-failover when the tunnel drops.
When I say “we” throughout this post, I mean me and Claude Code. Claude helped research, write configs, and debug issues. This is designed as a guide you can work through with your own AI assistant — paste sections in, adapt to your setup, and have it help you debug. Always have your AI review the security configuration for your specific environment.
Table of Contents
- Why This Matters
- The Architecture
- What You Need
- Set Up the SSH SOCKS5 Tunnel
- Configure Openclaw Browser Proxy
- Configure Router DNS
- Security Essentials
- Moving Locations as a Digital Nomad
- Quick Reference
- Files Created
- What This Doesn’t Solve
- Cost
- FAQ
- Next Steps
- Links
Why This Matters
Datacenter IPs are the #1 detection signal for anti-bot systems. Your VPS IP is in known datacenter ranges — that alone gets you blocked before any fingerprinting happens.
A residential IP fixes this. Your traffic appears to come from a regular home internet connection because it does.
For digital nomads this is especially useful: your travel router travels with you. New Airbnb? New coworking space? Plug it in, Tailscale reconnects, and your VPS browser traffic exits from wherever you are in the world.
The Architecture
VPS (Hetzner)
→ Openclaw browser (via wrapper script)
→ SSH SOCKS5 tunnel (localhost:1080)
→ Tailscale encrypted mesh
→ GL-iNet travel router (on residential/hotel network)
→ DNS resolves here (not on your VPS)
→ Target website sees residential IP
Only browser traffic takes this path. Everything else on the VPS — API calls, SSH sessions, Openclaw’s core — routes directly through the datacenter. Fast and unaffected.
Why SSH instead of running a proxy server on the router:
- Nothing to install on the router — SSH is already there
- SOCKS5h support — DNS resolves through the router, not the VPS (eliminates datacenter DNS fingerprint)
- Key auth, encrypted end-to-end
- Layered inside Tailscale’s WireGuard tunnel
What You Need
Hardware:
- A Linux VPS (this guide uses Hetzner, but any provider works)
- A GL-iNet travel router with Tailscale support — Slate AX (GL-AXT1800) is what I use, but any GL-iNet running firmware 4.x works (Beryl AX, Flint 2, Mango, etc.). $30-$100 range.
Software (on the VPS):
On the router:
- Tailscale enabled and connected to the same tailnet as your VPS
- SSH access (enabled by default on GL-iNet routers)
Time to complete: ~30 minutes with AI assistance.
Set Up the SSH SOCKS5 Tunnel
Verify Connectivity
From your VPS, confirm Tailscale can reach the router and the target port is free:
ping -c 2 <router-tailscale-ip>
ss -tlnp | grep 1080
Set Up SSH Key Auth
The tunnel needs passwordless login for unattended auto-reconnect.
# On the VPS — generate a dedicated key
ssh-keygen -t ed25519 -f ~/.ssh/router_proxy -N ""
# Copy it to the router (enter router root password when prompted)
ssh-copy-id -i ~/.ssh/router_proxy.pub root@<router-tailscale-ip>
# Verify — should print "connected" with no password prompt
ssh -i ~/.ssh/router_proxy root@<router-tailscale-ip> "echo connected"
The router’s root password is the one you set in the GL-iNet admin panel (192.168.8.1).
Test the Tunnel
ssh -D 1080 -f -C -N \
-o ServerAliveInterval=30 \
-o ServerAliveCountMax=3 \
-i ~/.ssh/router_proxy \
root@<router-tailscale-ip>
What these flags do:
-D 1080— SOCKS5 proxy on localhost:1080-C— Compress (helps on slow Wi-Fi)-N— Tunnel only, no remote shellServerAliveInterval/CountMax— Detect dead connections within 90 seconds
Verify it works:
# Should return the router's network IP (residential)
curl --socks5-hostname localhost:1080 -s https://httpbin.org/ip
# Compare with your VPS's direct IP
curl -s https://httpbin.org/ip
Two different IPs = working. The --socks5-hostname flag (not just --socks5) forces DNS through the proxy too.
Kill the test tunnel: pkill -f "ssh -D 1080"
Make It Persistent with systemd
A bare ssh -D dies when the connection drops. systemd brings it back automatically.
# /etc/systemd/system/socks-proxy.service
[Unit]
Description=SSH SOCKS5 Proxy via Travel Router
After=network.target tailscaled.service
Wants=tailscaled.service
[Service]
Type=simple
ExecStart=/usr/bin/ssh -D 1080 -C -N \
-o ServerAliveInterval=30 \
-o ServerAliveCountMax=3 \
-o ExitOnForwardFailure=yes \
-o StrictHostKeyChecking=accept-new \
-i /root/.ssh/router_proxy \
root@<router-tailscale-ip>
User=root
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable socks-proxy
sudo systemctl start socks-proxy
ExitOnForwardFailure=yes is important — if port 1080 is already bound (stale process, previous crash), SSH exits immediately instead of running silently without a tunnel.
Configure Openclaw Browser Proxy
The tunnel is running. Now Openclaw’s browser needs to use it — but only when it’s healthy.
Health Check Script
A cron job tests the proxy every minute and writes a state file:
#!/bin/bash
# /root/proxy-healthcheck.sh
PROXY="localhost:1080"
STATE_FILE="/root/proxy-active"
LOG_FILE="/var/log/proxy-health.log"
if curl --socks5-hostname "$PROXY" \
--connect-timeout 5 --max-time 10 \
-s -o /dev/null -w "%{http_code}" https://httpbin.org/ip 2>/dev/null | grep -q "200"; then
if [ "$(cat "$STATE_FILE" 2>/dev/null)" != "up" ]; then
echo "up" > "$STATE_FILE"
echo "$(date '+%Y-%m-%d %H:%M:%S'): Proxy RESTORED" >> "$LOG_FILE"
fi
else
if [ "$(cat "$STATE_FILE" 2>/dev/null)" != "down" ]; then
echo "down" > "$STATE_FILE"
echo "$(date '+%Y-%m-%d %H:%M:%S'): Proxy DOWN — falling back to direct" >> "$LOG_FILE"
fi
fi
chmod +x /root/proxy-healthcheck.sh
(crontab -l 2>/dev/null; echo "* * * * * /root/proxy-healthcheck.sh") | sort -u | crontab -
Browser Wrapper Script
Instead of pointing Openclaw at the Chromium binary directly, point it at this wrapper:
#!/bin/bash
# /root/chrome-proxied.sh
PROXY_ARGS=()
if [ "$(cat /root/proxy-active 2>/dev/null)" = "up" ]; then
PROXY_ARGS=(
"--proxy-server=socks5://localhost:1080"
"--host-resolver-rules=MAP * ~NOTFOUND , EXCLUDE localhost"
)
fi
exec /usr/bin/chromium "${PROXY_ARGS[@]}" "$@"
chmod +x /root/chrome-proxied.sh
What the Chrome flags do:
--proxy-server=socks5://localhost:1080— Route traffic through the tunnel--host-resolver-rules=MAP * ~NOTFOUND , EXCLUDE localhost— Force Chrome to resolve DNS through the proxy. Without this, Chrome resolves DNS locally on the VPS, leaking your datacenter DNS even though the traffic goes through the residential IP.
Configure Openclaw executablePath
In your Openclaw config (~/.openclaw/openclaw.json), set the browser executable path to the wrapper:
{
browser: {
enabled: true,
executablePath: "/root/chrome-proxied.sh",
headless: true,
noSandbox: true
}
}
That’s it. Openclaw now launches the browser through the wrapper, which checks the proxy state on every launch.
How the Failover Works
Cron runs health check every 60 seconds
→ Tests: can curl reach the internet through the proxy?
→ Writes "up" or "down" to the state file
Browser wrapper reads state file on every launch
→ "up" → Chrome launches with proxy flags
→ "down" → Chrome launches normally (direct VPS IP)
The browser never hangs on a dead proxy. When the tunnel recovers, the next health check flips the state back and the next browser launch goes through the proxy again.
The Bash Array Gotcha
If you modify the wrapper script, do not store the flags in a plain string:
# BROKEN — do not do this
PROXY_ARGS="--proxy-server=socks5://localhost:1080 --host-resolver-rules=MAP * ~NOTFOUND , EXCLUDE localhost"
exec /usr/bin/chromium $PROXY_ARGS "$@"
Bash will word-split on spaces (breaking the host-resolver-rules value) and glob-expand the * into filenames. Chromium receives garbage. The fix is the bash array shown above — "${PROXY_ARGS[@]}" expands each element as a single, properly-quoted argument.
Configure Router DNS
By default, the router uses whatever DNS the hotel or ISP assigns. For reliability and privacy, configure explicit DNS.
In the GL-iNet admin panel (192.168.8.1 → Network → DNS):
- Mode: Encrypted DNS
- Encryption Type: DNS over HTTPS
- Servers: Cloudflare (DNSSEC, No Log)
- Override DNS Settings of All Clients: ON
DNS over HTTPS means the hotel Wi-Fi can’t snoop on or filter your DNS queries either.
Security Essentials
This section covers the key security points. Have your AI assistant review your specific setup — paste your systemd service file, wrapper script, and file permissions into your AI and ask it to audit for issues.
Verify Proxy Binds to Localhost Only
ss -tlnp | grep 1080
You should see 127.0.0.1:1080. If you see 0.0.0.0:1080, your proxy is open to the internet — anyone can use your exit node.
Lock Down File Permissions
chmod 600 ~/.ssh/router_proxy
chmod 700 ~/.ssh
chmod 640 /root/proxy-active
chmod 640 /var/log/proxy-health.log
Security Checklist
After setup, run through this (or have your AI verify each one):
- Proxy binds to
127.0.0.1only (not0.0.0.0) - SSH key is ED25519 with permissions
600 - State file and logs are not world-readable
-
ExitOnForwardFailure=yeson the SSH tunnel -
ServerAliveIntervalandServerAliveCountMaxconfigured - Browser wrapper uses bash arrays, not strings
- Logrotate configured for
/var/log/proxy-health.log
Optional hardening: Add NoNewPrivileges=yes, ProtectSystem=strict, and PrivateDevices=yes to your systemd service’s [Service] section. Ask your AI to generate the full hardened service file for your setup.
Moving Locations as a Digital Nomad
This is the best part for digital nomads. When you change networks:
- Connect the router to new Wi-Fi (GL-iNet admin panel or repeater mode)
- Complete any captive portal login
- Tailscale reconnects automatically (30-60 seconds)
- systemd restarts the SSH tunnel automatically (within 10 seconds)
- Health check detects proxy is back (within 60 seconds)
- No changes needed on the VPS
New Airbnb in Lisbon? Plug in the router. Coworking space in Chiang Mai? Connect to their Wi-Fi. Your VPS browser traffic exits from wherever you are.
Captive portal gap: Between connecting and completing portal login, the health check marks the proxy “down” and the browser falls back to direct. Once you authenticate, everything recovers within a minute.
Quick Reference
| Command | What it does |
|---|---|
systemctl status socks-proxy | Tunnel health |
curl --socks5-hostname localhost:1080 -s https://httpbin.org/ip | Current proxy IP |
cat /root/proxy-active | Proxy state (up/down) |
tail -20 /var/log/proxy-health.log | Health log |
systemctl restart socks-proxy | Restart tunnel |
echo "down" > /root/proxy-active | Temporarily disable proxy |
Files Created
| File | Purpose |
|---|---|
~/.ssh/router_proxy | ED25519 SSH key for the tunnel |
/etc/systemd/system/socks-proxy.service | systemd service (auto-start, auto-restart) |
/root/chrome-proxied.sh | Browser wrapper with conditional proxy flags |
/root/proxy-healthcheck.sh | Cron health check |
/root/proxy-active | State file (“up” or “down”) |
/var/log/proxy-health.log | Health check log |
What This Doesn’t Solve
A residential IP fixes the most common detection vector — datacenter IP blocking. But sophisticated anti-bot systems can still fingerprint you via:
- Browser fingerprint — Canvas, WebGL, fonts, screen resolution all come from the VPS, not a consumer device
- Timezone mismatch — Your IP says Thailand but the browser timezone says UTC
- WebRTC leaks — Can expose the real VPS IP unless disabled with
--disable-webrtc - TLS fingerprint — Headless Chrome has a distinct TLS fingerprint
- Behavioral patterns — Inhuman click speed, no mouse movement, identical timing
The residential IP gets you past the front door. The rest depends on your browser automation setup.
Cost
- GL-iNet travel router: ~$100 one-time (or ~$30 for the Mango)
- Tailscale: Free tier (up to 100 devices)
- Hetzner VPS: Whatever you’re already paying
- Ongoing proxy fees: $0
Compare that to residential proxy services charging $5-15 per GB. If your browser does any volume at all, the router pays for itself in weeks.
FAQ
Does this work with Playwright or Puppeteer too?
Yes. The tunnel and wrapper script work with any tool that launches Chromium. For Playwright, set the executablePath in your launch config. For Puppeteer, same thing — point it at /root/chrome-proxied.sh instead of the Chromium binary.
What happens if the router loses power or internet?
systemd detects the dead SSH connection within 90 seconds (ServerAliveInterval × ServerAliveCountMax) and restarts the tunnel. The health check marks the proxy “down” within 60 seconds and the browser falls back to direct VPS traffic. When the router comes back, everything recovers automatically.
Can I use multiple routers for IP rotation?
Yes. Run multiple SSH tunnels on different ports (1080, 1081, 1082), each connecting to a different router. Modify the wrapper script to rotate between them. This gives you multiple residential IPs in different locations.
Is this legal?
Routing your own traffic through your own hardware on networks you’re authorized to use is legal. You’re not accessing anyone else’s connection without permission — you’re using the internet connection at your Airbnb, home, or coworking space as an exit point for your own VPS traffic. That said, always check the terms of service for the specific network and your VPS provider.
What about latency?
Expect 50-200ms added latency depending on the distance between your VPS and the router’s location. For browser automation this is rarely an issue — most tasks are I/O-bound, not latency-sensitive. Your non-browser VPS traffic is unaffected since it routes directly.
Next Steps
- Set up remote access — Control Openclaw from your phone while traveling: Control Claude Code from iPhone with SSH + Zellij
- Add push notifications — Get alerted when tasks finish: Push Notifications for Claude Code with ntfy
- Self-host a mobile UI — Manage everything from a web app on your phone: Self-Host Happy Server
Links
- Openclaw — AI agent platform
- Openclaw Docs — Configuration reference
- GL-iNet — Travel routers
- Tailscale — Zero-config VPN mesh
- Hetzner — VPS hosting
Questions? Find me using the links on the left or in my site’s menu.