Is there an "or" option for `header_up`?

I was reading the docs and assuming the answer is “no” but wanted to doublecheck.

Is there any “or” option for header_up?

My specific use-case: my setup is split in where all local traffic is only local, but all external traffic is through Cloudflare. If I want to do header_up X-Real-IP {http.request.header.CF-Connecting-IP}, that’s great for getting the real IP from Cloudflare, but when that data isn’t present (connection over LAN), then I end up with no (or ‘malformed’ some apps call it) data in that field.

In an ideal world, I would be able to configure Caddy to set that header only when CF-Connecting-IP is actually available, otherwise, just forward on the remote_ip or something.

Cloudflare should be sending along X-Forwarded-For – you can configure Caddy’s reverse_proxy with trusted_proxies to only trust that header if you received the request from one of Cloudflare’s IP ranges.

Use of X-Forwarded-For is more widespread than X-Real-IP, so I suggest you use that instead.

Several applications I use look for X-Real-IP and not X-Forwarded-For. The ones looking for X-Forwarded-For are already handled, of course.

Edit: a letter.

Best I can give you is that you can use the vars directive to set a variable conditionally (use a matcher for the condition) and then later use {vars.*} placeholder to set whatever.

But still, I don’t recommend using other headers for the remote IP, it’s risky. I’d suggest asking those applications to support X-Forwarded-For, either by default or by config

1 Like

I’ll check out vars; thank you.

Well, for the life of me I absolutely cannot figure out where on Earth vars is supposed to be positioned in a Caddyfile. :sweat_smile:

I also think I might be defining a vars wrong…was trying something like:

vars realip {http.request.header.CF-Connecting-IP} {http.request.header.X-Forwarded-For} {}

I thought that would would be how to name the vars and add essentially some ORs…but I don’t think so?

Edit: I’ve read both vars (Caddyfile directive) — Caddy Documentation and Request matchers (Caddyfile) — Caddy Documentation and neither has really cleared up anything for me. :sweat_smile:

Edit 2: Well, I made some progress?

	vars * {
		realip "{http.request.header.CF-Connecting-IP}"
		realip "{http.request.header.X-Forwarded-For}"
		realip "{}"

The above with or without quotes at least feeds data to {vars.realip}, but I’m either getting a blank reply/variable or literally realip as a string…so…progress-ish, but not really sure what to do to get this to work how I want/expect.

Edit 3: A Caddyfile like this seems to work for a local test…so… :upside_down_face: {
        vars {
                realip {http.request.header.CF-Connecting-IP}
                realip {http.request.header.X-Forwarded-For}
                realip {}
        respond * 200 {
                body "{vars.realip}"

Edit 4: More rogress. Removed the * and quotes from Edit 2. Then in the reverse_proxy section, I have a header_up X-Real-IP "{vars.realip}" and now I can get the IP for LAN connections…but not the ones coming from Cloudflare. :upside_down_face: So now I’ve flipped the original issue lol

	vars {
		realip {http.request.header.CF-Connecting-IP}
		realip {http.request.header.X-Forwarded-For}
		realip {}

vars doesn’t do try_files-like fallback logic. It’s literally just realip = "value", that’s it.

So you need to use matchers to tell it not to set the value in a certain situation.

Here’s an example checking that the header has some value before setting the var. The first one always runs, but the following ones might run depending on the matcher, to overwrite the value. Also, I’m using placeholder shortcuts as per Caddyfile Concepts — Caddy Documentation

vars realip {remote_host}

@cf header CF-Connecting-IP *
vars @cf realip {header.CF-Connecting-IP}

@xf header X-Forwarded-For *
vars @xf realip {header.X-Forwarded-For}

But either way, this is dangerous, because this doesn’t do anything to prevent spoofing the XFF or CF headers, if some bad actor stuffs in their own value for one of those headers. That’s the point of the changes made in v2.5.0, they prevent spoofing by forcing you to provide a list of IP ranges you trust to have given you correct values for the XFF header, and not just made-up values.

1 Like

The problem is my split setup, I guess. I’m also utterly behind Cloudflare with no open ports to connect to caddy via my WAN IP. A connection that isn’t internal LAN must and can literally only come from Cloudflare. If some bad actor can spoof whatever the Hell they want and Cloudflare lets them…seems like I have bigger problems.

My caddy is also 2.5.1.

There’s two ways I might access, for example, Let’s say I’m reverse proxying an application on that subdomain that requires X-Real-IP.

If I don’t header_up, obviously X-Real-IP is never set. (duh)

If I do header_up X-Real-IP {remote_host}, I’m going to either get a LAN IP if accessing via LAN or get the Docker IP of my cloudflared container when from WAN, even with trusted_proxies private_ranges <all_cf_ipv4> <all_cf_ipv6> (which should cover the 172.x.x.x space Docker uses, no?).

If I do header_up X-Real-IP {header.CF-Connecting-IP} (or XFF) I will only ever get the X-Real-IP for WAN connections from Cloudflare, LAN connections seem to just get some docker IP set.

Hopefully the above better explains the situation.


This does indeed seem to work like a charm. Thank you.

Fair enough then :+1:

But… I just went looking at Cloudflare’s docs and I was shocked to see that they don’t clean up the XFF header, they just append their own value!


I hate everything.


FWIW, that hasn’t been the case as far as I’ve seen. I’ve never seen more than 1 IP in the XFF field from Cloudflare.

That’s because you haven’t caught someone trying to spoof their IP.

You can try it yourself, make requests with curl, setting the XFF header on your request to something random.

1 Like

This topic was automatically closed after 30 days. New replies are no longer allowed.