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}"