Go is a favorite for high-performance crawlers, monitoring services, and data pipelines. Those workloads usually need proxies to spread traffic across IPs, reduce block rates, and reach geo-restricted content.
Using proxies in Golang is straightforward once you understand how net/http and SOCKS5 work together. This guide walks through:
net/httpgolang.org/x/net/proxyBy the end, you’ll be comfortable wiring proxies into real Go services, not just toy examples.
Before touching code, it helps to clarify a few basics.
HTTP / HTTPS proxies
You send plain HTTP requests to the proxy. The proxy forwards them to the target site and returns the response. Most datacenter proxy providers support this model.
SOCKS5 proxies
Work at a lower level and can tunnel arbitrary TCP connections. They’re often used for more complex routing and are a good fit when you need extra flexibility.
Datacenter vs residential / mobile
Authentication
In Go, everything revolves around customizing the http.Transport and reusing a configured http.Client.
net/httpThe standard library makes HTTP proxy support easy. You only need a Transport with a Proxy function.
package main
import (
"fmt"
"io"
"net/http"
"net/url"
)
func main() {
// Proxy with username/password
proxyURL, err := url.Parse("http://user:pass@proxyserver:8080")
if err != nil {
panic(err)
}
transport := &http.Transport{
Proxy: http.ProxyURL(proxyURL),
}
client := &http.Client{
Transport: transport,
}
resp, err := client.Get("https://httpbin.org/ip")
if err != nil {
fmt.Println("Request error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
If your proxy provider allows you to whitelist your server’s IP, you can omit the auth portion:
proxyURL, err := url.Parse("http://proxyserver:8080")
Everything else stays the same.
Go also respects standard proxy environment variables if you configure Transport.Proxy using http.ProxyFromEnvironment:
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
}
client := &http.Client{Transport: transport}
Then set environment variables like:
export HTTP_PROXY=http://user:pass@proxyserver:8080
export HTTPS_PROXY=http://user:pass@proxyserver:8080
This pattern is useful for CLI tools and microservices that share the same configuration style.
For production services, you should never rely on the default client. Set explicit timeouts and tune the transport.
package main
import (
"fmt"
"io"
"net"
"net/http"
"net/url"
"time"
)
func main() {
proxyURL, _ := url.Parse("http://user:pass@proxyserver:8080")
transport := &http.Transport{
Proxy: http.ProxyURL(proxyURL),
DialContext: (&net.Dialer{
Timeout: 5 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 5 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
client := &http.Client{
Transport: transport,
Timeout: 10 * time.Second, // overall per-request timeout
}
resp, err := client.Get("https://httpbin.org/ip")
if err != nil {
fmt.Println("Request error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
Key points:
Timeout on http.Client to prevent hanging. Dialer and IdleConnTimeout so connections are reused efficiently. MaxIdleConns to handle high concurrency.For SOCKS5, Go’s standard library doesn’t provide direct helpers, but the golang.org/x/net/proxy package fills the gap.
package main
import (
"fmt"
"io"
"net"
"net/http"
"time"
"golang.org/x/net/proxy"
)
func main() {
// SOCKS5 proxy without auth
dialer, err := proxy.SOCKS5("tcp", "proxyserver:1080", nil, proxy.Direct)
if err != nil {
panic(err)
}
// Wrap the SOCKS5 dialer into a net.DialContext function
httpTransport := &http.Transport{}
httpTransport.DialContext = func(ctx net.Context, network, addr string) (net.Conn, error) {
// proxy.Dialer only exposes Dial, so adapt it
return dialer.Dial(network, addr)
}
client := &http.Client{
Transport: httpTransport,
Timeout: 10 * time.Second,
}
resp, err := client.Get("https://httpbin.org/ip")
if err != nil {
fmt.Println("Request error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
auth := &proxy.Auth{
User: "user",
Password: "pass",
}
dialer, err := proxy.SOCKS5("tcp", "proxyserver:1080", auth, proxy.Direct)
SOCKS5 is especially useful when:
One of Go’s strengths is how easily it handles concurrency. Pairing goroutines with proxies lets you scale web tasks naturally.
package main
import (
"fmt"
"io"
"net/http"
"net/url"
"sync"
)
func main() {
proxyURL, _ := url.Parse("http://user:pass@proxyserver:8080")
transport := &http.Transport{Proxy: http.ProxyURL(proxyURL)}
client := &http.Client{Transport: transport}
urls := []string{
"https://httpbin.org/ip",
"https://example.com",
"https://golang.org",
}
var wg sync.WaitGroup
for _, u := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
resp, err := client.Get(u)
if err != nil {
fmt.Println("Error for", u, ":", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(u, "->", len(body), "bytes")
}(u)
}
wg.Wait()
}
package main
import (
"fmt"
"io"
"net/http"
"net/url"
"sync"
"sync/atomic"
)
func buildClient(proxyStr string) *http.Client {
u, _ := url.Parse(proxyStr)
tr := &http.Transport{Proxy: http.ProxyURL(u)}
return &http.Client{Transport: tr}
}
func main() {
proxyList := []string{
"http://user:pass@proxy1:8080",
"http://user:pass@proxy2:8080",
"http://user:pass@proxy3:8080",
}
clients := make([]*http.Client, len(proxyList))
for i, p := range proxyList {
clients[i] = buildClient(p)
}
var idx uint64
nextClient := func() *http.Client {
i := atomic.AddUint64(&idx, 1)
return clients[int(i-1)%len(clients)]
}
urls := []string{
"https://httpbin.org/ip",
"https://example.com",
"https://golang.org",
}
var wg sync.WaitGroup
for _, u := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
client := nextClient()
resp, err := client.Get(u)
if err != nil {
fmt.Println("Error for", u, ":", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(u, "->", len(body), "bytes")
}(u)
}
wg.Wait()
}
This pattern spreads requests across multiple datacenter proxies, which helps keep per-IP volume low.
Before you use proxies in a production crawler or pipeline, always verify:
Use any IP echo endpoint:
resp, err := client.Get("https://httpbin.org/ip")
The response should show the proxy’s IP, not your server’s IP.
A simple metrics layer (even just logs parsed by a dashboard) quickly pays off when you’re dealing with large proxy pools.
| Error / Symptom | Likely Cause | Fix |
|---|---|---|
dial tcp: lookup proxyserver: no such host |
Proxy hostname is wrong or DNS issue | Double-check host; try using the proxy’s IP instead |
connect: connection refused |
Proxy service down or wrong port | Verify host:port and that the proxy endpoint is online |
Proxy Authentication Required (407) |
Bad username/password or not whitelisted | Confirm credentials; ensure your server IP is in the allowlist |
Many timeout errors |
Slow proxy, network issues, or too high concurrency | Lower concurrency; add timeouts and retries; switch to healthier IPs |
| Sudden 403 / 429 from target sites | IP flagged or too many requests per IP | Spread load across more proxies; slow down; randomize request patterns |
| TLS handshake / certificate errors | Mixing HTTP proxy with HTTPS target incorrectly | Use an HTTPS-capable proxy endpoint; verify TLS settings |
Logs are critical. Always log the proxy IP (or ID) used for each request so you can correlate issues with specific endpoints.
High-performance Go crawlers can put real load on target systems. Proxies do not remove your responsibility to act responsibly.
Treat proxies as infrastructure, not as a way to ignore ethical or legal constraints.
For most web scraping, monitoring, and QA workloads, HTTP and HTTPS proxies are enough and easier to configure. SOCKS5 becomes useful when your provider only exposes SOCKS endpoints, when you want to tunnel different protocols, or when you need more control over how connections are established.
It depends on your concurrency and the strictness of the target sites. Small jobs may run fine on a handful of datacenter proxies. Larger, always-on crawlers often need dozens or hundreds of IPs to keep per-IP request volume low and reduce block rates. Start small, monitor metrics, and scale based on real-world results.
They are almost never a good idea for production. Free proxy lists are often unstable, slow, or controlled by unknown operators who might inspect or modify traffic. For serious workloads, use reputable paid providers with clean datacenter proxies, clear documentation, and support.
Often yes, but it depends on your organization’s policies. Some environments require outbound traffic to pass through an internal proxy or firewall. Coordinate with your network team so external proxies and ports are allowed and do not violate internal security rules.
Choose proxy locations close to your targets, keep connection pooling enabled through a shared http.Client, and use goroutines to parallelize work. Monitor latency and error rates, and retire underperforming proxies from your rotation. A small amount of extra latency is expected, but you can often offset it with smart concurrency.
Go already gives you the building blocks for fast, robust HTTP clients. Once you layer in net/http proxy settings, SOCKS5 support, sensible timeouts, and concurrency patterns, you can build crawlers and data pipelines that are both scalable and resilient.
Treat proxies as a first-class part of your architecture:
For production-grade deployments, dedicated datacenter proxies from ProxiesThatWork.com pair well with Go services: stable IPs, flexible authentication, and predictable pricing that keeps large-scale automation and data collection running smoothly.

Liam is a network security analyst and software developer specializing in internet privacy, cybersecurity protocols, and performance tuning for proxy and VPN networks. He frequently writes guides and tutorials to help professionals safely navigate the digital landscape.