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:
- Trusted Platform Header – If
Engine.TrustedPlatformis set, Gin extracts the IP from the specified header (e.g.,CF-Connecting-IP) immediately, bypassing proxy validation. - Legacy App Engine – Checks the deprecated
X-Appengine-Remote-Addrheader (deprecated in favor ofTrustedPlatform). - Trusted Proxy Validation – Compares the remote address against
engine.trustedCIDRsusingisTrustedProxy(gin.go, lines 68–70). - Header Inspection – If the remote IP is trusted, Gin inspects
RemoteIPHeaders(default:X-Forwarded-For,X-Real-IP) and validates the chain usingvalidateHeader(gin.go, lines 81–99), walking backwards throughX-Forwarded-Foruntil it finds the first untrusted address. - Fallback – Returns
c.Request.RemoteAddrif 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.5→10.0.0.5/32) - Returns
[]*net.IPNetstored inengine.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/0and::/0CIDRs allow IP spoofing; always callSetTrustedProxiesor setTrustedPlatform. - Whitelist precisely: Use
SetTrustedProxies([]string{"CIDR", "IP"})to define exactly which infrastructure can forward client headers. - Disable when unnecessary: Pass
niltoSetTrustedProxieswhen running without proxies to prevent header parsing entirely. - Leverage CDN headers: Use
TrustedPlatformfor managed services to bypass CIDR checks and trust verified infrastructure headers. - Validate the chain: Gin automatically walks the
X-Forwarded-Forchain backwards viavalidateHeaderto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →