How to Configure Trusted Proxies and Get Real Client IP in Gin

Use Engine.SetTrustedProxies() to whitelist specific IP ranges or CIDRs, and call c.ClientIP() to retrieve the real client address, ensuring you replace the default "trust all" configuration before deploying to production.

The gin-gonic/gin web framework provides a sophisticated, layered mechanism to determine the real client IP address when your application runs behind reverse proxies, load balancers, or CDNs. By default, Gin trusts all incoming proxy headers, which creates a security vulnerability in production environments. This guide explains how to configure trusted proxies and get real client IP in Gin using the actual source implementation from gin.go and context.go.

How Gin Resolves Client IP Addresses

Gin determines the client IP through a strict hierarchy implemented in Context.ClientIP (context.go, lines 70–124). Understanding this flow is essential for secure configuration:

  1. Trusted Platform Header – If Engine.TrustedPlatform is set, Gin extracts the IP from the specified header (e.g., CF-Connecting-IP) immediately, bypassing proxy validation.
  2. Legacy App Engine – Checks the deprecated X-Appengine-Remote-Addr header (deprecated in favor of TrustedPlatform).
  3. Trusted Proxy Validation – Compares the remote address against engine.trustedCIDRs using isTrustedProxy (gin.go, lines 68–70).
  4. Header Inspection – If the remote IP is trusted, Gin inspects RemoteIPHeaders (default: X-Forwarded-For, X-Real-IP) and validates the chain using validateHeader (gin.go, lines 81–99), walking backwards through X-Forwarded-For until it finds the first untrusted address.
  5. Fallback – Returns c.Request.RemoteAddr if no trusted proxy information exists.

The Default Security Risk

By default, defaultTrustedCIDRs in gin.go (lines 39–48) trusts the entire IPv4 and IPv6 address space (0.0.0.0/0 and ::/0). The isUnsafeTrustedProxies method (lines 56–58) flags this configuration as unsafe because it allows any client to spoof their IP address via headers.

Configuring Trusted Proxies

Whitelisting Specific Networks with SetTrustedProxies

The Engine.SetTrustedProxies method (gin.go, lines 43–55) accepts a slice of IP addresses or CIDR notation strings. Behind the scenes, prepareTrustedCIDRs (lines 14–41) parses these entries, automatically converting plain IPs to /32 (IPv4) or /128 (IPv6) masks.

package main

