Haproxy and Anubis - real ip in the logs

Hello,

We configured Anubis with haproxy and everything is working nicely except 1 thing.

I’m not able to have the real ip in the haproxy logs after Anubis (that sends back the request to Haproxy if the client passed Anubis test)

Our Haproxy version: 2.6.12-1+deb12u2

  • Haproxy frontend before Anubis
frontend before-anubis-frontend
  log global
  mode http
  bind :80 v4v6
  bind :443 v4v6 ssl crt /etc/haproxy/ssl alpn h2,http/1.1 allow-0rtt

  option forwardfor
  option http-server-close
  option  httplog

  http-request set-header Strict-Transport-Security "max-age=63072000; preload;"
  http-request set-header X-Forwarded-Ssl on if { ssl_fc }
  http-request set-header X-Forwarded-Port %[dst_port]
  http-request set-header X-Forwarded-Proto https if { ssl_fc }
  http-request add-header X-Forwarded-For %[src]
  http-request add-header X-Real-IP %[src]

  redirect scheme https code 301 if !{ ssl_fc }

  ...
  several ACLs and backend redirections here
  ...

  use_backend before-anubis-backend
  • Haproxy backend before Anubis
backend before-anubis-backend
  log global
  mode    http
  option forwardfor

  http-request set-header X-Forwarded-Ssl on if { ssl_fc }
  http-request set-header X-Forwarded-Port %[dst_port]
  http-request set-header X-Forwarded-Proto https if { ssl_fc }

  server anubis           192.168.1.49:8930
  • Example of logs with this:

2026-03-24T20:03:38.172510+00:00 haproxy-01 haproxy[3321156]: 1.2.3.4:51076 [24/Mar/2026:20:24:01.962] before-anubis-frontend~ before-anubis-backend/anubis 0/0/1/1/2 200 2125 - - ---- 69/64/3/3/0 0/0 “GET xxxx.net - This website is for sale! - xxxx Resources and Information. HTTP/2.0”

At this point, we can see my ip (1.2.3.4) in the logs.

  • Anubis get the request and if the client passes Anubis, it sends back the request to port 81
  • Haproxy frontend after Anubis
frontend after-anubis-frontend
  bind :81 v4v6
  mode http
  option  httplog
  option forwardfor

  http-request set-header X-Forwarded-Ssl on
  http-request set-header X-Forwarded-Port %[dst_port]
  http-request set-header X-Forwarded-Proto https if { ssl_fc }

  ...
  several ACLs and backend redirections here
  ...

  use_backend %[req.hdr(host),lower,word(1,:)]
  • Request is sent to the right VM.
  • Example of logs here:

2026-03-24T20:03:38.173271+00:00 haproxy-01 haproxy[3321156]: 192.168.1.49:40250 [24/Mar/2026:20:25:17.491] after-anubis-frontend xxxx.net - This website is for sale! - xxxx Resources and Information. 0/0/0/49/49 200 4295 - - ---- 73/4/0/0/0 0/0 “GET /users/sign_in HTTP/1.1”

Here, the ip showed in logs is the local ip of the Anubis VM (192.168.1.49) and not my ip (1.2.3.4).

With this 2 lines in the after-anubis-frontend section, I was able to have all the headers that are presents in the request when coming back from Anubis:

log-format "${HAPROXY_HTTP_LOG_FMT} hdrs:%{+Q}[var(txn.req_hdrs)]"
http-request set-var(txn.req_hdrs) req.hdrs

With it, the haproxy logs shows this for my request:

  • unformatted:

2026-03-24T20:03:38.173271+00:00 haproxy-01 haproxy[3321863]: hdrs:“host: gitlab.xxxx.net#015#012user-agent: curl/7.88.1#015#012accept: /#015#012strict-transport-security: max-age=63072000; preload;#015#012x-anubis-action: ALLOW#015#012x-anubis-rule: threshold/minimal-suspicion#015#012x-forwarded-for: 1.2.3.4, 192.168.1.11#015#012x-forwarded-proto: https#015#012x-http-fingerprint-ja4h: ge11nn090000_9fa0313c43ea_000000000000_000000000000#015#012x-real-ip: 1.2.3.4#015#012accept-encoding: gzip#015#012x-forwarded-ssl: on#015#012x-forwarded-port: 81#015#012#015#012”

  • formatted:
