Skip to main content

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
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:
1
Add an ID to Configuration
2
{
  "apps": {
    "http": {
      "servers": {
        "myserver": {
          "@id": "my-server",
          "listen": [":80"]
        }
      }
    }
  }
}
3
Access by ID
4
# 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

1
API Usage Guidelines
2
  • 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"
    

    Build docs developers (and LLMs) love