Create a Proxy Testing CLI Tool in Go
Go is ideal for proxy testing tools. Its goroutines make concurrent testing trivial, the compiled binary runs on any platform without dependencies, and its standard library includes everything needed for HTTP and SOCKS proxy support.
This tutorial builds a command-line proxy testing tool that checks proxy connectivity, measures latency, detects anonymity level, and outputs results in multiple formats.
Why Go for Proxy Tools
Python proxy checkers work fine for small lists. For testing thousands of proxies quickly, Go’s concurrency model gives you a 5-10x speed advantage. The resulting binary is a single file — no Python interpreter, no virtual environments, no dependency management. Deploy it on any machine by copying one file.
Project Setup
mkdir proxy-tester && cd proxy-tester
go mod init github.com/yourname/proxy-tester
go get golang.org/x/net/proxyData Types
// types.go
package main
import "time"
type ProxyProtocol string
const (
HTTP ProxyProtocol = "http"
HTTPS ProxyProtocol = "https"
SOCKS4 ProxyProtocol = "socks4"
SOCKS5 ProxyProtocol = "socks5"
)
type AnonymityLevel string
const (
Transparent AnonymityLevel = "transparent"
Anonymous AnonymityLevel = "anonymous"
Elite AnonymityLevel = "elite"
Unknown AnonymityLevel = "unknown"
)
type ProxyResult struct {
Proxy string `json:"proxy"`
Protocol ProxyProtocol `json:"protocol"`
Alive bool `json:"alive"`
LatencyMs int64 `json:"latency_ms"`
Anonymity AnonymityLevel `json:"anonymity"`
ExternalIP string `json:"external_ip"`
Country string `json:"country"`
Error string `json:"error,omitempty"`
SupportsSSL bool `json:"supports_ssl"`
CheckedAt time.Time `json:"checked_at"`
}Proxy Tester Core
// tester.go
package main
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"net"
"net/http"
"net/url"
"strings"
"sync"
"time"
"golang.org/x/net/proxy"
)
type ProxyTester struct {
TestURL string
Timeout time.Duration
Concurrency int
MyIP string
}
func NewProxyTester(timeout time.Duration, concurrency int) *ProxyTester {
return &ProxyTester{
TestURL: "https://httpbin.org/ip",
Timeout: timeout,
Concurrency: concurrency,
}
}
func (t *ProxyTester) DetectMyIP() error {
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Get("https://httpbin.org/ip")
if err != nil {
return err
}
defer resp.Body.Close()
var data map[string]string
json.NewDecoder(resp.Body).Decode(&data)
t.MyIP = data["origin"]
return nil
}
func (t *ProxyTester) TestProxy(proxyAddr string) ProxyResult {
result := ProxyResult{
Proxy: proxyAddr,
CheckedAt: time.Now(),
}
// Detect protocol
result.Protocol = t.detectProtocol(proxyAddr)
// Create HTTP client with proxy
client, err := t.createClient(proxyAddr, result.Protocol)
if err != nil {
result.Error = err.Error()
return result
}
// Test connectivity and measure latency
start := time.Now()
resp, err := client.Get(t.TestURL)
if err != nil {
result.Error = err.Error()
return result
}
defer resp.Body.Close()
result.LatencyMs = time.Since(start).Milliseconds()
result.Alive = true
// Parse response for anonymity check
var data map[string]string
json.NewDecoder(resp.Body).Decode(&data)
result.ExternalIP = data["origin"]
// Determine anonymity
result.Anonymity = t.checkAnonymity(result.ExternalIP, resp.Header)
// Test SSL support
result.SupportsSSL = t.testSSL(proxyAddr, result.Protocol)
// Get country (simplified)
result.Country = t.lookupCountry(result.ExternalIP)
return result
}
func (t *ProxyTester) TestBatch(proxies []string) []ProxyResult {
results := make([]ProxyResult, len(proxies))
sem := make(chan struct{}, t.Concurrency)
var wg sync.WaitGroup
for i, p := range proxies {
wg.Add(1)
sem <- struct{}{}
go func(idx int, proxyAddr string) {
defer wg.Done()
defer func() { <-sem }()
results[idx] = t.TestProxy(proxyAddr)
status := "DEAD"
if results[idx].Alive {
status = "ALIVE"
}
fmt.Printf(" [%s] %s — %dms %s\n",
status, proxyAddr,
results[idx].LatencyMs,
results[idx].Country,
)
}(i, p)
}
wg.Wait()
return results
}
func (t *ProxyTester) detectProtocol(addr string) ProxyProtocol {
lower := strings.ToLower(addr)
switch {
case strings.HasPrefix(lower, "socks5://"):
return SOCKS5
case strings.HasPrefix(lower, "socks4://"):
return SOCKS4
case strings.HasPrefix(lower, "https://"):
return HTTPS
default:
return HTTP
}
}
func (t *ProxyTester) createClient(proxyAddr string, protocol ProxyProtocol) (*http.Client, error) {
switch protocol {
case SOCKS5, SOCKS4:
// Strip protocol prefix for SOCKS dialer
addr := strings.TrimPrefix(proxyAddr, string(protocol)+"://")
dialer, err := proxy.SOCKS5("tcp", addr, nil, proxy.Direct)
if err != nil {
return nil, err
}
transport := &http.Transport{
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
return dialer.Dial(network, address)
},
TLSClientConfig: &tls.Config{InsecureSkipVerify: false},
}
return &http.Client{
Transport: transport,
Timeout: t.Timeout,
}, nil
default:
proxyURL, err := url.Parse(proxyAddr)
if err != nil {
return nil, err
}
transport := &http.Transport{
Proxy: http.ProxyURL(proxyURL),
}
return &http.Client{
Transport: transport,
Timeout: t.Timeout,
}, nil
}
}
func (t *ProxyTester) checkAnonymity(visibleIP string, headers http.Header) AnonymityLevel {
if t.MyIP != "" && strings.Contains(visibleIP, t.MyIP) {
return Transparent
}
via := headers.Get("Via")
xff := headers.Get("X-Forwarded-For")
if via != "" || xff != "" {
return Anonymous
}
return Elite
}
func (t *ProxyTester) testSSL(proxyAddr string, protocol ProxyProtocol) bool {
client, err := t.createClient(proxyAddr, protocol)
if err != nil {
return false
}
client.Timeout = 5 * time.Second
resp, err := client.Get("https://httpbin.org/ip")
if err != nil {
return false
}
resp.Body.Close()
return true
}
func (t *ProxyTester) lookupCountry(ip string) string {
cleanIP := strings.Split(ip, ",")[0]
cleanIP = strings.TrimSpace(cleanIP)
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Get(fmt.Sprintf("https://ipinfo.io/%s/json", cleanIP))
if err != nil {
return "unknown"
}
defer resp.Body.Close()
var data map[string]interface{}
json.NewDecoder(resp.Body).Decode(&data)
if country, ok := data["country"].(string); ok {
return country
}
return "unknown"
}CLI Interface
// main.go
package main
import (
"bufio"
"encoding/csv"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"time"
)
func main() {
inputFile := flag.String("input", "", "File with proxy list (one per line)")
outputFile := flag.String("output", "", "Output file (auto-detects format from extension)")
format := flag.String("format", "table", "Output format: table, json, csv")
concurrency := flag.Int("concurrency", 50, "Number of concurrent checks")
timeout := flag.Int("timeout", 10, "Timeout in seconds per proxy")
aliveOnly := flag.Bool("alive-only", false, "Show only alive proxies")
proxy := flag.String("proxy", "", "Test a single proxy")
flag.Parse()
tester := NewProxyTester(
time.Duration(*timeout)*time.Second,
*concurrency,
)
fmt.Println("Detecting your IP...")
if err := tester.DetectMyIP(); err != nil {
fmt.Fprintf(os.Stderr, "Warning: could not detect IP: %v\n", err)
} else {
fmt.Printf("Your IP: %s\n", tester.MyIP)
}
var proxies []string
if *proxy != "" {
proxies = []string{*proxy}
} else if *inputFile != "" {
var err error
proxies, err = readProxyFile(*inputFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading file: %v\n", err)
os.Exit(1)
}
} else {
fmt.Fprintln(os.Stderr, "Usage: proxy-tester -input proxies.txt")
fmt.Fprintln(os.Stderr, " proxy-tester -proxy http://host:port")
flag.PrintDefaults()
os.Exit(1)
}
fmt.Printf("Testing %d proxies with concurrency %d...\n\n", len(proxies), *concurrency)
start := time.Now()
results := tester.TestBatch(proxies)
elapsed := time.Since(start)
if *aliveOnly {
var filtered []ProxyResult
for _, r := range results {
if r.Alive {
filtered = append(filtered, r)
}
}
results = filtered
}
// Summary
alive := 0
var totalLatency int64
for _, r := range results {
if r.Alive {
alive++
totalLatency += r.LatencyMs
}
}
fmt.Printf("\n--- Results ---\n")
fmt.Printf("Total: %d | Alive: %d | Dead: %d\n", len(proxies), alive, len(proxies)-alive)
if alive > 0 {
fmt.Printf("Avg Latency: %dms\n", totalLatency/int64(alive))
}
fmt.Printf("Completed in %.1fs\n\n", elapsed.Seconds())
// Output
switch *format {
case "json":
outputJSON(results, *outputFile)
case "csv":
outputCSV(results, *outputFile)
default:
outputTable(results)
}
}
func readProxyFile(path string) ([]string, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
var proxies []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line != "" && !strings.HasPrefix(line, "#") {
proxies = append(proxies, line)
}
}
return proxies, scanner.Err()
}
func outputTable(results []ProxyResult) {
fmt.Printf("%-40s %-8s %-8s %-12s %-8s %-6s\n",
"PROXY", "STATUS", "LATENCY", "ANONYMITY", "SSL", "COUNTRY")
fmt.Println(strings.Repeat("-", 90))
for _, r := range results {
status := "DEAD"
if r.Alive {
status = "ALIVE"
}
ssl := "no"
if r.SupportsSSL {
ssl = "yes"
}
fmt.Printf("%-40s %-8s %-8d %-12s %-8s %-6s\n",
r.Proxy, status, r.LatencyMs,
r.Anonymity, ssl, r.Country,
)
}
}
func outputJSON(results []ProxyResult, filename string) {
data, _ := json.MarshalIndent(results, "", " ")
if filename != "" {
os.WriteFile(filename, data, 0644)
fmt.Printf("Results written to %s\n", filename)
} else {
fmt.Println(string(data))
}
}
func outputCSV(results []ProxyResult, filename string) {
var writer *csv.Writer
if filename != "" {
file, _ := os.Create(filename)
defer file.Close()
writer = csv.NewWriter(file)
} else {
writer = csv.NewWriter(os.Stdout)
}
defer writer.Flush()
writer.Write([]string{
"proxy", "protocol", "alive", "latency_ms",
"anonymity", "external_ip", "country", "ssl", "error",
})
for _, r := range results {
writer.Write([]string{
r.Proxy,
string(r.Protocol),
fmt.Sprintf("%t", r.Alive),
fmt.Sprintf("%d", r.LatencyMs),
string(r.Anonymity),
r.ExternalIP,
r.Country,
fmt.Sprintf("%t", r.SupportsSSL),
r.Error,
})
}
if filename != "" {
fmt.Printf("Results written to %s\n", filename)
}
}Building and Distribution
Build for multiple platforms:
# Build for current platform
go build -o proxy-tester .
# Cross-compile
GOOS=linux GOARCH=amd64 go build -o proxy-tester-linux-amd64 .
GOOS=darwin GOARCH=arm64 go build -o proxy-tester-darwin-arm64 .
GOOS=windows GOARCH=amd64 go build -o proxy-tester-windows-amd64.exe .Usage Examples
# Test a single proxy
./proxy-tester -proxy http://proxy.example.com:8080
# Test from file
./proxy-tester -input proxies.txt -concurrency 100
# Export alive proxies to JSON
./proxy-tester -input proxies.txt -alive-only -format json -output alive.json
# Export to CSV
./proxy-tester -input proxies.txt -format csv -output results.csvInternal Links
- Building a Proxy Checker Tool — Python alternative
- Proxy Health Monitor with Node.js — continuous monitoring
- Creating a Proxy Benchmarking Suite — advanced benchmarking
- Proxy Troubleshooting Guide — common proxy errors
- Best Proxy Checker Tools 2026 — compare with existing tools
FAQ
How fast is the Go proxy tester compared to Python?
With 100 concurrent goroutines, Go tests 1,000 proxies in 15-20 seconds. An equivalent Python asyncio implementation takes 40-60 seconds. The difference comes from Go’s lightweight goroutines and compiled execution.
Can I test proxies that require authentication?
Yes. Include credentials in the proxy URL: http://user:pass@host:port. The Go HTTP client handles Basic authentication automatically. For SOCKS5 with auth, the golang.org/x/net/proxy package supports username/password authentication.
How do I distribute the binary to my team?
Go produces a single statically-linked binary. No runtime dependencies are required. Upload the binary to a shared drive, GitHub releases, or your package manager. Build for each target platform (Linux, macOS, Windows) using Go’s cross-compilation flags.
What proxy list format does the tool accept?
One proxy URL per line. Lines starting with # are treated as comments. The tool auto-detects the protocol from the URL scheme (http://, socks5://, etc). If no scheme is specified, HTTP is assumed.
How do I add proxy rotation to the tester itself?
The tester checks proxies against a test endpoint, not through another proxy. If you need to test through a chain (proxy through proxy), modify the createClient function to accept a secondary proxy for the outbound connection. This is useful for testing proxy chains.
- Build an Anti-Detection Test Suite: Verify Browser Stealth
- Build a Proxy Rotator in Python: Complete Tutorial
- AJAX Request Interception: Scraping API Calls Directly
- Bandwidth Optimization for Proxies: Reduce Costs & Increase Speed
- How to Configure Proxies on iPhone and Android
- How to Use Proxies in Node.js (Axios, Fetch, Puppeteer)
- Build an Anti-Detection Test Suite: Verify Browser Stealth
- Build a Proxy Rotator in Python: Complete Tutorial
- AJAX Request Interception: Scraping API Calls Directly
- Bandwidth Optimization for Proxies: Reduce Costs & Increase Speed
- How to Configure Proxies on iPhone and Android
- How to Use Proxies in Node.js (Axios, Fetch, Puppeteer)
- Build an Anti-Detection Test Suite: Verify Browser Stealth
- Build a News Crawler in Python: Step-by-Step Tutorial
- AJAX Request Interception: Scraping API Calls Directly
- Azure Functions for Serverless Web Scraping: the Complete Guide
- How to Configure Proxies on iPhone and Android
- How to Use Proxies in Node.js (Axios, Fetch, Puppeteer)
Related Reading
- Build an Anti-Detection Test Suite: Verify Browser Stealth
- Build a News Crawler in Python: Step-by-Step Tutorial
- AJAX Request Interception: Scraping API Calls Directly
- Azure Functions for Serverless Web Scraping: the Complete Guide
- How to Configure Proxies on iPhone and Android
- How to Use Proxies in Node.js (Axios, Fetch, Puppeteer)