Load balancing queries

I thought it might be interesting to show how the wildcard caddy block changed as a result of discussions arising from this thread.

This is an excerpt of the original Caddyfile including the wildcard Caddy block and a ‘patch’ for load balancing.

office.udance.com.au {
  encode gzip zstd
  import logging udance.com.au

  reverse_proxy 10.1.1.13:8880 10.1.3.132:9006
}

*.udance.com.au {

  encode gzip zstd
  import logging udance.com.au

  map {labels.3} {backend} {online} {mtls} {phpmyadmin} {

#   HOSTNAME     BACKEND         ONLINE mTLS PHPMYADMIN #COMMENT
#---------------------------------------------------------------

    # Docker containers

#    office       10.1.1.13:8880  yes    no   no         # OnlyOffice
    portainer    10.1.1.13:9000  yes    no   no         # Portainer
    truecommand  10.1.1.13:8086  yes    no   no         # TrueCommand 2 nightly
    tc123        10.1.1.13:8082  yes    no   no         # TrueCommand v1.2.3
    tc132        10.1.1.13:8084  yes    no   no         # TrueCommand v1.3.2
    nc-fpm       10.1.1.13:8031  yes    no   no         # Nextcloud+Caddy
    wordpress    10.1.1.13:5050  yes    no   no         # WordPress
    nc-apache    10.1.1.13:8030  yes    no   no         # Nextcloud+Apache
    collabora    10.1.1.13:9980  yes    no   no         # Collabora

    # Jails

    rslsync      10.1.1.22:8888  yes    no   no         # Resilio Sync
    cloud        10.1.1.29:80    yes    no   no         # Nextcloud
    heimdall     10.1.1.23:80    yes    no   no         # Heimdall
    test         test.lan:80     yes    no   yes        # test.udance.com.au
    blog         10.1.1.54:80    yes    no   yes        # blog.udance.com.au
    basil        10.1.1.56:80    yes    no   yes        # basil.udance.com.au
    sachika      10.1.1.57:80    yes    no   yes        # sachika.udance.com.au
    file         file.lan:443    yes    yes  yes        # file.udance.com.au
    default      unknown         yes    no   no         # subdomain does not exist
}

  route {
# Error handling
    @unknown expression `{backend} == "unknown"`
    respond @unknown "Denied" 403

# Site offline
    @offline expression `{online} == "no"`
    redir @offline https://udance.statuspage.io temporary

    @split {
      expression `{online} == "split"`
      not remote_ip 10.1.1.0/24 10.1.2.0/24
    }
    redir @split https://udance.statuspage.io temporary

# Authenticate phpMyAdmin on production WordPress sites
    @phpmyadmin expression `{phpmyadmin} == "yes"`
    route @phpmyadmin {
      import authorise /phpmyadmin*
    }

# Fix when using the Nextcloud+Apache Docker image with Caddy.
    @nc-apache host nc-apache.udance.com.au
    route @nc-apache {
      redir /.well-known/carddav /remote.php/carddav 301
      redir /.well-known/caldav /remote.php/caldav 301
    }

# Enable HSTS for Nextcloud
    @hsts host cloud.udance.com.au
    header @hsts Strict-Transport-Security "max-age=31536000;"

# Secure backend communication
    @mtls expression `{mtls} == "yes"`
    reverse_proxy @mtls {backend} {
      header_up Host {http.reverse_proxy.upstream.hostport}
      header_up X-Forwarded-Host {host}
      transport http {
        tls
      }
    }

# Unsecured backend communication
    @nomtls expression `{mtls} == "no"`
    reverse_proxy @nomtls {backend}

  }
}

After decoupling reverse_proxy from the map directive, this is what the Caddyfile excerpt looks like now. The outcome is the same. The difference is that the API endpoint described earlier in this thread can now be used to monitor the availability of upsteam servers.

