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

> Learn how to configure trusted proxies and get the real client IP in Gin. Use Engine SetTrustedProxies and c.ClientIP for secure IP retrieval in production.

- Repository: [Gin-Gonic/gin](https://github.com/gin-gonic/gin)
- Tags: how-to-guide
- Published: 2026-02-27

---

**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`](https://github.com/gin-gonic/gin/blob/main/gin.go) and [`context.go`](https://github.com/gin-gonic/gin/blob/main/context.go).

## How Gin Resolves Client IP Addresses

Gin determines the client IP through a strict hierarchy implemented in `Context.ClientIP` ([`context.go`](https://github.com/gin-gonic/gin/blob/main/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`](https://github.com/gin-gonic/gin/blob/main/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`](https://github.com/gin-gonic/gin/blob/main/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`](https://github.com/gin-gonic/gin/blob/main/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`](https://github.com/gin-gonic/gin/blob/main/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.

```go
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:

```go
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`](https://github.com/gin-gonic/gin/blob/main/context.go).

```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:

```go
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.

```go
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`](https://github.com/gin-gonic/gin/blob/main/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.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`](https://github.com/gin-gonic/gin/blob/main/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`](https://github.com/gin-gonic/gin/blob/main/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`](https://github.com/gin-gonic/gin/blob/main/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.