Unable to pass real IP to Gitea behind CF and Caddy

I cannot seem to pass a user’s real IP to Gitea when behind CloudFlare and Caddy and have tried everything I can think of.

CaddyFile looks like:

https://git.mydomain.com {
  reverse_proxy {
    header_up X-Real-IP {remote_host}
    header_up X-Forwarded-For {remote_host}
    # Also tried the below
    # header_up X-Real-IP {http.request.header.CF-Connecting-IP}
    # header_up X-Forwarded-For {http.request.header.CF-Connecting-IP}

Gitea’s app.ini looks like:

REVERSE_PROXY_LIMIT = 1 #(tried 2, too)

I’ve tried including CF’s IPs in the trusted proxy list and *, and increasing the proxy limit to 2.

Caddy logs show it is correctly picking up the real IP, Gitea just sees Caddy’s local IP.

Any help would be greatly appreciated.

Try using remote instead of remote_host in the caddyfile like this

header_up X-Real-IP {remote}
header_up X-Forwarded-For {remote}

I am not sure if it matters but maybe setting the trusted proxy to will help too

I think your Gitea configuration is correct, REVERSE_PROXY_LIMIT should probably be set to 2 though.

It looks like your Caddy configuration has two issues though. Caddy manages all the X-Forwarded-For, X-Forwarded-Proto, etc. for you by default so you shouldn’t set those. X-Real-IP is not set by default in Caddy but may cause issues with using two chained reverse proxies in Gitea, I am not sure though (haven’t tried it). Another issue is that Caddy will not trust CloudFlare and ignore its X-Forwarded-For because it is not in Caddy’s trusted_proxies (see reverse_proxy (Caddyfile directive) — Caddy Documentation)

Change to {remote} didn’t work.

Thanks for pointing out the trusted_proxies setting.

I added it and confirmed that caddy is correctly passing X-Forwarded-For and showing the real IP (by testing it against PHP and specifically calling on HTTP_X_FORWARDED_FOR).

Unfortunately, Gitea just will not pick up the real IP - or even the CF proxy IP for that matter.
The failed login logs keep showing Caddy’s local address.

Even with REVERSE_PROXY_TRUSTED_PROXIES = * it doesn’t work.

Can you check X-Real-IP header value in PHP? The relevant code in Gitea checks that header first and only use X-Forwarded-For if X-Real-IP does not exist:

func realIP(r *http.Request, options *ForwardedHeadersOptions) string {
	host, _, err := net.SplitHostPort(r.RemoteAddr)
	if err != nil {
		return ""

	if !options.isTrustedProxy(net.ParseIP(host)) {
		return ""

	var ip string

	if xrip := r.Header.Get(xRealIP); xrip != "" {
		ip = xrip
	} else if xff := r.Header.Get(xForwardedFor); xff != "" {
		ip = xff[p:]

	return ip

(link to source code)

1 Like

Using the PHP (on vanilla PHP+NGINX) snippet below confirms that both X-Real-IP and X-Forwarded-For are being passed correctly and accurately by Caddy.

echo "<br>";

In CaddyFile:

header_up X-Real-IP {http.request.header.CF-Connecting-IP}

In app.ini I’ve now tried:

REVERSE_PROXY_LIMIT = 2 # (I've tried, 1, 2, 3)

Also, REVERSE_PROXY_TRUSTED_PROXIES = * doesn’t seem to change anything.

Turns out I had


Under [server] in my app.ini instead of under [security] :man_facepalming:

Everything working as it should!