Serving requests made from vpn

1. The problem I’m having:

I’m trying to configure Caddy so that it returns a different page for a particular address, say work.hard.com depending on where the request comes from. Caddy and the application in 2) below are running in docker on bridge networks (including one shared network).

  1. via eth0 / external IP: serves a single page saying “no access” e.g. ‘/var/webroot/index.html’
  2. via wg0 / internal vpn subnet IP: proxy to docker application

Currently I have the docker application available via the external interface but I want to make it vpn-only accessible. I believe I need to modify my Caddyfile and manage the tls implications of the request coming from the vpn (if there are such implications). Currently it is configured as follows:

work.hard.com {
reverse_proxy http://docker-application


}

Is it possible to turn this into some sort of conditional configuration which serves something different depending on the source of the request. I have experimented using the ‘bind’ directive, e.g.

work.hard.com {
bind vpn_subnet
reverse_proxy http://docker-application


}

work.hard.com {
bind external ip
reverse_proxy http://docker-application


}

2. Error messages and/or full log output:

{"level":"info","ts":1713821278.215026,"logger":"http.auto_https","msg":"server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS","server_name":"srv0","https_port":443}
{"level":"info","ts":1713821278.2150505,"logger":"http.auto_https","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
{"level":"info","ts":1713821278.2150848,"logger":"http.auto_https","msg":"server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS","server_name":"srv1","https_port":443}
{"level":"info","ts":1713821278.2150972,"logger":"http.auto_https","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv1"}
{"level":"info","ts":1713821278.2151647,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0x400042e900"}
{"level":"info","ts":1713821278.2191105,"logger":"tls.cache.maintenance","msg":"stopped background certificate maintenance","cache":"0x400042e900"}
Error: loading initial config: loading new config: http app module: start: listening on [vpn_subnet]:443: listen tcp [vpn_subnet:443: bind: cannot assign requested address

3. Caddy version:

2.7.6

4. How I installed and ran Caddy:

a. System environment:

Debian 12, Docker 26.1.0

b. Command:

docker compose up -d

c. Service/unit/compose file:

caddy:
    hostname: caddy
    image: caddy:latest
    container_name: caddy
    restart: always
    volumes:
      - ${DockerDir}/caddy/Caddyfile:/etc/caddy/Caddyfile
      - /var/www:/srv
    ports:
      - 80:80
      - 443:443
    networks:
      - caddy_external
    security_opt:
      - no-new-privileges:true

d. My complete Caddy config:

## global
{
        # Logging
        #debug
        # TLS Options
        email [  ]
        key_type [  ]
}

(log_roll) {
        roll_size 5MiB
        roll_keep 5
}

work.hard.com {
        reverse_proxy http://docker-application
        log {
                output file /var/log/work.hard.com_access.log {
                        import log_roll
                }
                format console
        }
}

5. Links to relevant resources:

bind will work. Just run caddy on system level, not docker

Yeah what you want is bind, but this is more of a Docker networking problem than a Caddy one.

Your other option though is to use a remote_ip matcher, if connections through your VPN always come from a specific range of IPs, then you can have separate handle blocks in the one site block that do different things.

Fantastic, remote_ip worked. FYI, this is what I did:

work.hard.com {
        @vpn remote_ip 10.0.0.0/24

        handle @vpn {
                reverse_proxy http://docker-container
                }

What does the client_ip matcher do?

The docs explain. It’s only useful if you have another proxy in front of Caddy, and if you configured trusted_proxies, then it has the value of the original client IP, parsed from HTTP headers.