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.
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
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
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())
}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>"}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)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-checkand--force-webrtc-ip-handling-policy=disable_non_proxied_udpto launcher flags. - →Timezone & locale: match the proxy IP's geo.
page.MustSetExtraHeaders+Emulation.setTimezoneOverride. - →stealth.js: ship the upstream stealth bundle through
page.MustEvalOnNewDocumentto masknavigator.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.