TLDR: Gitub project

Following up on my blogpost about selfhosting private services, I have improved my setup by developing a single Python app that deals with Caddy access management entirely. It acts as a gatekeeper for Caddy-backed web services that we want to proctect, by managing whitelisted IPs and letting Caddy know who can access our web services.

CaddyKnocker, the name of my project, acts as a webserver that’s accessible through Caddy, and has two main uses:

  • Caddy uses it whenever somebody tries to access a protected website, to check whether the client’s IP address is already whitelisted. If the client is not already whitelisted, this program will forward away the HTTP connection using the Location: https://... HTTP headers; the URL used as the redirection target is configurable. Otherwise, according to Caddy’s documentation on the forward_auth directive, Caddy will continue with the next line of configuration to let the client in and access the secured service. (That’s the /check endpoint in CaddyKnocker)
  • A user may want to allow himself access to protected websites, by using a specific URL and HTTP Header to authorize himself. (That’s the /knock endpoint in CaddyKnocker)

The term knocking as in port knocking is taken broadly. According to Wikipedia:

(…) port knocking is a method of externally opening ports on a firewall by generating a connection attempt on a set of prespecified closed ports. Once a correct sequence of connection attempts is received, the firewall rules are dynamically modified to allow the host which sent the connection attempts to connect over specific port(s). A variant called single packet authorization (SPA) exists, where only a single “knock” is needed, consisting of an encrypted packet.

My method is more alike to SPA, in the sense that to authorize an IP, we use a single HTTP request to a specific URL (as opposed to sending encrypted data to a specific port) to unlock protected websites. However I liked more the use of the verb “to knock” here as it’s more descriptive of the action involved.

I have extensively documented the project on it’s GitHub page, but a client who wants access to resources protected by Caddy can identify himself with an URL carefully placed in the Caddyfile and open up access with a request like this one, where the Nonce client header is where the TOTP Token is sent:

$ curl -i -H 'Nonce: 34332332'
HTTP/2 204 
server: Microsoft-IIS/10.0
x-whitelist: True


Protecting a Caddyfile section is only a matter of placing a forward_auth section before dealing with the request normally. Requests are all being checked on http://caddyknocker:8000/check regardless of the URL path. {
    # Checks if the IP has already been whitelisted, if not the request is forwarded elsewhere by caddyknocker
    forward_auth caddyknocker:8000 {
            uri /check
            copy_headers X-Forwarded-For

    # If caddyknocker dosen't forward the request away, Other 'normal' directives for this host goes after this line: 
    reverse_proxy http://bitwarden:3000

This also works with subfolders. In this following Caddyfile example, I have included both the authorization URL to “knock-in” {
    # super secret URL that you will use to authorize yourself
    # it can be long and arbitrary
    handle /api/0/getInfo {
        # 'rewrite' tells Caddy to always redirect the path to /knock, regardless of the path in 'handle' above 
        # this is configurable in the app
        rewrite * /knock   
        reverse_proxy http://caddyknocker:8000

    # protected subfolder
    handle_path /subfolder/* {
        forward_auth caddy-knocker:8000 {
            uri /check
            copy_headers X-Forwarded-For
        reverse_proxy http://whatever:9000

For more information, please consult the Github Project.

Icons for the banner has been provided by Vecteezy. The Caddy logo was uploaded by user Mwholt on Wikipedia and is more likely the principal developer of the Caddy project (unverified).