NewUnlimited residential proxies: unmetered traffic on a fixed price. From $33.25/day

Go ยท Integration guide

How to use proxies with Go

Go needs no third-party packages for proxies: net/http handles authenticated HTTP and SOCKS5 gateways. This guide covers rotation, sticky sessions, location targeting and reading gateway status codes.

  • 6 steps
  • 12 min setup
  • Copy-paste code

Before you start

  • Go 1.20 or newer
  • The standard library only (net/http and net/url)
  • A Proxonym residential or mobile plan
Get proxy credentials

Already a customer? Your username and password are in the dashboard.

  1. Create a module

    Every example on this page is a single main.go file that uses the standard library, so a fresh module is all you need. Run each example with go run ..

    Shell
    mkdir proxydemo && cd proxydemo
    go mod init proxydemo
  2. Send a request through the gateway

    Give the transport a proxy URL with your credentials. Go sends them as a Proxy-Authorization header, including on the CONNECT request that tunnels HTTPS.

    Go
    package main
    
    import (
    	"fmt"
    	"io"
    	"log"
    	"net/http"
    	"net/url"
    	"time"
    )
    
    func main() {
    	proxyURL, err := url.Parse("http://USERNAME:[email protected]:8000")
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	client := &http.Client{
    		Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)},
    		Timeout:   60 * time.Second,
    	}
    
    	resp, err := client.Get("https://api.ipify.org")
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer resp.Body.Close()
    
    	ip, err := io.ReadAll(resp.Body)
    	if err != nil {
    		log.Fatal(err)
    	}
    	fmt.Println("Exit IP:", string(ip))
    }
  3. Rotate or keep the same IP

    The gateway assigns an IP per connection, and http.Transport keeps connections alive. Disable keep-alives to get a new IP on every request, or keep them on and add a -session- ID to hold one IP for up to 120 minutes.

    Go
    package main
    
    import (
    	"crypto/rand"
    	"encoding/hex"
    	"fmt"
    	"io"
    	"net/http"
    	"net/url"
    	"strings"
    	"time"
    )
    
    // proxonymClient adds parameters such as "country-us" or "session-a7k2" to the username.
    func proxonymClient(keepAlive bool, params ...string) *http.Client {
    	user := strings.Join(append([]string{"USERNAME"}, params...), "-")
    	proxyURL := &url.URL{
    		Scheme: "http",
    		User:   url.UserPassword(user, "PASSWORD"),
    		Host:   "gw.proxonym.com:8000",
    	}
    	return &http.Client{
    		Transport: &http.Transport{
    			Proxy:             http.ProxyURL(proxyURL),
    			DisableKeepAlives: !keepAlive,
    		},
    		Timeout: 60 * time.Second,
    	}
    }
    
    func newSessionID() string {
    	b := make([]byte, 4)
    	rand.Read(b)
    	return hex.EncodeToString(b)
    }
    
    func exitIP(client *http.Client) string {
    	resp, err := client.Get("https://api.ipify.org")
    	if err != nil {
    		return "error: " + err.Error()
    	}
    	defer resp.Body.Close()
    	body, _ := io.ReadAll(resp.Body)
    	return string(body)
    }
    
    func main() {
    	// Rotating: keep-alives off, so each request opens a new connection and gets a new IP
    	rotating := proxonymClient(false, "country-us")
    	for i := 0; i < 3; i++ {
    		fmt.Println("rotating", exitIP(rotating))
    	}
    
    	// Sticky: one session ID keeps the same IP for up to 30 minutes
    	sticky := proxonymClient(true, "country-us", "session-"+newSessionID(), "lifetime-30")
    	for i := 0; i < 3; i++ {
    		fmt.Println("sticky", exitIP(sticky))
    	}
    }
  4. Target a country, state, city or ASN

    Pass location parameters to proxonymClient() from the previous step. Use lowercase ISO country codes and underscores for spaces, and choose a state or a city, not both.

    Go
    clients := map[string]*http.Client{
    	"California": proxonymClient(true, "country-us", "state-california"),
    	"London":     proxonymClient(true, "country-gb", "city-london"),
    	"Comcast":    proxonymClient(true, "country-us", "asn-7922"),
    }
    
    for name, client := range clients {
    	resp, err := client.Get("https://ipinfo.io/json")
    	if err != nil {
    		fmt.Println(name, "error:", err)
    		continue
    	}
    	body, _ := io.ReadAll(resp.Body)
    	resp.Body.Close()
    	fmt.Println(name, string(body))
    }
  5. Use the SOCKS5 endpoint

    net/http also accepts socks5:// proxy URLs with a username and password. Hostnames are sent to the proxy unresolved, so DNS lookups happen on the gateway side.

    Go
    proxyURL, err := url.Parse("socks5://USERNAME-country-gb:[email protected]:1080")
    if err != nil {
    	log.Fatal(err)
    }
    
    client := &http.Client{
    	Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)},
    	Timeout:   60 * time.Second,
    }
    
    resp, err := client.Get("https://api.ipify.org")
    if err != nil {
    	log.Fatal(err)
    }
    defer resp.Body.Close()
    ip, _ := io.ReadAll(resp.Body)
    fmt.Println("Exit IP:", string(ip))
  6. Read gateway status codes and retry

    The OnProxyConnectResponse hook exposes the status of every CONNECT request. Retry 429, 502 and 504 with a backoff, and stop on 402, 403 or 407, which point to balance, policy or credential problems.

    Go
    package main
    
    import (
    	"context"
    	"errors"
    	"fmt"
    	"net/http"
    	"net/url"
    	"time"
    )
    
    // gatewayError carries the status the gateway returned for a CONNECT request.
    type gatewayError struct{ status int }
    
    func (e *gatewayError) Error() string { return fmt.Sprintf("gateway returned %d", e.status) }
    
    func newClient(proxyURL *url.URL) *http.Client {
    	return &http.Client{
    		Timeout: 60 * time.Second,
    		Transport: &http.Transport{
    			Proxy: http.ProxyURL(proxyURL),
    			OnProxyConnectResponse: func(_ context.Context, _ *url.URL, _ *http.Request, res *http.Response) error {
    				if res.StatusCode != http.StatusOK {
    					return &gatewayError{status: res.StatusCode}
    				}
    				return nil
    			},
    		},
    	}
    }
    
    func getWithRetry(client *http.Client, target string, tries int) (*http.Response, error) {
    	var err error
    	for attempt := 1; attempt <= tries; attempt++ {
    		var resp *http.Response
    		resp, err = client.Get(target)
    		if err == nil && resp.StatusCode != http.StatusTooManyRequests && resp.StatusCode < 500 {
    			return resp, nil
    		}
    		if err == nil {
    			resp.Body.Close()
    			err = fmt.Errorf("target returned %d", resp.StatusCode)
    		}
    		var gwErr *gatewayError
    		if errors.As(err, &gwErr) && (gwErr.status == 402 || gwErr.status == 403 || gwErr.status == 407) {
    			return nil, err // retrying will not help: fix the balance, target or credentials
    		}
    		time.Sleep(time.Duration(attempt) * time.Second)
    	}
    	return nil, err
    }
    
    func main() {
    	proxyURL, _ := url.Parse("http://USERNAME-country-us:[email protected]:8000")
    	resp, err := getWithRetry(newClient(proxyURL), "https://example.com", 3)
    	if err != nil {
    		fmt.Println("request failed:", err)
    		return
    	}
    	defer resp.Body.Close()
    	fmt.Println("status:", resp.StatusCode)
    }

