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)
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
}
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.