Using Python 3 and the REST API to control Caddy

The following examples show how control Caddy using the REST API and Python 3.

The relative links make more sense on GitHub:
https://github.com/tonyallan/python3-experiments/tree/main/caddy-server-api

Also most links removed because I am a new user. Sorry.

Setup

These examples assume Caddy v2.5.1 or later.

View your full configuration in your browser:
localhost:2019/config/

Start Caddy with no initial configuration:

./caddy run

While it is running you can access the:

  • Configuration endpoint localhost:2019/config
  • Metrics endpoint localhost:2019/metrics

Start a local webserver (anything will do) in a separate terminal for the examples:

python3 -m http.server 9004 --bind=localhost

Example script

Download and then run the example in a separate terminal:

cd ~/GitHub/python3-experiments/caddy-server-api
python3 caddy-api.py

Sample output:

ping       static_response      
test       reverse_proxy        localhost:9004
www.test   static_response

Have a look at various Caddy Configuration URLs:

  • localhost:2019/config/
  • localhost:2019/config/apps/http/servers/server0
  • The various website definitions (using @id (caddyserver docs/api#using-id-in-json)
    • localhost:2019/id/ping
    • localhost:2019/id/ping/handle/0/body
    • localhost:2019/id/test
    • localhost:2019/id/www.test

And the three websites:

  • The ping website ping.localhost
  • The test website test.localhost
  • Redirecting www www.test.localhost

Notes:

  • Most configuration errors return an unhelpful status 500. The Caddy log might give a hint about the error.

Sample configuration extract

localhost:2019/id/ping/ response:

{
  "@id": "ping",
  "handle": [
    {
      "body": "{\"result\":\"ok\", \"unix_ms\":\"{time.now.unix_ms}\"}",
      "handler": "static_response",
      "headers": {
        "Content-Type": [
          "application/json"
        ]
      },
      "status_code": 200
    }
  ],
  "match": [
    {
      "host": [
        "ping.localhost"
      ]
    }
  ],
  "terminal": true
}

Other API functions

You can also perform specific functions (e.g. stopping Caddy)

curl -X POST "http://localhost:2019/stop"

The base JSON Config Structure is:

{
    "admin": {•••},
    "logging": {•••},
    "storage": {•••},
    "apps": {•••}
}

Each route has the structure (within the whole JSON config):

{"apps": 
    {"http": 
        {"servers": 
            {"server0": 
                {"listen": [":443"], 
                    "routes": [
                        {
                            "group": "",
                            "match": [{•••}],
                            "handle": [{•••}],
                            "terminal": false
                        },
                        {•••},
                        {•••},
                    ]
                }
            }
        }
    }
}

Because you can POST to a point within the overall structure, code can be simpler, for example:

def simple_website(website_id, host=None, upstream=None):
    config = dict(
        match=[
            dict(
                host=[host])
            ],
        terminal=True,
        handle=[
            dict(
                handler='reverse_proxy',
                upstreams=[dict(dial=upstream)])
        ])

    config['@id'] = website_id

    return api(path='apps/http/servers/server0/routes', method='POST', data=config)

With a call such as:

simple_website('test', host='test.localhost', upstream='localhost:9004')

Vars

There is also a function to define variables to use in configuration strings:

handle=[
    dict(
        handler='vars',
        foo=bar),
    dict(
        handler='static_response',
        •••)
    ])

The variables are accessed using a string such as:

"{http.vars.foo}"
1 Like