note

This article was last updated on February 1, 2024, 4 months ago. The content may be out of date.

Caddy is a server best known for its auto HTTPS features. It manages TLS certificates automatically without any human intervention. It’s also quite extensible, there are some built-in namespaces that define how we can implement some interfaces to add new functionalities to Caddy.

First, let’s see how Caddy works.

Caddy Architecture

Caddy has three parts: a commandline interface, a core and module system.

Command Interface

It is how Caddy interprets commandline arguments to run some common tasks such as printing help information and running a simple reverse proxy or a file server.

note

We can get a glimpse of how to generate a Caddy configuration programmatically from the code of Caddy. For example, when running a simple file server, Caddy will load several modules and serialize their configurations to json format. The resulting configuration is then run by Caddy core.

Core

Caddy core is responsible for configuration management: validating, running, reloading and stopping. Validation is done before running and reloading a configuration. When reloading, the new configuration is ready before the old configuration is stopped.

Caddy loads configuration by parsing the json configuration which is what Caddy natively supports - every other types of configuration first need to be adapted to json before they can be used. Caddy supports Caddyfile by default.

Modules

Caddy modules are what actually do the most of the heavy work. Built-in Caddy modules support TLS certificates management and HTTP servers. They are what we call Caddy plugins.

Every module has a life cycle that is related to the configuration they are run in.

note

These methods are called by the core automatically during their life cycles.

Implementing Caddy Plugins

Now that we know the basics of Caddy plugins, we can implement one that does absolutely nothing.

package mymodule

import "github.com/caddyserver/caddy/v2"

func init() {
	caddy.RegisterModule(Gizmo{})
}

// Gizmo is an example; put your own type here.
type Gizmo struct {
}

// CaddyModule returns the Caddy module information.
func (Gizmo) CaddyModule() caddy.ModuleInfo {
	return caddy.ModuleInfo{
		ID:  "foo.gizmo",
		New: func() caddy.Module { return new(Gizmo) },
	}
}

This is from the official document. This is the bare minimal a plugin can be. To make a useful plugin, we’ll need to choose an appropriate ID and implement the necessary interface(s). We’ll continue this in the next post of this series.