import (
	"fmt"

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

func main() {
	r := gin.Default()

	// Trust only your load balancer or internal proxy subnet
	if err := r.SetTrustedProxies([]string{
		"192.168.1.0/24",    // entire subnet
		"10.0.0.5",          // single IPv4 (becomes 10.0.0.5/32)
		"2001:db8::/32",     // IPv6 CIDR
	}); err != nil {
		panic(err) // handles invalid CIDR formats
	}

	r.GET("/", func(c *gin.Context) {
		// Returns the leftmost trusted IP from X-Forwarded-For
		fmt.Printf("Real client IP: %s\n", c.ClientIP())
	})
	r.Run(":8080")
}

Disabling Proxy Trust Entirely

If your Gin application runs without any reverse proxy (directly exposed to the internet), disable header parsing to prevent spoofing:

r := gin.Default()
r.SetTrustedProxies(nil) // disables all proxy header checks

r.GET("/", func(c *gin.Context) {
	// Returns the raw TCP connection address
	fmt.Println("IP:", c.ClientIP())
})

When trustedCIDRs is nil, isTrustedProxy always returns false, causing ClientIP to skip header inspection and return RemoteAddr directly.

Using Trusted Platform Headers for CDNs

When running behind a managed CDN like Cloudflare, AWS CloudFront, or Fly.io, use the TrustedPlatform field to skip CIDR validation and trust a specific header unconditionally. This is implemented at the top of Context.ClientIP in context.go.

r := gin.Default()

// Use Cloudflare's specific header
r.TrustedPlatform = gin.PlatformCloudflare

r.GET("/", func(c *gin.Context) {
	// Reads CF-Connecting-IP without checking proxy CIDRs
	fmt.Println("Client IP from Cloudflare:", c.ClientIP())
})

Available constants include gin.PlatformGoogleAppEngine, gin.PlatformCloudflare, and gin.PlatformFlyIO. You can also specify a custom string:

r.TrustedPlatform = "X-CDN-Client-IP"

Security Warning: Ensure your CDN strips the chosen header from inbound client requests. If clients can send X-CDN-Client-IP directly, they can forge any IP address.

Customizing Header Inspection

You can modify which headers Gin inspects by setting Engine.RemoteIPHeaders. By default, this slice contains X-Forwarded-For and X-Real-IP. Context.ClientIP iterates this list in order, using validateHeader to ensure each IP in the chain belongs to a trusted proxy.

r := gin.Default()
r.SetTrustedProxies([]string{"10.0.0.0/8"})

// Prioritize custom CDN header, then standard headers
r.RemoteIPHeaders = []string{
	"True-Client-IP",    // Cloudflare/CloudFront alternative
	"X-Real-IP",         // Nginx standard
	"X-Forwarded-For",   // Generic proxy chain
}

The validateHeader function walks the X-Forwarded-For chain backwards (right-to-left), stopping when it encounters an IP that does not match trustedCIDRs, thereby preventing clients from injecting fake IPs after the first legitimate proxy.

Key Implementation Details

CIDR Parsing and Validation

The prepareTrustedCIDRs function in gin.go (lines 14–41) handles the heavy lifting:

  • Validates CIDR format using net.ParseCIDR
  • Converts plain IPs to proper subnet masks (10.0.0.510.0.0.5/32)
  • Returns []*net.IPNet stored in engine.trustedCIDRs

Proxy Trust Verification

isTrustedProxy (lines 68–70) iterates the parsed CIDRs using net.IPNet.Contains to check if the remote address falls within a trusted range.

Header Chain Validation

validateHeader (lines 81–99) specifically handles the X-Forwarded-For comma-separated format. It parses each IP, checks trustworthiness against trustedCIDRs, and returns the first (leftmost) IP in the chain that is not itself a trusted proxy—effectively finding the original client.

Summary

  • Never deploy with default settings: The default 0.0.0.0/0 and ::/0 CIDRs allow IP spoofing; always call SetTrustedProxies or set TrustedPlatform.
  • Whitelist precisely: Use SetTrustedProxies([]string{"CIDR", "IP"}) to define exactly which infrastructure can forward client headers.
  • Disable when unnecessary: Pass nil to SetTrustedProxies when running without proxies to prevent header parsing entirely.
  • Leverage CDN headers: Use TrustedPlatform for managed services to bypass CIDR checks and trust verified infrastructure headers.
  • Validate the chain: Gin automatically walks the X-Forwarded-For chain backwards via validateHeader to prevent injection attacks once properly configured.

Frequently Asked Questions

How does Gin determine the client IP address by default?

By default, Gin trusts all proxies (0.0.0.0/0 and ::/0) and checks X-Forwarded-For and X-Real-IP headers. The ClientIP method in context.go extracts the leftmost IP from these headers if the immediate remote address is trusted, falling back to RemoteAddr if not. This default is flagged as unsafe by isUnsafeTrustedProxies in gin.go (lines 56–58) because it allows any client to spoof their IP.

What is the difference between SetTrustedProxies and TrustedPlatform?

SetTrustedProxies configures a whitelist of IP ranges (CIDRs) that are permitted to send forwarding headers like X-Forwarded-For; Gin validates the remote peer IP against this list. TrustedPlatform bypasses this CIDR validation entirely and extracts the client IP directly from a specific header (e.g., CF-Connecting-IP), assuming you are behind a specific CDN that strips that header from user requests. Use SetTrustedProxies for private load balancers; use TrustedPlatform for public CDNs.

Why does Gin log a warning about "running with untrusted proxies"?

This warning triggers when isUnsafeTrustedProxies detects that trustedCIDRs contains 0.0.0.0/0 or ::/0, meaning your application trusts headers from any IP address on the internet. An attacker can send requests with a forged X-Forwarded-For header, and Gin will trust it as the real client IP. Resolve this by explicitly setting trusted proxy ranges with SetTrustedProxies.

Can I use custom headers instead of X-Forwarded-For to get the real IP?

Yes. Modify the Engine.RemoteIPHeaders slice to include your custom headers before starting the server. Context.ClientIP iterates this slice in order (defined in context.go, lines 70–124), returning the first valid IP found from a trusted proxy. However, ensure the header is sanitized by your proxy; otherwise, combine custom headers with TrustedPlatform for unconditional trust of a specific header name.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →