Guide

How to Use Proxies With Colly (Go)

How to use proxies with Colly in Go: SetProxy for one proxy, the RoundRobinProxySwitcher for rotation, SOCKS, authentication, timeouts and retries, and verifying the exit IP.

HProxy Team··4 min read
HProxy.Guide

Skip the dead lists.

Our free proxy list re-checks every exit every few minutes across 100+ countries, with a live last-checked time, so you copy IPs that worked moments ago, not a stale text dump.

Open the free proxy list

Colly is the scraping framework most Go projects use, and it ships proxy rotation in the box, which is rare and worth building on correctly. This guide covers proxies with Colly end to end: SetProxy for a single exit, the RoundRobinProxySwitcher for rotation, SOCKS, authentication, the concurrency and timeout settings that keep a proxied crawl from thrashing, and verifying the exit IP so you know the requests actually left through the proxy.

We run a proxy network and a live proxy checker, so the Colly proxy question we answer most is why a crawl that rotates a free pool still gets blocked. The usual answer is concurrency: Colly's async mode fires a wall of requests, and against a small or datacenter pool that pattern is exactly what defenses look for. The settings below fix that. Every example is runnable Go. Where one needs a live proxy, pull a fresh one from our free proxy API, which returns recently checked endpoints with no key.

How do you set a proxy in Colly?

For one proxy, call SetProxy on the collector. Every request that collector makes then goes through it:

package main

import (
	"fmt"
	"github.com/gocolly/colly"
)

func main() {
	c := colly.NewCollector()
	c.SetProxy("http://203.0.113.7:8080")

	c.OnResponse(func(r *colly.Response) {
		fmt.Println(string(r.Body))
	})
	c.Visit("https://httpbin.org/ip")
}

The proxy URL scheme can be http:// or socks5://, because Colly hands the URL to Go's net/http, which understands both.

Rotating with the RoundRobinProxySwitcher

Colly's own proxy package gives you rotation without a third-party library. Build a switcher from a list of proxy URLs and set it with SetProxyFunc:

import (
	"github.com/gocolly/colly"
	"github.com/gocolly/colly/proxy"
)

c := colly.NewCollector()

rp, err := proxy.RoundRobinProxySwitcher(
	"http://203.0.113.7:8080",
	"http://203.0.113.24:3128",
	"socks5://198.51.100.14:1080",
)
if err != nil {
	panic(err)
}
c.SetProxyFunc(rp)

Colly cycles through the list, one exit per request. Note that a socks5:// entry sits happily alongside HTTP ones in the same list, which is convenient when a free pool hands you a mix.

How Colly picks a proxy per request
  1. c.Visit(url)

    one request queued

  2. SetProxyFunc

    switcher returns next proxy

  3. net/http dials it

    http or socks5

  4. Response, or retry

    on a fresh exit if it fails

Source: The RoundRobinProxySwitcher hands out one exit per request

A smarter switcher that drops dead exits

Round-robin is fine until half your list is dead, which on a free pool it will be. SetProxyFunc takes any function of the form func(*http.Request) (*url.URL, error), so you can write one that skips exits known to be down:

import (
	"math/rand"
	"net/http"
	"net/url"
	"sync"
)

type Pool struct {
	mu    sync.Mutex
	alive []string
}

func (p *Pool) Get(_ *http.Request) (*url.URL, error) {
	p.mu.Lock()
	defer p.mu.Unlock()
	return url.Parse(p.alive[rand.Intn(len(p.alive))])
}

// call Drop(proxy) from an OnError handler when an exit fails
func (p *Pool) Drop(dead string) {
	p.mu.Lock()
	defer p.mu.Unlock()
	for i, u := range p.alive {
		if u == dead {
			p.alive = append(p.alive[:i], p.alive[i+1:]...)
			return
		}
	}
}

Wire Drop into an OnError callback so a connection failure retires that exit, and the crawl stops wasting requests on corpses. Fill alive from our free proxy API at startup.

Authentication

For a paid proxy, put the credentials in the URL, user:pass@host:port, the form net/http expects. Both SetProxy and the switcher accept it:

c.SetProxy("http://myuser:mypass@203.0.113.7:8080")