*.udance.com.au {

  encode gzip zstd
  import logging udance.com.au

  map {labels.3} {online} {

#   HOSTNAME     ONLINE # HSTS  mTLS    PHPMY   COMMENT
#                                       ADMIN
#---------------------------------------------------------------

    # Docker

    collabora    yes    # no    no      no      Collabora
    nc-apache    yes    # yes   no      no      Nextcloud+Apache
    nc-fpm       yes    # yes   no      no      Nextcloud+Caddy
    office       yes    # no    no      no      OnlyOffice
    portainer    yes    # no    no      no      Portainer
    tc123        yes    # no    no      no      TrueCommand v1.2.3
    tc132        yes    # no    no      no      TrueCommand v1.3.2
    truecommand  yes    # no    no      no      TrueCommand 2 nightly
    wordpress    yes    # no    no      no      WordPress

    # Jails

    basil        yes    # no    no      yes     basil.udance.com.au
    blog         yes    # no    no      yes     blog.udance.com.au
    cloud        yes    # yes   no      no      Nextcloud
    file         yes    # no    yes     no      file.udance.com.au
    heimdall     yes    # no    no      no      Heimdall
    rslsync      yes    # no    no      no      Resilio Sync
    sachika      yes    # no    no      yes     sachika.udance.com.au
    test         yes    # no    no      yes     test.udance.com.au

    default      alien  # no    no      no      subdomain does not exist
}

  route {

### Exception handling ###

# Non-existent subdomain
    @unknown expression `{online} == "alien"`
    respond @unknown "Denied" 403

# Site offline
    @offline expression `{online} == "no"`
    redir @offline https://udance.statuspage.io temporary

    @split {
      expression `{online} == "split"`
      not remote_ip 10.1.1.0/24 10.1.2.0/24
    }
    redir @split https://udance.statuspage.io temporary

# Authenticate phpMyAdmin on production WordPress sites
    @phpmyadminhosts header_regexp phpmyadmin host (test|blog|basil|sachika)\.udance\.com\.au
    route @phpmyadminhosts {
      import authorise /phpmyadmin*
    }

# Enable HSTS for Nextcloud sites
    @hstshosts header_regexp hsts host (cloud|nc-apache|nc-fpm)\.udance\.com\.au
    header @hstshosts Strict-Transport-Security "max-age=31536000;"

# Fix when using the Nextcloud+Apache Docker image with Caddy.
    @nc-apachefix host nc-apache.udance.com.au
    route @nc-apachefix {
      redir /.well-known/carddav /remote.php/carddav 301
      redir /.well-known/caldav /remote.php/caldav 301
    }

### Reverse Proxies ###

# Docker

    @collabora host collabora.udance.com.au
    @nc-apache host nc-apache.udance.com.au
    @nc-fpm host nc-fpm.udance.com.au
    @office host office.udance.com.au
    @portainer host portainer.udance.com.au
    @tc123 host tc123.udance.com.au
    @tc132 host tc132.udance.com.au
    @truecommand host truecommand.udance.com.au
    @wordpress host wordpress.udance.com.au

    reverse_proxy @collabora    10.1.1.13:9980
    reverse_proxy @nc-apache    10.1.1.13:8030
    reverse_proxy @nc-fpm       10.1.1.13:8031
    reverse_proxy @office       10.1.1.13:8880 10.1.3.132:9006
    reverse_proxy @portainer    10.1.1.13:9000
    reverse_proxy @tc123        10.1.1.13:8082
    reverse_proxy @tc132        10.1.1.13:8084
    reverse_proxy @truecommand  10.1.1.13:8086
    reverse_proxy @wordpress    10.1.1.13:5050

# Jails

    @basil host basil.udance.com.au
    @blog host blog.udance.com.au
    @cloud host cloud.udance.com.au
    @file host file.udance.com.au
    @heimdall host heimdall.udance.com.au
    @rslsync host rslsync.udance.com.au
    @sachika host sachika.udance.com.au
    @test host test.udance.com.au

    reverse_proxy @basil        10.1.1.56
    reverse_proxy @blog         10.1.1.54
    reverse_proxy @cloud        10.1.1.29
    reverse_proxy @heimdall     10.1.1.23
    reverse_proxy @rslsync      10.1.1.22:8888
    reverse_proxy @sachika      10.1.1.57
    reverse_proxy @test         test.lan

    reverse_proxy @file https://file.lan {
      header_up Host {http.reverse_proxy.upstream.hostport}
      header_up X-Forwarded-Host {host}
    }
  }
}

There are basically three sections to the Caddy wildcard block. At the top is a map block; in the middle is some exception handling, and in the lower third are the reverse proxies. Some observations:

  1. I still find the map handler really useful for an ‘at a glance’ bird’s eye view of what’s happening in the wildcard Caddy block. Time-dependent actions, like whether a site online or not, are an active part of the map handler. More permanent subdomain specifics are tabulated in the comments section, but dealt with under exception handling.
  2. The exception handling (routing-first) section immediately following the map Caddy block serves several purposes.
    a. Handles boundary conditions and switching for the map handler.
    b. Addresses subtle differences between subdomains.
  3. In the exception handling section, I found reg_exp very useful for describing traits common to a subset of subdomains. For instance, it was an effective alternative to using a combination of map, expression logic and a snippet for phpmyadmin basic auth.
  4. Though I lost the association of subdomains and ip addresses when backends were included in the map block, I got that association back again in the lower portion of the wildcard Caddy block where the reverse proxies are documented. The trick here was to match the names of the request handlers for the reverse proxies to the labels used in the map block. This form of documentation also lends itself to extra upstream servers being included for reverse proxies.
  5. A further benefit of decoupling reverse_proxy and map is the slightly less abstract logic for mTLS (last reverse_proxy in the wildcard Caddy block).
1 Like