2026-03-24T20:03:38.173271+00:00 haproxy-01 haproxy[3321863]: hdrs:"
host: gitlab.xxxx.net
user-agent: curl/7.88.1
accept: */*
strict-transport-security: max-age=63072000; preload;
x-anubis-action: ALLOW
x-anubis-rule: threshold/minimal-suspicion
x-forwarded-for: 1.2.3.4, 192.168.1.11
x-forwarded-proto: https
x-http-fingerprint-ja4h: ge11nn090000_9fa0313c43ea_000000000000_000000000000
x-real-ip: 1.2.3.4
accept-encoding: gzip
x-forwarded-ssl: on
x-forwarded-port: 81

We can see that both the headers x-real-ip and x-forwarded-for have the real ip.

I tried to play with these rules, but whatever I tried, I couldn’t get anything else than 192.168.1.49:

http-request set-header X-Forwarded-For %[src]
http-request set-header X-Real-IP %[src]
http-request add-header X-Real-Ip %[src]
capture request header X-Real-Ip len 15
http-response add-header X-Forwarded-For

But for me, as the x-real-ip header and x-forwarded-for have the correct ip, it should work out of the box.

Any idea how I can have the real ip in the logs?

In my setup I did few weeks ago with Nginx → Anubis → Nginx, I had the same problem and the solution was to use the header proxy_add_x_forwarded_for instead of x-real-ip or x-forwarded-for.
But I don’t know if Haproxy has an equivalent, I didn’t find information about it on the net.

Thanks

You need to set the source IP to the IP found in X-Real-IP header:

http-request set-src hdr(x-real-ip)

Careful with the X-Forwarded-For header. It can contain multiple IP addresses.

Also, always think about how an attacker can just send those headers as well, so make sure that Anubis always rewrites X-Real-IP and make sure it is NOT used in the before-anubis configuration.

1 Like

@lukastribus Amazing. It’s working. Thanks a lot.

Also, always think about how an attacker can just send those headers as well

Hmm. That’s true. Need to be sure it’s working with port 81 closed to outside world.

make sure that Anubis always rewrites X-Real-IP

I don’t understand. Anubis gets the real-ip from the x-real-ip header added by Haproxy and keeps that header when forwarding the request if it passes the test, or am I misunderstanding how it works?

make sure it is NOT used in the before-anubis configuration

But my haproxy is adding the header x-real-ip. Shouldn’t I not do this?

Already big thanks for the help.

I changed my config in order to remove the use of the x-forwarded-for header (as I don’t need it as x-real-ip is working) and force value on port and proto.

Is this setup more secure following your advices (if the port 81 is not accessible from outside)?

  • frontend before-anubis-frontend
frontend before-anubis-frontend
  log global
  mode http
  bind :80 v4v6
  bind :443 v4v6 ssl crt /etc/haproxy/ssl alpn h2,http/1.1 allow-0rtt

  option http-server-close
  option  httplog

  http-request set-header Strict-Transport-Security “max-age=63072000; preload;”
  http-request set-header X-Forwarded-Ssl on if { ssl_fc }
  http-request set-header X-Forwarded-Port %[dst_port]
  http-request set-header X-Forwarded-Proto https if { ssl_fc }
  http-request add-header X-Real-IP %[src]

  redirect scheme https code 301 if !{ ssl_fc }

  …
  several ACLs and backend redirections here
  …

  use_backend before-anubis-backend
  • backend before anubis
backend before-anubis-backend
  log global
  mode    http

  http-request set-header X-Forwarded-Ssl on
  http-request set-header X-Forwarded-Port 443
  http-request set-header X-Forwarded-Proto https

  server anubis           192.168.1.49:8930
  • frontend after-anubis-frontend
frontend after-anubis-frontend
  bind :81 v4v6
  mode http
  option  httplog

  http-request set-header X-Forwarded-Ssl on
  http-request set-header X-Forwarded-Port 443
  http-request set-header X-Forwarded-Proto https
  http-request set-src hdr(x-real-ip)

  …
  several ACLs and backend redirections here
  …

  use_backend %[req.hdr(host),lower,word(1,:)]

And the headers found in the request back from Anubis to Haproxy are these

026-03-25T17:33:48.677967+00:00 haproxy-01 haproxy[3351023]: hdrs:"
host: ``gitlab.xxxx.net
user-agent: gitlab-runner 18.8.0 (18-8-stable; go1.25.3 X:cacheprog; linux/amd64)
content-length: 948
accept: application/json
accept-encoding: gzip
content-type: application/json
runner-token: glrt-t3_X-i5-18Gyuyqz3dGx6Dd
strict-transport-security: max-age=63072000; preload;
x-anubis-action: ALLOW
x-anubis-rule: threshold/minimal-suspicion
x-forwarded-for: 192.168.1.11
x-http-fingerprint-ja4h: po11nn130000_859d97526f2c_000000000000_000000000000
x-real-ip: 1.2.3.4
x-request-id: f952abafa252471e9b6709fdf829aa21
x-forwarded-ssl: on
x-forwarded-port: 443
x-forwarded-proto: https

Yes, making port 81 reachable just from Anubis is exactly what should be done to avoid anyone from bypassing both Anubis but also the trust boundary that you create (you trust that request from Anubis do not have attacker controlled values in headers like X-Real-IP, just as Anubis trusts that those headers are not attacker controlled from haproxy).

Haproxy always needs explicit configuration for every step, because everything else would by a insecure by default design.

Think about it: in the before-Anubis section you cannot trust any headers, and by using http-request set-header you always replace all existing headers with the intended value.

In the after-anubis sections you implicitly trust the x-real-ip header, which is the exact opposite.

Haproxy cannot know that, that is why all those configurations need to be explicit.

You’re right, in this case it is the before-Anubis configuration in haproxy that overwrites the headers. Because you are using http-request set-header in the before Anubis configuration, you are safe as that means the particular header is always replaced (even if a client would send a header with a fake X-Real-IP it would not matter because it gets fully replaced).

I just meant that http-request set-src xxx should only be in the after-anubis section. All the set-header stuff belongs to the before-anubis section, which is already the case.

1 Like

Thanks for the explanation. It makes things clearer.

I activated our firewall script and port 81 is blocked.

All is well.

Best.

1 Like