Frequently asked questions

Does Go support SOCKS5 proxies without extra packages?

Yes. net/http accepts socks5:// proxy URLs, including a username and password, and sends hostnames to the proxy for resolution. For raw TCP connections outside HTTP, the golang.org/x/net/proxy package provides a SOCKS5 dialer that takes the same credentials and username parameters as the HTTP endpoint.

Why do all requests from one client show the same IP?

The gateway picks an IP per connection, and http.Transport reuses connections to the same host while they stay open. Set DisableKeepAlives to true on a rotating client so each request opens a new connection, or create separate clients with different session IDs when you need several stable IPs at once.

How do I see the gateway status for HTTPS requests?

For HTTPS targets the gateway status belongs to the CONNECT request, which normally surfaces only as an error string. Since Go 1.20 the OnProxyConnectResponse hook on http.Transport receives the CONNECT response, so you can return a typed error that carries the status code and decide whether to retry.

How many goroutines can share the gateway?

Proxonym places no limit on concurrent sessions, and each account has a burst limit of around 10,000 requests per second. In practice the target site sets the pace, so cap concurrency with a worker pool or semaphore, tune MaxConnsPerHost on the transport, and back off when you see 429 responses.

Ready to plug it in? Get your credentials.

Pick a plan, copy your username and password from the dashboard and run the code above.

  • Pay as you go
  • Instant activation
  • Crypto accepted
  • 24/7 support