All guides
DEVELOPER GUIDES · 8 MIN READ

How to configure a Go HTTP proxy

Route Go net/http through an explicit proxy, keep credentials out of logs, cap response work, and separate proxy failures from target responses.

How to configure a Go HTTP proxy

Quick answer: assign an explicit proxy function to Go's Hypertext Transfer Protocol (HTTP) transport, http.Transport, then use a bounded http.Client and validate the response body. For HTTPS, a proxy CONNECT challenge can surface as a transport error before Go returns an HTTP response.

Prerequisites

  • Go 1.27.1 (tested, current supported baseline); the example uses only the standard library.
  • An HTTP or HTTPS proxy endpoint authorized for this request, with credentials supplied through a secret manager or runtime environment bindings.
  • A permitted HTTPS target and a stable marker or parsed field that defines a useful response.

Save the complete example as main.go. Set PROXY_URL to the endpoint without user information. The program inserts a supplied PROXY_USER and PROXY_PASSWORD into proxy authentication in memory; the client sends them to the proxy, not the target. Set both variables together when authentication is required. The defaults use https://example.com/ and Example Domain; TARGET_URL and EXPECTED_MARKER allow an authorized test target. Run go run main.go. The example makes one request and does not retry errors.

Warning: Send one bounded request to a permitted test target and confirm its status and expected content before enabling production jobs. With Basic proxy authentication and an http:// proxy URL, credentials cross the client-to-proxy hop without TLS. CONNECT only establishes the tunnel; TLS to the target encrypts application traffic carried inside it, not the earlier proxy login. Go supports an https:// proxy URL for TLS to the proxy; use it only when the endpoint supports TLS and its certificate validates. Keep credentials in a secret manager or runtime binding, rotate them if exposed, and check account usage after unexpected traffic.

Configure an explicit HTTP transport

http.ProxyURL creates a proxy function for a fixed URL. The transport uses that function for HTTP requests and for an HTTPS target reached through CONNECT. Go also supports proxy selection from HTTP_PROXY, HTTPS_PROXY, and NO_PROXY through ProxyFromEnvironment, but an explicit URL makes this one request's route visible in code.

The baseline uses only the standard library and was tested with Go 1.27.1. The standard net/http.Transport accepts https:// proxy URLs to protect the client-to-proxy hop; the example's PROXY_URL can use either http:// or https:// with the corresponding endpoint port. It expects these environment bindings:

PROXY_URL=http://proxy.example:3128
PROXY_USER=<PROXY_USER>
PROXY_PASSWORD=<PROXY_PASSWORD>

Keep the URL scheme, host, and port in PROXY_URL. Set these uppercase environment variables from a secret store or process environment. Do not put user:password@ in a shared configuration value.

Save this as main.go. The transport bounds connection setup at 5 seconds and TLS/header phases at 10 seconds; the client timeout is 15 seconds (timeout guide). The target check accepts only 200 OK, while a CONNECT 407 appears as a transport error in the tested Go version:

package main

import (
	"context"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"
)

const maxBodyBytes = int64(1 << 20)

func requiredEnv(name string) string {
	value := strings.TrimSpace(os.Getenv(name))
	if value == "" {
		panic("missing required environment binding: " + name)
	}
	return value
}

func readBody(response *http.Response) (string, error) {
	defer response.Body.Close()

	body, err := io.ReadAll(io.LimitReader(response.Body, maxBodyBytes+1))
	if err != nil {
		return "", err
	}
	if int64(len(body)) > maxBodyBytes {
		return "", fmt.Errorf("response body exceeded local cap")
	}
	return string(body), nil
}