A wrong or missing login returns HTTP 407 Proxy Authentication Required, which confirms the proxy is reachable and only the credentials are off.

Concurrency and timeouts, the part that decides success

This is where a proxied Colly crawl lives or dies. Async mode is fast and is exactly what overwhelms a small or datacenter pool, so bound it and set a timeout so a dead exit fails fast:

c := colly.NewCollector(colly.Async(true))

c.Limit(&colly.LimitRule{
	DomainGlob:  "*",
	Parallelism: 4,                 // not 50: a free pool cannot take it
	RandomDelay: 2 * time.Second,   // space requests so one exit is not hammered
})

c.SetRequestTimeout(15 * time.Second)   // a dead exit fails in 15s, not on a default wait

Parallelism caps simultaneous requests per domain, RandomDelay spaces them so a single exit is not obviously scripted, and SetRequestTimeout stops a stalled proxy from holding a worker. On a free pool these three are the difference between a crawl that makes progress and one that thrashes on dead IPs.

Verify the exit IP

Confirm the proxy took effect rather than trusting it. Visit an IP echo and print what the target saw:

c.OnResponse(func(r *colly.Response) {
	fmt.Println("exit IP:", string(r.Body))   // from https://httpbin.org/ip
})
c.Visit("https://httpbin.org/ip")

If the address is your own, the proxy did not apply, which usually means SetProxy or SetProxyFunc was called on a different collector than the one doing the visit. For a fuller read of the exit, our proxy checker reports the exit IP, country, latency and anonymity grade in one paste.

Where to go from here

Colly gives you rotation for free, and the work is in feeding it a pool that is alive and pacing the crawl so it does not out-run the exits. Once the switcher, the concurrency limit, and the timeout are set, the discipline is the same as any scraper: verify exits, drop dead ones, and match the proxy type to the target.

The Go proxy guide covers routing plain net/http and other Go clients if part of your code is outside Colly, proxies for web scraping covers picking the right proxy type, and how to avoid IP bans while scraping is the prevention checklist for when rotation alone is not enough. When a hand-managed pool becomes the bottleneck, a rotating gateway that returns a fresh residential IP per request at $0.44/GB replaces the whole switcher with one endpoint.

Frequently asked questions

How do I set a proxy in Colly?
For a single proxy, call collector.SetProxy("http://host:port"). For rotation, build a switcher with proxy.RoundRobinProxySwitcher(url1, url2, ...) and pass it to collector.SetProxyFunc, which hands out a different proxy per request. The switcher is part of Colly's own proxy package, so no extra dependency is needed for basic rotation.
How do I rotate proxies in Colly?
Use the built-in RoundRobinProxySwitcher from github.com/gocolly/colly/proxy. Pass it a list of proxy URLs, then set it with c.SetProxyFunc(rp). Colly cycles through the list per request. For smarter selection (dropping dead exits, weighting by success) write your own function with the signature func(*http.Request) (*url.URL, error) and pass that to SetProxyFunc instead.
Does Colly support SOCKS5 proxies?
Yes. Colly builds on Go's net/http, which supports socks5:// proxy URLs, so both SetProxy and the RoundRobinProxySwitcher accept socks5:// alongside http:// entries. You can mix HTTP and SOCKS proxies in the same switcher list, which is handy when a free pool gives you both.
How do I authenticate a proxy in Colly?
Put the credentials in the proxy URL as user:pass@host:port, the same form net/http expects. Both SetProxy and the switcher accept an authenticated URL. A wrong or missing login comes back as HTTP 407 Proxy Authentication Required, which confirms the proxy is reachable and only the credentials are wrong.
Why does my Colly proxy get blocked so fast?
Usually because the pool is small or the exits are datacenter IPs, and Colly's async mode fires many requests at once through them. Lower Parallelism in the LimitRule, add a RandomDelay, rotate a larger pool, and set a request timeout so a dead exit fails fast instead of holding a worker. On a defended target, move from a free list to rotating residential.

Get proxies that are alive right now

Our free list re-checks every exit every few minutes and shows a last-checked time, so you copy IPs that worked moments ago, not a stale text dump. When the location has to survive a real check, the paid network holds up.

129M+ proxy checks run · 100+ countries · HTTP / HTTPS / SOCKS · re-checked every few minutes · no signup