caddyserver/caddy · error

loading certificates with 'automate' requires array of strin

Error message

loading certificates with 'automate' requires array of strings, got: %T

What it means

The special 'automate' entry under tls.certificates must unmarshal to *AutomateLoader, which is defined as a []string of subject names to be managed by automation. The type assertion modIface.(*AutomateLoader) failed (or yielded nil), meaning the JSON value for "automate" was not a plain array of strings.

Source

Thrown at modules/caddytls/tls.go:240

	// certificate loaders
	val, err := ctx.LoadModule(t, "CertificatesRaw")
	if err != nil {
		return fmt.Errorf("loading certificate loader modules: %s", err)
	}
	for modName, modIface := range val.(map[string]any) {
		if modName == "automate" {
			// special case; these will be loaded in later using our automation facilities,
			// which we want to avoid doing during provisioning
			if automateNames, ok := modIface.(*AutomateLoader); ok && automateNames != nil {
				if t.automateNames == nil {
					t.automateNames = make(map[string]struct{})
				}
				repl := caddy.NewReplacer()
				for _, sub := range *automateNames {
					t.automateNames[repl.ReplaceAll(sub, "")] = struct{}{}
				}
			} else {
				return fmt.Errorf("loading certificates with 'automate' requires array of strings, got: %T", modIface)
			}
			continue
		}
		t.certificateLoaders = append(t.certificateLoaders, modIface.(CertificateLoader))
	}

	// using the certificate loaders we just initialized, load
	// manual/static (unmanaged) certificates - we do this in
	// provision so that other apps (such as http) can know which
	// certificates have been manually loaded, and also so that
	// commands like validate can be a better test
	certCacheMu.RLock()
	magic := certmagic.New(certCache, certmagic.Config{
		Storage: ctx.Storage(),
		Logger:  t.logger,
		OnEvent: t.onEvent,
		OCSP: certmagic.OCSPConfig{
			DisableStapling: t.DisableOCSPStapling,

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Change the automate value to a JSON array of hostnames only
  2. If you need per-name issuer/policy options, move those names into tls.automation.policies subjects instead of the automate loader
  3. Run 'caddy validate' on the config to catch the type error before runtime

Example fix

// before
{"certificates": {"automate": {"subject": "example.com"}}}
// after
{"certificates": {"automate": ["example.com"]}
Defensive patterns

Strategy: type-guard

Validate before calling

import "encoding/json"

var raw json.RawMessage
json.Unmarshal(cfg, &raw)
var certs map[string]json.RawMessage
json.Unmarshal(raw, &certs)
if v, ok := certs["automate"]; ok {
	var names []string
	if err := json.Unmarshal(v, &names); err != nil {
		return fmt.Errorf("automate must be an array of strings")
	}
}

Type guard

func isAutomateArray(v json.RawMessage) bool {
	var names []string
	return json.Unmarshal(v, &names) == nil
}

Prevention

When it happens

Trigger: Writing {"automate": "example.com"} (a bare string), {"automate": [{"subject": "example.com"}]} (array of objects), or any object form under the automate key. Only {"automate": ["example.com", ...]} is valid.

Common situations: Hand-edited JSON where users assume automate takes an object with options; converting from Caddyfile syntax and guessing the JSON shape; copy-paste from configs written for a different fork/schema.

Understand the failure class

Related errors


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/c4121267ec7c1c2d. Report an issue: GitHub.