func main() {
	proxyURL, err := url.Parse(requiredEnv("PROXY_URL"))
	if err != nil || proxyURL.Host == "" || proxyURL.User != nil || (proxyURL.Scheme != "http" && proxyURL.Scheme != "https") {
		panic("PROXY_URL must use an HTTP or HTTPS scheme, host, and no credentials")
	}

	proxyUser, hasUser := os.LookupEnv("PROXY_USER")
	proxyPassword, hasPassword := os.LookupEnv("PROXY_PASSWORD")
	if hasUser != hasPassword || (hasUser && (proxyUser == "" || proxyPassword == "")) {
		panic("PROXY_USER and PROXY_PASSWORD must be supplied together and non-empty")
	}
	if hasUser {
		proxyURL.User = url.UserPassword(proxyUser, proxyPassword)
	}

	transport := &http.Transport{
		Proxy:                 http.ProxyURL(proxyURL),
		DialContext:           (&net.Dialer{Timeout: 5 * time.Second}).DialContext,
		TLSHandshakeTimeout:   10 * time.Second,
		ResponseHeaderTimeout: 10 * time.Second,
		MaxIdleConns:          8,
		MaxIdleConnsPerHost:   4,
	}
	defer transport.CloseIdleConnections()

	client := &http.Client{
		Transport: transport,
		Timeout:   15 * time.Second,
		CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
			return http.ErrUseLastResponse
		},
	}

	target := os.Getenv("TARGET_URL")
	if target == "" {
		target = "https://example.com/"
	}
	expectedMarker := os.Getenv("EXPECTED_MARKER")
	if expectedMarker == "" {
		expectedMarker = "Example Domain"
	}
	targetURL, err := url.Parse(target)
	if err != nil || targetURL.Host == "" || targetURL.User != nil || (targetURL.Scheme != "http" && targetURL.Scheme != "https") {
		panic("TARGET_URL must be an HTTP or HTTPS URL without credentials")
	}
	request, err := http.NewRequestWithContext(
		context.Background(),
		http.MethodGet,
		target,
		nil,
	)
	if err != nil {
		panic("request configuration failed")
	}

	response, err := client.Do(request)
	if err != nil {
		panic("request failed in transport or proxy negotiation")
	}

	status := response.StatusCode
	body, bodyErr := readBody(response)
	if bodyErr != nil {
		panic("response body failed local acceptance checks")
	}
	if status != http.StatusOK {
		panic("target returned a non-accepted HTTP status")
	}
	if !strings.Contains(body, expectedMarker) {
		panic("target marker was absent")
	}

	fmt.Printf("status=%d marker=present\n", status)
}

Expected output for a successful response:

status=200 marker=present

The Client.Timeout covers connection, redirects, and body reading. The transport timeouts give the failure a more useful phase, while CloseIdleConnections releases pooled sockets when this short-lived program exits. In a worker, reuse one configured client and transport for a bounded unit of work instead of creating a transport per request.

This example makes one attempt. For an idempotent GET, add retries only after classifying the failure, honoring a valid Retry-After rate-limit instruction, and setting a job-level deadline. Do not retry transport, TLS, configuration, or proxy-authentication failures without correcting the cause. With Go 1.27.1, an HTTPS CONNECT 407 returned an error from Client.Do before a response object. The standalone program uses panic to keep this short example's failure paths visible, but production handlers should return a classified error to the application boundary and decide there whether to retry; do not log proxy URLs, credentials, or raw transport errors. Do not copy retries to a request with side effects.

Accept only a useful result

The example accepts one result only when all three checks pass:

input:  permitted target URL + expected marker + explicit proxy binding
output: status=200, marker present, body <= 1 MiB

status=200 is an HTTP result, not proof that the record required by the application is present. The marker is intentionally simple; replace it with the field, schema, or checksum required by the job. The body cap is an application bound, not a proxy-account quota. To verify the exit IP, send a request through the same http.Client to a trusted IP-check endpoint with no target credentials. Compare the response with the assigned exit IP; this checks only the request path to that endpoint.

Observation Boundary Next decision
PROXY_URL rejected before a request Local configuration Check scheme, host, port, and secret binding without printing values
Transport error or CONNECT failure Client to proxy or tunnel Check reachability, proxy protocol, TLS, and proxy credentials; stop before retrying
407 Proxy authentication Repair proxy auth or entitlement; do not treat it as a target response or keep retrying the same secret
401 or 403 after a successful tunnel Target policy Check target credentials, permission, or target-side rules
429 Target rate limit Follow the rate-limit and Retry-After guide and reduce request rate; do not change exits to evade the limit
3xx redirect Target response Inspect Location and permit the destination before sending a new request
200 with no marker Application validation Reject the record and inspect the target response or parser
Body exceeds 1 MiB Local resource policy Reject or stream with a job-specific limit; do not silently raise the cap

For a wider protocol comparison, see HTTP versus SOCKS5 proxies. The curl proxy guide is useful for a hop-by-hop terminal probe.

In the worker, reuse this http.Client and http.Transport for requests in the bounded job, and return classified failures to the job boundary instead of calling panic.

Sources and further reading

ProxyLaneTraffic never expires

Premium residential proxies
From $2/GB

Scale your workflows with the tools you already use. Rotating or sticky sessions up to 72 hours.

Try 350 MB for $1.95 View plans

Keep reading

Amazon Scraper API vs Proxy: Choose a Product Data Source

Compare seller APIs, licensed Amazon product data and proxy page checks by record, access scope and reuse rights.

Read guide

Antidetect browser proxy setup: choose a profile guide

Choose the right anti-detect browser proxy guide, map the shared verification steps, and keep account access separate from the network route.

Read guide

Best Proxies for Market Research: Choose by Sample and Cost

Our picks for market research: ProxyLane for small, intermittent projects, plus options for regional tracking, monthly collection and managed extraction.

Read guide