Build a Go HTTP Proxy from Scratch: Full Guide with Code

Learn how to implement a functional HTTP/HTTPS proxy in Go, covering proxy concepts, forward and reverse types, protocol specifics, header transformations, and a complete, runnable code example that listens on TCP, parses requests, connects to target servers, and forwards traffic.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
Build a Go HTTP Proxy from Scratch: Full Guide with Code

This article explains how to implement an HTTP proxy using Go, providing a practical guide for developers who need proxy functionality in real-world applications.

A proxy acts as an intermediary in a network, forwarding client requests to servers and returning responses, effectively serving as a transit station for network information.

Proxies are classified into forward (client‑side) and reverse (server‑side) proxies.

Forward proxy: the client configures the proxy; the proxy forwards requests to the server and is transparent to the server.

Reverse proxy: the server configures the proxy; the proxy forwards client requests to the server and is transparent to the client.

Different protocols are used for each type:

Forward proxy protocols: http, https, socks4, socks5, vpn.

Reverse proxy protocols: tcp, udp, http, https.

http proxy overview

An http proxy is a simple forward proxy that uses the http protocol for communication between the client and the proxy server.

It can carry http, https, ftp, and other protocols, with slight variations in the request format.

http protocol

Client request headers sent to the proxy:

// direct connection
GET / HTTP/1.1
Host: staight.github.io
Connection: keep-alive

// http proxy
GET http://staight.github.io/ HTTP/1.1
Host: staight.github.io
Proxy-Connection: keep-alive

Key differences compared to a direct connection:

URL becomes a full path (e.g., http://staight.github.io/).

The Connection header is replaced by Proxy-Connection.

All other headers remain unchanged.

Why use the full path?

To identify the target server; without a full URL and Host header the proxy cannot determine where to forward the request.

Why replace Connection with Proxy-Connection?

To maintain compatibility with older HTTP/1.0 proxies that do not understand the keep‑alive semantics of HTTP/1.1.

Interaction diagram for an http proxy:

https protocol

Client request headers for an https proxy (CONNECT method):

CONNECT staight.github.io:443 HTTP/1.1
Host: staight.github.io:443
Proxy-Connection: keep-alive

Differences from the http case:

The request method changes from GET to CONNECT.

The URL does not include a protocol field.

After the CONNECT handshake, the proxy simply forwards encrypted TCP data without modifying the payload.

Interaction diagram for an https proxy:

Code implementation

First, create a TCP listener on port 8080 and handle each incoming connection:

// tcp connection, listen on 8080
l, err := net.Listen("tcp", ":8080")
if err != nil {
    log.Panic(err)
}
for {
    client, err := l.Accept()
    if err != nil {
        log.Panic(err)
    }
    go handle(client)
}

Read the client data into a buffer:

var b [1024]byte
n, err := client.Read(b[:])
if err != nil {
    log.Println(err)
    return
}

Parse the request method and URL, then determine the target address based on the protocol (CONNECT indicates https, otherwise http with a default port of 80 if none is specified):

var method, URL, address string
fmt.Sscanf(string(b[:bytes.IndexByte(b[:], '
')]), "%s%s", &method, &URL)
hostPortURL, err := url.Parse(URL)
if err != nil {
    log.Println(err)
    return
}
if method == "CONNECT" {
    address = hostPortURL.Scheme + ":" + hostPortURL.Opaque
} else {
    address = hostPortURL.Host
    if strings.Index(hostPortURL.Host, ":") == -1 {
        address = hostPortURL.Host + ":80"
    }
}

Establish a TCP connection to the target server and forward data. For CONNECT requests, send an HTTP 200 response to the client first:

server, err := net.Dial("tcp", address)
if err != nil {
    log.Println(err)
    return
}
if method == "CONNECT" {
    fmt.Fprint(client, "HTTP/1.1 200 Connection established

")
} else {
    server.Write(b[:n])
}
go io.Copy(server, client)
io.Copy(client, server)

The complete source code is provided below:

package main

import (
    "bytes"
    "fmt"
    "io"
    "log"
    "net"
    "net/url"
    "strings"
)

func main() {
    l, err := net.Listen("tcp", ":8080")
    if err != nil {
        log.Panic(err)
    }
    for {
        client, err := l.Accept()
        if err != nil {
            log.Panic(err)
        }
        go handle(client)
    }
}

func handle(client net.Conn) {
    if client == nil {
        return
    }
    defer client.Close()
    log.Printf("remote addr: %v
", client.RemoteAddr())
    var b [1024]byte
    n, err := client.Read(b[:])
    if err != nil {
        log.Println(err)
        return
    }
    var method, URL, address string
    fmt.Sscanf(string(b[:bytes.IndexByte(b[:], '
')]), "%s%s", &method, &URL)
    hostPortURL, err := url.Parse(URL)
    if err != nil {
        log.Println(err)
        return
    }
    if method == "CONNECT" {
        address = hostPortURL.Scheme + ":" + hostPortURL.Opaque
    } else {
        address = hostPortURL.Host
        if strings.Index(hostPortURL.Host, ":") == -1 {
            address = hostPortURL.Host + ":80"
        }
    }
    server, err := net.Dial("tcp", address)
    if err != nil {
        log.Println(err)
        return
    }
    if method == "CONNECT" {
        fmt.Fprint(client, "HTTP/1.1 200 Connection established

")
    } else {
        server.Write(b[:n])
    }
    go io.Copy(server, client)
    io.Copy(client, server)
}

After adding the proxy configuration, run the program and point your client to the proxy server.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendProxyGolangCode ExampleNetwork programmingHTTP proxy
MaGe Linux Operations
Written by

MaGe Linux Operations

Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.