Digital nomad routing VPS browser traffic through a travel router to get a residential IP, with Openclaw crab logo, Tailscale, and global network visualization

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

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 shell
  • ServerAliveInterval/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.1 only (not 0.0.0.0)
  • SSH key is ED25519 with permissions 600
  • State file and logs are not world-readable
  • ExitOnForwardFailure=yes on the SSH tunnel
  • ServerAliveInterval and ServerAliveCountMax configured
  • 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:

  1. Connect the router to new Wi-Fi (GL-iNet admin panel or repeater mode)
  2. Complete any captive portal login
  3. Tailscale reconnects automatically (30-60 seconds)
  4. systemd restarts the SSH tunnel automatically (within 10 seconds)
  5. Health check detects proxy is back (within 60 seconds)
  6. 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

CommandWhat it does
systemctl status socks-proxyTunnel health
curl --socks5-hostname localhost:1080 -s https://httpbin.org/ipCurrent proxy IP
cat /root/proxy-activeProxy state (up/down)
tail -20 /var/log/proxy-health.logHealth log
systemctl restart socks-proxyRestart tunnel
echo "down" > /root/proxy-activeTemporarily disable proxy

Files Created

FilePurpose
~/.ssh/router_proxyED25519 SSH key for the tunnel
/etc/systemd/system/socks-proxy.servicesystemd service (auto-start, auto-restart)
/root/chrome-proxied.shBrowser wrapper with conditional proxy flags
/root/proxy-healthcheck.shCron health check
/root/proxy-activeState file (“up” or “down”)
/var/log/proxy-health.logHealth 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.

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



Questions? Find me using the links on the left or in my site’s menu.