Skip to content
1 GB of residential proxies, free. $0 today, card required.
Transport routing runbook

Golang HTTP Proxy: Go net/http Transport and SOCKS5 Setup

Route Go net/http through an authenticated proxy with Transport.Proxy, ProxyFromEnvironment or a SOCKS5 dialer, verify the observed exit, and diagnose CONNECT 407, deadline and DNS errors by hop.

Go net/http → DatabayOne Transport per route, one proxy decision per request
Go net/httpHTTP client library
Databay gateway modesClient support is covered below
gw.databay.co:8888
By Databay Research Team10 min runbook

Operating principle

Give each Databay username one reused Transport, let Transport.Proxy or a SOCKS5 dialer own the proxy hop, and observe the exit before real traffic.

How Go net/http Decides Whether a Request Uses a Proxy

In Go, proxy routing belongs to the http.Transport. For each request it calls its Proxy function, and the documentation is explicit: if Proxy is nil or returns a nil *url.URL, no proxy is used. http.DefaultTransport sets Proxy: http.ProxyFromEnvironment, but a Transport you construct starts with a nil Proxy and silently ignores the environment. A custom Proxy function should return an error, which aborts the request, rather than nil when it cannot choose a route.

Through an http proxy, http:// destinations are forwarded in absolute form, while https:// destinations get a CONNECT tunnel with destination TLS negotiated end to end inside it. Databay's gateway, gw.databay.co:8888, serves HTTP proxying, HTTPS CONNECT and SOCKS5, with no UDP; Go's https proxy scheme, which means TLS to the proxy itself, is not one of those modes. Route only traffic to destinations you own or are permitted to access, and follow each destination's terms.

Map Databay Gateway Values to Go Transport Fields

Each row pairs a Databay value with a documented control and the evidence to check.

Go net/http controlDatabay valueWhat to verify
Transport.Proxy set to http.ProxyURL(u)u.Scheme http, u.Host gw.databay.co:8888Same URL for every request; NO_PROXY is never consulted
u.User from url.UserPasswordDashboard username and password via DATABAY_PROXY_USER and DATABAY_PROXY_PASSBoth loaded at start-up; neither printed
Transport.ProxyConnectHeaderOptional headers for CONNECT onlyNot sent for http:// destinations
Transport.OnProxyConnectResponseA logging hook, no secretCONNECT status recorded before a non-200 becomes an error
http.ProxyFromEnvironmentHTTPS_PROXY, HTTP_PROXY, NO_PROXYScheme-matched variable set; no exclusion matches
proxy.SOCKS5 in Transport.DialContext"tcp", gw.databay.co:8888, proxy.AuthTransport.Proxy is nil, so no HTTP hop is stacked

Routing lives in the username. The documented residential example is USER-zone-residential-countryCode-us, and USER-zone-residential-countryCode-us-sessionId-abc123 adds a sticky session. Copy the complete username the dashboard shows rather than inventing segments for other pools. Premium Residential accepts one of country, state, city, ZIP, coordinate or ASN targeting at a time; Residential Flex, Datacenter and Mobile target a country or continent. All pools are shared.

A Complete Go Program With Environment Credentials

The program clones http.DefaultTransport to keep its documented dial, TLS handshake and HTTP/2 defaults, then changes only the proxy decision and pool settings. Credentials come from variables your secret manager injects.

package main

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"time"
)

func newGatewayClient(user, pass string) *http.Client {
	proxyURL := &url.URL{
		Scheme: "http",
		Host:   "gw.databay.co:8888",
		User:   url.UserPassword(user, pass),
	}
	transport := http.DefaultTransport.(*http.Transport).Clone()
	transport.Proxy = http.ProxyURL(proxyURL)
	transport.MaxIdleConns = 20
	transport.IdleConnTimeout = 60 * time.Second
	return &http.Client{Transport: transport, Timeout: 30 * time.Second}
}

