Random notes from a security-aware software engineer, open-source advocate and occasional lecturer.
In HAProxy, Caddy, PROXY Protocol and X-Forwarded-For I explored how HAProxy and Caddy can work well together and pass or the real user’s IP addresses, exploring different options and observing the behavior of HAProxy and Caddy. You don’t care and just want a quick recommendation instead? That’s what you get here.
We have an application cluster with HAProxy as load balancer and Caddy in front of the apps. The flow of a request to an app therefor looks like this:

In HAProxy, add option forwardfor.
This will make HAProxy send X-Forward-For headers.
backend caddy_server
mode http
option forwardfor
default-server ca-file /etc/ssl/certs/ca-bundle.crt check ssl verify required sni req.hdr(host)
server s1 caddy.example.com:443
In Caddy, mark HAProxy as trusted proxy.
Assuming 2.2.2.2 is the IP of HAProxy:
{
servers {
trusted_proxies static 2.2.2.2
}
}
caddy.example.com {
…
}
This will result in Caddy accepting or augmenting the X-Forwarded-* headers from HAProxy:
❯ nc -l 8000
HEAD / HTTP/1.1
Host: example.com
Via: 1.1 Caddy
X-Client-Ip: 1.1.1.1
X-Forwarded-For: 1.1.1.1, 2.2.2.2
X-Forwarded-Host: example.com
X-Forwarded-Proto: https
X-Original-Forwarded-For: 1.1.1.1
X-Remote-Ip: [2.2.2.2]:60950
You can now use client_ip to check the user’s IP address if necessary and Caddy will pass on the user’s IP to applications.