A ZeroSSL Project Store Forum GitHub Theme: System
Caddy web server Caddy web server
  • Documentation
    Install How to install Caddy on your computer or server Caddyfile A simplified, human-friendly configuration format JSON Powerful and programmable native config format
    Tutorials
    Caddy basics Static files Reverse proxy Troubleshooting
    Reference
    Command line Caddyfile API JSON Config Auto HTTPS
    Develop
    Architecture Contribute Write a module
    Articles
    Logging Monitoring Run as service Profiling All articles...
    Examples Learn from examples contributed by the community FAQ Get fast answers to common questions Modules The official list of registered Caddy plugins
    Documentation index Community wiki Contribute to docs
  • Features
  • Account
  • Support
Download Sponsor
Pages
  • Get Caddy
  • Install
  • Build from source
  • Tutorials
  • Getting Started
  • Quick-starts
    • Using the API
    • Using a Caddyfile
    • Static files
    • Reverse proxy
    • HTTPS
  • Caddy API
  • Caddyfile
  • Reference
  • Command Line
  • API
  • Caddyfile
    • Concepts
    • Global options
    • Directives
    • Request matchers
    • Response matchers
    • Common patterns
  • Modules
  • JSON Config Structure
  • Automatic HTTPS
  • Articles
  • Caddy Architecture
  • Conventions
  • Config Adapters
  • Keep Caddy Running
  • How Logging Works
  • Monitoring Caddy
  • Profiling Caddy
  • Verifying Asset Signatures
  • Troubleshooting Strategies
  • Developers
  • Extending Caddy
    • Caddyfile Support
    • Config Adapters
    • Placeholders
  • Module Namespaces

API Tutorial

This tutorial will show you how to use Caddy's admin API, which makes it possible to automate in a programmable fashion.

Objectives:

  • 🔲 Run the daemon
  • 🔲 Give Caddy a config
  • 🔲 Test config
  • 🔲 Replace active config
  • 🔲 Traverse config
  • 🔲 Use @id tags

Prerequisites:

  • Basic terminal / command line skills
  • Basic JSON experience
  • caddy and curl in your PATH

To start the Caddy daemon, use the run subcommand:

caddy run
Run the daemon

This blocks forever, but what is it doing? At the moment... nothing. By default, Caddy's configuration ("config") is blank. We can verify this using the admin API in another terminal:

curl localhost:2019/config/

We can make Caddy useful by giving it a config. One way to do this is by making a POST request to the /load endpoint. Just like any HTTP request, there are many ways to do this, but in this tutorial we'll use curl.

Your first config

To prepare our request, we need to make a config. Caddy's configuration is simply a JSON document (or anything that converts to JSON).

Config files are not required. The configuration API can always be used without files, which is handy when automating things. This tutorial uses a file because it is more convenient for editing by hand.

Save this to a JSON file:

{
	"apps": {
		"http": {
			"servers": {
				"example": {
					"listen": [":2015"],
					"routes": [
						{
							"handle": [{
								"handler": "static_response",
								"body": "Hello, world!"
							}]
						}
					]
				}
			}
		}
	}
}

Then upload it:

curl localhost:2019/load \
	-H "Content-Type: application/json" \
	-d @caddy.json
Make sure you don't forget the @ in front of your filename; this tells curl you are sending a file.
Give Caddy a config

We can verify that Caddy applied our new config with another GET request:

curl localhost:2019/config/

Test that it works by going to localhost:2015 in your browser or using curl:

curl localhost:2015
Hello, world!
Test config

If you see Hello, world!, then congrats -- it's working! It's always a good idea to make sure your config works as you expect, especially before deploying into production.

Let's change our welcome message from "Hello world!" to something a little more motivational: "I can do hard things." Make this change in your config file, so that the handler object now looks like this:

{
	"handler": "static_response",
	"body": "I can do hard things."
}

Save the config file, then update Caddy's active configuration by running the same POST request again:

curl localhost:2019/load \
	-H "Content-Type: application/json" \
	-d @caddy.json
Replace active config

For good measure, verify that the config was updated:

curl localhost:2019/config/

Test it by refreshing the page in your browser (or running curl again), and you will see an inspirational message!

Config traversal

Instead of uploading the entire config file for a small change, let's use a powerful feature of Caddy's API to make the change without ever touching our config file.

Making little changes to production servers by replacing the entire config like we did above can be dangerous; it's like having root access to a file system. Caddy's API lets you limit the scope of your changes to guarantee that other parts of your config don't get changed accidentally.

Using the request URI's path, we can traverse into the config structure and update only the message string (be sure to scroll right if clipped):

curl \
	localhost:2019/config/apps/http/servers/example/routes/0/handle/0/body \
	-H "Content-Type: application/json" \
	-d '"Work smarter, not harder."'

Every time you change the config using the API, Caddy persists a copy of the new config so you can --resume it later!

You can verify that it worked with a similar GET request, for example:

curl localhost:2019/config/apps/http/servers/example/routes

You should see:

[{"handle":[{"body":"Work smarter, not harder.","handler":"static_response"}]}]

You can use the jq command to prettify JSON output: curl ... | jq

Traverse config

Important note: This should be obvious, but once you use the API to make a change that is not in your original config file, your config file becomes obsolete. There are a few ways to handle this:

  • Use the --resume of the caddy run command to use the last active config.
  • Don't mix the use of config files with changes via the API; have one source of truth.
  • Export Caddy's new configuration with a subsequent GET request (less recommended than the first two options).

Using @id in JSON

Config traversal is certainly useful, but the paths are little long, don't you think?

We can give our handler object an @id tag to make it easier to access:

curl \
	localhost:2019/config/apps/http/servers/example/routes/0/handle/0/@id \
	-H "Content-Type: application/json" \
	-d '"msg"'

This adds a property to our handler object: "@id": "msg", so it now looks like this:

{
	"@id": "msg",
	"body": "Work smarter, not harder.",
	"handler": "static_response"
}

@id tags can go in any object and can have any primitive value (usually a string). Learn more

We can then access it directly:

curl localhost:2019/id/msg

And now we can change the message with a shorter path:

curl \
	localhost:2019/id/msg/body \
	-H "Content-Type: application/json" \
	-d '"Some shortcuts are good."'

And check it again:

curl localhost:2019/id/msg/body
Use @id tags
On this page
Caddy web server

A free open source project that relies on sponsors.

Privacy-respecting analytics by Fathom


© 2026 ZeroSSL. All rights reserved.

Project
Features Download Documentation
Business services
Support Sponsorships
Community
Forum GitHub Twitter / X Research

Caddy supports an open Web that promotes privacy, preserves data ownership, fosters innovation, freely allows varieties of client software, and safeguards human sanctity.

002859