Home/Blog/Mobile Proxy for go-rod
Tool Integration

Mobile Proxy for go-rod

go-rod is the Go equivalent of Puppeteer — it drives Chrome via CDP. The launcher package exposes .Proxy(host:port), and authentication is handled with a Fetch.AuthRequired hook. Together that gets your headless Chrome egressing from a real mobile IP.

7 min read·Browser Automation·Last updated: May 2026

Prerequisites

  • Go 1.21+ and github.com/go-rod/rod v0.114+.
  • Chrome/Chromium binary on the host (go-rod auto-downloads if missing).
  • Mobile proxy credentials + API key from mobileproxies.org.

Step-by-Step Configuration

STEP 01

Initialize the project

mkdir scraper && cd scraper
go mod init scraper
go get github.com/go-rod/rod
go get github.com/go-rod/rod/lib/launcher
go get github.com/go-rod/rod/lib/proto
STEP 02

Launch Chrome with proxy + auth hook

// main.go
package main

import (
    "fmt"
    "os"

    "github.com/go-rod/rod"
    "github.com/go-rod/rod/lib/launcher"
    "github.com/go-rod/rod/lib/proto"
)

func main() {
    host := os.Getenv("MP_HOST") // proxy.mobileproxies.org
    port := os.Getenv("MP_PORT") // 8000
    user := os.Getenv("MP_USER")
    pass := os.Getenv("MP_PASS")

    // Chrome's --proxy-server doesn't accept user:pass in the URL,
    // so we pass host:port here and authenticate via Fetch domain.
    url := launcher.New().
        Proxy(fmt.Sprintf("http://%s:%s", host, port)).
        Headless(true).
        MustLaunch()

    browser := rod.New().ControlURL(url).MustConnect()
    defer browser.MustClose()

    // Hook authentication BEFORE navigating
    go browser.MustHandleAuth(user, pass)()

    page := browser.MustPage()

    // Optional: set a mobile UA so the fingerprint matches the IP
    _ = page.MustSetUserAgent(&proto.NetworkSetUserAgentOverride{
        UserAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) " +
            "AppleWebKit/605.1.15 Mobile/15E148 Safari/604.1",
    })

    page.MustNavigate("https://api.ipify.org?format=json").MustWaitLoad()
    fmt.Println("egress:", page.MustElement("body").MustText())
}
STEP 03

Run it

export MP_HOST=proxy.mobileproxies.org
export MP_PORT=8000
export MP_USER=u_4a9c
export MP_PASS=p_2X7q...
export MP_API_KEY=YOUR_API_KEY
export MP_SLOT=us-mob-01

go run .
# egress: {"ip":"<a mobile carrier IP>"}
STEP 04

Per-context proxy (BrowserContext isolation)

For parallel sessions on different proxies in the same Chrome instance, use incognito-style browser contexts via the Target.createBrowserContext CDP call:

ctx, _ := proto.TargetCreateBrowserContext{
    ProxyServer:    "http://proxy.mobileproxies.org:8000",
    ProxyBypassList: "<-loopback>",
}.Call(browser)

target, _ := proto.TargetCreateTarget{
    URL: "about:blank",
    BrowserContextID: ctx.BrowserContextID,
}.Call(browser)

page := browser.MustPageFromTargetID(target.TargetID)
STEP 05

Rotate before each session

import (
    "net/http"
    "time"
)

func rotate(slot, apiKey string) error {
    req, _ := http.NewRequest("POST",
        "https://buy.mobileproxies.org/api/v1/proxies/"+slot+"/switch", nil)
    req.Header.Set("Authorization", "Bearer "+apiKey)
    resp, err := http.DefaultClient.Do(req)
    if err != nil { return err }
    defer resp.Body.Close()
    time.Sleep(4 * time.Second)  // wait for new IP to bind
    return nil
}

// Call rotate(...) between page.MustNavigate batches.

Verify It Works

The body of api.ipify.org should print a carrier-owned IP. Run page.MustNavigate("https://ipinfo.io/json") and parse theorg field — it should match a mobile carrier (AS7018, AS21928, AS12430, etc). If you see Google's AS15169 or AWS's AS16509, the auth hook fired too late — make sure go browser.MustHandleAuth(...) is called BEFORE the first navigation.

Hardening Tips

  • WebRTC leak: Chrome can leak the real IP via STUN. Add --enforce-webrtc-ip-permission-check and --force-webrtc-ip-handling-policy=disable_non_proxied_udp to launcher flags.
  • Timezone & locale: match the proxy IP's geo. page.MustSetExtraHeaders + Emulation.setTimezoneOverride.
  • stealth.js: ship the upstream stealth bundle through page.MustEvalOnNewDocument to mask navigator.webdriver.

Common Errors

"net::ERR_TUNNEL_CONNECTION_FAILED"

Chrome can't reach the proxy host. Confirm DNS resolves and the port is the HTTP port, not SOCKS5. go-rod's launcher proxy field is HTTP-only.

Auth popup appears in headed mode

The MustHandleAuth goroutine wasn't started or already exited. Make sure it's called before MustNavigate and that the returned wait function is invoked exactly once per session.

Page hangs on TLS error pages

For targets that present certs through the proxy that Chrome doesn't trust, add --ignore-certificate-errors to launcher flags — use sparingly, only against known targets.

Related Guides

Real Mobile IPs Inside Headless Chrome

$5 trial. Drop the proxy into launcher.New().Proxy() and your go-rod sessions ride a carrier ASN.