IPv4 or IPv6 preference for individual subdomains

  1. The problem I’m having:

I want specific subdomains to be either IPv4 or IPv6 only.
My overall goal is that I run a service that relies on the X-Forwarded-For header to show the client’s IP address. My server has both IPv4 and IPv6, but Caddy always forwards the IPv6 address when it’s available. I need to be able to access the client’s IPv4 as well, without disabling IPv6 entirely. So I want to make dedicated v4/v6 subdomains but I don’t know how…

  1. Error messages and/or full log output:

I can’t provide logs for it, since I can’t figure out how can I even do this. Currently everything responds to both v4 and v6.

  1. Caddy version:

caddy version
v2.10.0

  1. How I installed and ran Caddy:

I used xcaddy to build it with caddy-dns/cloudflare.
xcaddy build --with github.com/caddy-dns/cloudflare

a. System environment:

Debian 12 (Linux x86_64), with systemd – no docker.
Domain: (*.)example.com

b. Command:

c. Service/unit/compose file:

cat /lib/systemd/system/caddy.service

[Unit]
Description=Caddy
Documentation=https://caddyserver.com/docs/
After=network.target network-online.target
Requires=network-online.target

[Service]
Type=notify
User=caddy
Group=caddy
ExecStart=/usr/local/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/local/bin/caddy reload --config /etc/caddy/Caddyfile --force
TimeoutStopSec=5s
LimitNOFILE=1048576
LimitNPROC=512
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_BIND_SERVICE
Environment="CLOUDFLARE_API_KEY=REDACTED"
Restart=on-failure
RestartSec=5s

d. My complete Caddy config:

caddy fmt /etc/caddy/Caddyfile

(cf) {
  tls {
    dns cloudflare {env.CLOUDFLARE_API_KEY}
  }
}

*.demo.example.com {
import cf
@demo host app.demo.example.com
handle @demo {
  reverse_proxy 10.0.0.10:3006
}

handle {
  abort
}
}

example.com {
import cf
reverse_proxy 10.0.0.14:3000
}

.example.com {
import cf
import sites/
handle {
redir https://example.com permanent
}
}

Example from my sites/ directory:

cat /etc/caddy/sites/service.example.com

@service host service.example.com
handle @service {
  reverse_proxy 10.0.0.10:8002
}
  1. Links to relevant resources:

Not sure if this is what you’re looking for but you could try bind

IPv4/IPv6 is a preference of the client, not the server.

A connection over IPv6 is fully distinct form a connection over IPv4

If the service does not work with IPv6, remove the AAAA from the domain in the DNS

Your are incorrect, binding a server to just a single ip doesn’t work.

Taking your example with nignx, if one site block is bound to IPv6 and IPv4, while the other is only bound to IPv4, if you try to access the latter over IPv6, the browser connects to the server, then gets an error from the server saying it doesn’t host the site. From the browsers perspective, there was a vaid connection and it never falls back to another IP

Even in the case when you are hosting one website, it is still unreliable, as the browser might end up connecting to the other IP and hit a timeout. (or in the case of a DNS64 gateway, it never even sees the A record if there is an AAAA record introduced). You would also be depending on the Happy Eyeballs algoritmh in browsers, which is unreliable.

And remember that letsencrypt and Zerotier doesn’t support this. If your website has an AAAA and A record, letsencrypt only tries AAAA (with a 1 time A request fallback that gets eaten up by the HTTP to HTTPS redirect). This is a common issue that gets posted on these forums and the letsencrypt forums by people asking why they cannot get a certificate

SOLUTION:

Create separate AAAA and A dns records on top of your existing wildcard.

EXAMPLE:

*.example.com. 3600 IN A 1.1.1.1
ipv4.example.com. 3600 IN A 1.1.1.1
ipv6.example.com. 3600 IN AAAA 2606:4700:4700::1111
ipv4.example.com {
    reverse_proxy 1.2.3.4
}
ipv6.example.com {
    reverse_proxy 1.2.3.4
}