Proxying AdGuardHome DNS Traffic via Caddy Without Losing Client IPs

1. The problem I’m having:

What i want to achieve ?

I want to proxy all traffic to AdGuardHome through Caddy in order to preserve and forward the original client IP address.

why ?

When using rootless Podman, all incoming connections received by AdGuardHome appear to originate from the gateway IP of the internal-reverse-proxy network defined by Podman.
This means AdGuardHome logs show internal NAT IPs instead of real client IPs, which breaks visibility and logging.
I deliberately avoid rootful Podman for security and privilege separation reasons.

How do i try to resolve this ?

To solve this while staying within a rootless environment, I’m exploring Caddy + systemd socket activation as a viable solution.
Since socket activation allows passing the original connection file descriptors directly to Caddy, I thought of using the Caddy layer4 module to proxy DNS traffic and preserve the source IP.

2. Error messages and/or full log output:

Error: loading initial config: loading new config: layer4 app module: start: file file+net fd/3: invalid argument

3. Caddy version:

v2.9.1 h1:OEYiZ7DbCzAWVb6TNEkjRcSCRGHVoZsJinoDR/n9oaY=

4. How I installed and ran Caddy:

Caddy is installed using the official Docker image and runs rootlessly with Podman, managed via Quadlets. Quadlet is a systemd integration tool that simplifies running Podman containers as systemd services.

a. System environment:

OS

Debian GNU/Linux 12 (bookworm)

Podman

Client: Podman Engine
Version: 5.5.0
API Version: 5.5.0
Go Version: go1.24.2
Git Commit: 0dbcb51477ee7ab8d3b47d30facf71fc38bb0c98
Built: Mon Jun 2 19:29:24 2025
OS/Arch: linux/arm64

b. Command:

systemctl --user start internal-reverse-proxy.socket
systemctl --user start internal-reverse-proxy.service

c. Service/unit/compose file:

internal-reverse-proxy.socket

[Socket]
### sockets for the internal reverse proxy
# fd/3
ListenDatagram=[::]:53
# fd/4
ListenStream=[::]:784
# fd/5
ListenStream=[::]:853
# fd/6
ListenStream=[::]:8853
# fd/7
ListenStream=[::]:3000
# fd/8
ListenStream=[::]:80
# fd/9
ListenStream=[::]:443

BindIPv6Only=both

[Install]
WantedBy=sockets.target

internal-reverse-proxy.container

[Unit]
Description=Internal Reverse Proxy (Caddy)
Requires=internal-reverse-proxy.socket
After=internal-reverse-proxy.socket

[Container]
ContainerName=internal-reverse-proxy
Image=localhost/internal-reverse-proxy

Notify=true

# Mount host paths
Volume=/var/log/caddy:/var/log/caddy
Volume=%h/.config/containers/internal-reverse-proxy/Caddyfile:/Caddyfile

# Named volume (external)
Volume=internal-reverse-proxy:/data

# Environment variables
Environment=EMAIL=bbarnoux@gmail.com
Environment=ACME_AGREE=true
Environment=TZ=Europe/Paris

# Secret mounted as a file
Secret=gandi_password,type=env,target=GANDI_BEARER_TOKEN

# Network
Network=internal-reverse-proxy.network

[Install]
WantedBy=default.target

d. My complete Caddy config:

{
        persist_config off
        log internal-reverse-proxy {
                level INFO
                output file /var/log/caddy/caddy.log
        }
        auto_https disable_redirects
        default_bind fd/9 {
                protocols h1 h2
        }
        servers {
                timeouts {
                        # sets timeouts for various actions
                        idle 10s # timeout for idle connections
                        read_body 1m # timeout for reading request body
                        read_header 10s # timeout for reading request header
                }
                max_header_size 4KB # sets maximum header size for requests
        }
        acme_dns gandi {env.GANDI_BEARER_TOKEN}
        layer4 {
                fd/3 {
                        route {
                                proxy adguardhome:53
                        }
                }
                fd/4 {
                        route {
                                proxy adguardhome:784
                        }
                }
                fd/5 {
                        route {
                                proxy adguardhome:853
                        }
                }
                fd/6 {
                        route {
                                proxy adguardhome:8853
                        }
                }
                fd/7 {
                        route {
                                proxy adguardhome:3000
                        }
                }
        }
}

http:// {
        bind fd/8 {
                protocols h1
        }
        redir https://{host}{uri}
        log
}

adguard.one4all.icu {
        log
        encode gzip zstd
        reverse_proxy adguardhome:8080
}

5. Links to relevant resources: