Using the Admin API
The Caddy Admin API allows you to manage your server at runtime without restarts. It provides full control over configuration, certificates, and server state.
Admin Endpoint
By default, the admin API listens on localhost:2019:
curl localhost:2019/config/
Custom Listen Address
Change the admin endpoint address:
{
"admin" : {
"listen" : "127.0.0.1:2020"
}
}
The admin endpoint can be configured via the CADDY_ADMIN environment variable, which defaults to localhost:2019.
Disable Admin API
{
"admin" : {
"disabled" : true
}
}
Disabling the admin API makes runtime changes impossible. You’ll need to restart Caddy to apply new configurations.
Configuration Management
Get Current Config
Retrieve the entire configuration:
curl localhost:2019/config/ | jq
Load Configuration
Replace the entire configuration:
curl -X POST localhost:2019/load \
-H "Content-Type: application/json" \
-d @config.json
Update Specific Path
Modify a specific configuration path:
# Add a new route
curl -X POST localhost:2019/config/apps/http/servers/srv0/routes \
-H "Content-Type: application/json" \
-d '{
"match": [{"host": ["example.com"]}],
"handle": [{"handler": "file_server"}]
}'
PATCH vs POST vs PUT
GET - Retrieve configuration
POST - Add to an array or create if doesn’t exist
PUT - Create new value (fails if exists)
PATCH - Update existing value (fails if doesn’t exist)
DELETE - Remove configuration
POST (Append)
PATCH (Update)
DELETE (Remove)
curl -X POST localhost:2019/config/apps/http/servers/srv0/routes/... \
-H "Content-Type: application/json" \
-d '{"handle":[{"handler":"static_response","body":"Hello"}]}'
Using @id for Easier Access
Identify configuration objects with @id for cleaner API paths:
Add an ID to Configuration
{
"apps" : {
"http" : {
"servers" : {
"myserver" : {
"@id" : "my-server" ,
"listen" : [ ":80" ]
}
}
}
}
}
# Instead of: /config/apps/http/servers/myserver
curl localhost:2019/id/my-server
Common Operations
Add a New Site
curl -X POST localhost:2019/config/apps/http/servers/srv0/routes/0 \
-H "Content-Type: application/json" \
-d '{
"@id": "example-site",
"match": [{"host": ["example.com"]}],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handler": "file_server",
"root": "/var/www/html"
}
]
}
]
}
]
}'
Update Upstream Servers
curl -X PATCH localhost:2019/config/apps/http/servers/srv0/routes/0/handle/0/upstreams \
-H "Content-Type: application/json" \
-d '[
{"dial": "10.0.0.1:8080"},
{"dial": "10.0.0.2:8080"},
{"dial": "10.0.0.3:8080"}
]'
Reload Certificates
Trigger certificate renewal:
curl -X POST localhost:2019/config/apps/tls/certificates/automate
Security
Origin Enforcement
Protect against DNS rebinding attacks:
{
"admin" : {
"enforce_origin" : true ,
"origins" : [
"localhost:2019" ,
"admin.example.com:2019"
]
}
}
Remote Admin (TLS)
Secure the admin endpoint with mutual TLS:
{
"admin" : {
"identity" : {
"identifiers" : [ "admin.example.com" ],
"issuers" : [
{ "module" : "acme" , "email" : "[email protected] " }
]
},
"remote" : {
"listen" : ":2021" ,
"access_control" : [
{
"public_keys" : [ "<base64-encoded-public-key>" ],
"permissions" : [
{
"paths" : [ "/config/*" , "/load" ],
"methods" : [ "GET" , "POST" , "PUT" , "PATCH" , "DELETE" ]
}
]
}
]
}
}
}
Remote admin requires identity management to be configured. The endpoint uses mutual TLS for authentication.
Debugging Endpoints
Caddy exposes several debugging endpoints:
pprof Profiling
# CPU profile
curl localhost:2019/debug/pprof/profile?seconds= 30 > cpu.prof
# Memory profile
curl localhost:2019/debug/pprof/heap > mem.prof
# Goroutine dump
curl localhost:2019/debug/pprof/goroutine > goroutines.txt
Metrics
# Expvar metrics
curl localhost:2019/debug/vars | jq
Graceful Shutdown
Stop Caddy gracefully:
curl -X POST localhost:2019/stop
ETag Support
The API supports ETags to prevent concurrent modifications:
# Get config with ETag
CONFIG = $( curl -i localhost:2019/config/ )
ETAG = $( echo " $CONFIG " | grep -i etag | awk '{print $2}' | tr -d '\r' )
# Update only if ETag matches
curl -X PATCH localhost:2019/config/apps/http/servers/srv0 \
-H "Content-Type: application/json" \
-H "If-Match: $ETAG " \
-d '{"listen":[": 8080"]}'
Complete Example
Manage a site lifecycle via API:
#!/bin/bash
API = "http://localhost:2019"
# 1. Add a new site
echo "Adding new site..."
curl -X POST " $API /config/apps/http/servers/srv0/routes/0" \
-H "Content-Type: application/json" \
-d '{
"@id": "my-app",
"match": [{"host": ["app.example.com"]}],
"handle": [{
"handler": "reverse_proxy",
"upstreams": [{"dial": "localhost:8080"}]
}]
}'
# 2. Verify configuration
echo "\nVerifying config..."
curl " $API /id/my-app" | jq
# 3. Update upstream
echo "\nUpdating upstream..."
curl -X PATCH " $API /id/my-app/handle/0/upstreams/0" \
-H "Content-Type: application/json" \
-d '{"dial": "localhost:8081"}'
# 4. Add health check
echo "\nAdding health check..."
curl -X PATCH " $API /id/my-app/handle/0/health_checks" \
-H "Content-Type: application/json" \
-d '{
"active": {
"uri": "/health",
"interval": "30s"
}
}'
# 5. Remove site
echo "\nRemoving site..."
curl -X DELETE " $API /id/my-app"
Best Practices
Use @id fields - Makes configuration paths more readable and stable
Check ETags - Prevent concurrent modification conflicts
Validate JSON - Use jq or similar to validate before posting
Test changes - Use GET to verify changes were applied correctly
Handle errors - Check HTTP status codes and response bodies
The admin API returns detailed error messages in JSON format when operations fail. Always check the response body for debugging information.
Troubleshooting
Connection Refused
# Check if admin endpoint is configured
caddy environ | grep CADDY_ADMIN
# Verify Caddy is running
caddy list-modules --versions | head -1
Permission Denied
Ensure origin/host headers match allowed origins:
curl localhost:2019/config/ -H "Host: localhost:2019"
Invalid JSON
Validate JSON before posting:
jq empty config.json && echo "Valid JSON"