func run() error {
	user, pass := os.Getenv("DATABAY_PROXY_USER"), os.Getenv("DATABAY_PROXY_PASS")
	if user == "" || pass == "" {
		return errors.New("proxy credentials are not set")
	}
	client := newGatewayClient(user, pass)

	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
	defer cancel()
	req, err := http.NewRequestWithContext(ctx, http.MethodGet,
		"https://api.ipify.org?format=json", nil)
	if err != nil {
		return err
	}
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("via gw.databay.co:8888: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("destination answered %s", resp.Status)
	}
	var observed struct {
		IP string `json:"ip"`
	}
	if err := json.NewDecoder(io.LimitReader(resp.Body, 4096)).Decode(&observed); err != nil {
		return err
	}
	fmt.Println("observed exit:", observed.IP)
	return nil
}

func main() {
	if err := run(); err != nil {
		log.Fatal(err)
	}
}

url.UserPassword stores raw values, so a password containing @ or : needs no hand-encoding, and the Transport derives Proxy-Authorization from that userinfo. Errors from client.Do name the destination, so the program adds the gateway host instead of formatting proxyURL, whose String method includes the password. The 4,096-byte read cap matters because Databay bills traffic per GB.

Ready to run this for real?

Credentials appear in your dashboard in minutes, on the same gateway and username format this Go net/http guide uses.

ProxyFromEnvironment, HTTPS_PROXY and NO_PROXY Rules

http.ProxyFromEnvironment reads HTTP_PROXY, HTTPS_PROXY and NO_PROXY, or their lowercase versions, and uses the variable matching the destination scheme: https:// requests consult only HTTPS_PROXY, so setting HTTP_PROXY alone leaves them direct. A value may be a full URL or host[:port], which assumes http. Requests to localhost and, per the httpproxy documentation, loopback addresses never use it, so a local test server proves nothing about the gateway.

NO_PROXY takes comma-separated IP addresses, CIDR prefixes, domain names that also match their subdomains, leading-dot names that match subdomains only, optional ports, or a single * that disables proxying. A stale inherited entry makes a correct client look broken. Current releases read the environment once per process, so set it before start-up. Authenticating this way puts the secret inside the value, as in HTTPS_PROXY=http://USER:PASS@gw.databay.co:8888, shown here only so you can recognize it, not as a recommendation: child processes inherit it and environment dumps capture it.

Carry Proxy Credentials in Go Without Leaking Them

Userinfo on the proxy URL covers both request shapes: absolute-form requests for http:// destinations and CONNECT for https://. Transport.ProxyConnectHeader applies to CONNECT only; GetProxyConnectHeader, its dynamic form, overrides it when set. Carrying the credential there keeps the URL free of secrets, but http:// destinations on that Transport then go unauthenticated, so use one carrier per Transport.

// HTTPS destinations only: the proxy URL carries no userinfo.
token := base64.StdEncoding.EncodeToString([]byte(user + ":" + pass))
transport.Proxy = http.ProxyURL(&url.URL{Scheme: "http", Host: "gw.databay.co:8888"})
transport.ProxyConnectHeader = http.Header{"Proxy-Authorization": {"Basic " + token}}

// Log the CONNECT status before a non-200 becomes an error.
transport.OnProxyConnectResponse = func(ctx context.Context, proxyURL *url.URL,
	connectReq *http.Request, connectRes *http.Response) error {
	log.Printf("CONNECT %s via %s: %d", connectReq.Host, proxyURL.Host, connectRes.StatusCode)
	return nil
}

Keep both values in a secret manager, inject them at runtime, and never commit, screenshot or log them. Do not print the Transport, the proxy URL or the header map. URL.Redacted masks only the password, leaving the routing username visible.

SOCKS5 With golang.org/x/net/proxy or the socks5 Scheme

Transport.Proxy accepts socks5 URLs and treats socks5 the same as socks5h, so http.ProxyURL with Scheme: "socks5" and the same userinfo works without extra modules. For an explicit dialer, golang.org/x/net/proxy provides SOCKS5, which returns a Dialer for RFC 1928 connections with optional username and password authentication. This variant replaces newGatewayClient; run go get golang.org/x/net and add the net and proxy imports:

