Around Go: Gin services · 3 MIN READ

Trusted proxies: forwarded headers are not proof of identity

Configure proxy trust explicitly and keep client IP separate from authentication.

A reverse proxy may supply the original client address, but a direct client can also send X-Forwarded-For. If a Go service trusts forwarding headers from every peer, address-based controls can be bypassed. Configure exactly which proxy origins are trusted and make the edge replace or sanitize incoming forwarding headers. An IP address remains a network observation, not a user credential.

gin · A MAP OF THE IDEA

  1. Observe actual peer
  2. Check proxy trust
  3. Derive network address
  4. Authorize principal separately

Useful when

  • Configure proxy trust explicitly and keep client IP separate from authentication.
  • Makes the owner and the failure boundary explicit.

Watch out

  • Trusting 0.0.0.0/0 or accepting a header called X-User is not authentication.
  • Run native service and failure tests in your own development environment.

Implementation & operation cost: Additional state and tests must be justified by the demonstrated failure; this lesson is not a requirement for every Go program.

01

The contract and the failure

A reverse proxy may supply the original client address, but a direct client can also send X-Forwarded-For. If a Go service trusts forwarding headers from every peer, address-based controls can be bypassed. Configure exactly which proxy origins are trusted and make the edge replace or sanitize incoming forwarding headers. An IP address remains a network observation, not a user credential.

02

Work through the example

Write down the chain of load balancers and ingress proxies. Restrict direct access to the service where practical, configure Gin trust and verify how the edge processes preexisting forwarding headers. Use TLS according to the actual termination boundary. Rate-limit authenticated identities and service capacity as well as addresses; many legitimate users can share one NAT address.

go
package main

import (
	"fmt"
	"net/http/httptest"

	"github.com/gin-gonic/gin"
)

func main() {
	gin.SetMode(gin.TestMode)
	r := gin.New()
	if err := r.SetTrustedProxies(nil); err != nil {
		panic(err)
	}
	r.GET("/ip", func(c *gin.Context) { c.String(200, c.ClientIP()) })
	req := httptest.NewRequest("GET", "/ip", nil)
	req.RemoteAddr = "192.0.2.10:4321"
	req.Header.Set("X-Forwarded-For", "203.0.113.77")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	fmt.Println(w.Body.String()) // 192.0.2.10, not the supplied header
}
Read the code

With trusted proxies disabled, ClientIP uses the peer address and ignores the claimed forwarded client address. The documentation-only addresses are reserved example ranges. Behind a real controlled proxy, configure its actual narrow address ranges instead of copying these values or trusting all private networks.

03

Production decisions

Trusting 0.0.0.0/0 or accepting a header called X-User is not authentication. Forwarded host and scheme can also affect generated URLs and redirects. Test a direct spoofed request, a legitimate proxy, multiple forwarded hops and IPv6. Keep proxy configuration immutable while the engine serves traffic and review it after topology changes.

04

Explain it in an interview

The service has no proxy. Should it trust X-Forwarded-For supplied by clients? No. Disable proxy trust when unused; behind a proxy trust only controlled peers and keep authentication separate from client IP. Describe the state before the operation, its owner, the failure between steps and the evidence you would test. Distinguish what this example demonstrates from the infrastructure a production service still needs.

TAKE THIS WITH YOU

Configure proxy trust explicitly and keep client IP separate from authentication. No. Disable proxy trust when unused; behind a proxy trust only controlled peers and keep authentication separate from client IP.

Check your understanding

The service has no proxy. Should it trust X-Forwarded-For supplied by clients?

Go to the source

Progress stays in this browser. No account needed.