func newSOCKS5Client(user, pass string) (*http.Client, error) {
	forward := &net.Dialer{Timeout: 10 * time.Second, KeepAlive: 30 * time.Second}
	dialer, err := proxy.SOCKS5("tcp", "gw.databay.co:8888",
		&proxy.Auth{User: user, Password: pass}, forward)
	if err != nil {
		return nil, err
	}
	contextDialer, ok := dialer.(proxy.ContextDialer)
	if !ok {
		return nil, errors.New("SOCKS5 dialer lacks DialContext")
	}
	transport := http.DefaultTransport.(*http.Transport).Clone()
	transport.Proxy = nil // SOCKS5 is the only proxy hop
	transport.DialContext = contextDialer.DialContext
	return &http.Client{Transport: transport, Timeout: 30 * time.Second}, nil
}

Setting Proxy to nil matters: the clone inherits ProxyFromEnvironment, and an inherited HTTPS_PROXY would be dialed through the SOCKS tunnel as a stacked hop. The ContextDialer assertion supplies DialContext, so request deadlines also bound the SOCKS handshake. On either path, destination host names go to the gateway for resolution, and only TCP is carried. The HTTP versus SOCKS5 comparison covers the trade-offs.

Reuse One Go Client per Route and Bound Every Request

The net/http documentation says Clients and Transports are safe for concurrent use and should be created once and reused. Build one Transport per proxy configuration, meaning one per Databay username, and share it across goroutines. MaxIdleConns caps idle connections across hosts, MaxIdleConnsPerHost falls back to 2 when unset, and IdleConnTimeout closes idle connections after the interval. Read bodies to EOF and close them so connections can be reused.

Reuse affects routing: a new proxy connection may select an available exit, while a reused connection or tunnel, including one HTTP/2 connection multiplexing many requests, may retain the same exit. Neither outcome is promised, and DisableKeepAlives forces new connections, not new IPs. Client.Timeout covers connection time, redirects and the body read; a context from http.NewRequestWithContext controls the entire lifetime of one request. Use the context per call and Client.Timeout as a backstop. Retry only idempotent requests, and never retry or switch sessions on 401, 403, 407 or 429.

Verify the Observed Exit Before the Real Destination

  1. Baseline: request https://api.ipify.org?format=json once with a nil Proxy and note the address.
  2. Gateway: run the program. A result equal to the baseline means the request went direct: check for a nil Proxy, a NO_PROXY match or a Proxy function returning nil.
  3. Second observer: request https://databay.com/what-is-my-ip/json through the same client. A rotating username may show another exit there, because a new tunnel may select one.
  4. Continuity: for a sessionId username, repeat at checkpoints inside the session window (up to 1440 minutes on Premium Residential and Residential Flex where available, or up to 120 minutes on Datacenter and Mobile). Matches show continuity for those observations only because the IP can change before the window ends.
  5. Destination: only then call the authorized destination, with the same client.

Log the Go version, UTC time, route class and observed IP, never the password. An observed exit is point-in-time evidence, not proof of anonymity. The What Is My IP page gives a second observation outside your code.

Troubleshoot Go Proxy Errors by Hop: 407, Deadlines, DNS

Proxy-hop failures (gateway DNS, TCP, authentication, CONNECT or SOCKS negotiation) occur before any destination byte moves. Destination-hop failures (TLS inside the tunnel, then the HTTP status) mean the gateway did its part.

EvidenceHopNext safe check
proxyconnect tcp: dial tcp: lookup gw.databay.co ending in no such hostProxy hop: gateway DNSThrough an HTTP proxy only the gateway name resolves locally. On Unix, GODEBUG=netdns=go+1 or cgo+1 prints the resolver decision.
proxyconnect tcp with i/o timeout or a refused connectionProxy hop: TCPEgress rules for port 8888
HTTPS destination: Do returns an error with a reason phrase such as Proxy Authentication RequiredProxy hop: CONNECT authenticationStatus from OnProxyConnectResponse, loaded variables, complete username
HTTP destination: resp.StatusCode equals http.StatusProxyAuthRequiredProxy hop: absolute-form authenticationUserinfo present; ProxyConnectHeader does not cover http://
Error text containing socks connectProxy hop: SOCKS5 dial or authentication (RFC 1929)Read the wrapped cause; repeat the checks above
context deadline exceeded or a Client.Timeout messageWhichever phase was runningTrace the phases; retest once with a justified budget
tls: failed to verify certificate or another x509 errorDestination hop: TLSTrust store, such as a missing CA bundle
Destination 401, 403 or 429Destination hop: HTTP statusStop; honor Retry-After and the access rules

To place a deadline, attach an httptrace.ClientTrace to the request context. For an HTTPS destination, ConnectDone reports the gateway dial and TLSHandshakeStart fires only after the gateway processes CONNECT, so a stall between them is a proxy-hop problem. Never set InsecureSkipVerify to make a request pass; the crypto/tls documentation warns that TLS is then susceptible to machine-in-the-middle attacks. To compare outside Go, reproduce one request with the cURL proxy runbook.

Troubleshooting desk

Questions specific to Go net/http

Does Go net/http use HTTP_PROXY automatically?
Only through http.ProxyFromEnvironment, which http.DefaultTransport and therefore http.DefaultClient use. A Transport you construct starts with a nil Proxy field and ignores the variables until you set Proxy. HTTPS destinations read HTTPS_PROXY, not HTTP_PROXY.
How do I pass proxy credentials to a Go http.Client?
Set the User field of the proxy URL with url.UserPassword, using values injected from a secret store, and Go derives a Proxy-Authorization header from that userinfo. For HTTPS-only clients, Transport.ProxyConnectHeader can carry the header on CONNECT instead. Never log the URL or the header map.
Why does Go report Proxy Authentication Required instead of a 407 response?
For an https:// destination the 407 answers the CONNECT request, so client.Do returns an error rather than a Response. Use Transport.OnProxyConnectResponse to record the status code. For an http:// destination the 407 arrives as a normal Response with StatusCode 407.
Does http.Transport accept socks5:// proxy URLs?
Yes. The Transport.Proxy documentation lists http, https, socks5 and socks5h as supported schemes and says socks5 is treated the same as socks5h. golang.org/x/net/proxy remains useful when you want an explicit SOCKS5 dialer wired into DialContext.
What does context deadline exceeded mean when a Go request uses a proxy?
The request context expired during some phase: the gateway dial, the CONNECT exchange, destination TLS, waiting for headers or reading the body. Attach an httptrace.ClientTrace to see which hooks fired, then fix that hop instead of raising every timeout.
Should I set InsecureSkipVerify when a proxied Go request fails TLS?
No. Proxy routing never requires it. A certificate error inside a CONNECT tunnel comes from the destination or the local trust store, so install the correct CA bundle and keep verification enabled.
Adjacent runbooks

Same gateway, different control surface

  • cURL

    Command-line client

    curl -> proxy auth -> CONNECT or SOCKS -> approved destination

  • Python Requests

    HTTP client library

    Session -> HTTPAdapter policy -> proxy hop -> approved destination

  • Node.js fetch

    HTTP client library

    fetch -> env opt-in or same-package dispatcher -> proxy evidence

  • .NET HttpClient

    HTTP client library

    HttpClient -> SocketsHttpHandler.Proxy -> CONNECT or SOCKS5 -> observed exit

  • Java HttpClient

    HTTP client library

    HttpClient.Builder -> ProxySelector.of -> Authenticator on 407 -> observed exit

  • PHP Guzzle and cURL

    HTTP client library

    Guzzle option or CURLOPT -> libcurl handle -> CONNECT or SOCKS5 -> observed exit

Pick the network once the route works. Residential for home-connection exits with state, city, ZIP and ASN targeting; datacenter for fast server IPs on public websites. Switching is one word in the username.

Residential →Datacenter →

Where teams run Go net/http behind the gateway: Proxies for web scraping · Proxies for market research

Ship Go net/http to production

Create an account, drop in your gateway credentials, and route your first Go net/http request in minutes.

Pricing, order minimums, and traffic validity vary by product.