caddyserver/caddy · error

config is not valid JSON: %w, at offset %d; did you mean to

Error message

config is not valid JSON: %w, at offset %d; did you mean to use a config adapter (the --adapter flag)?

What it means

No adapter applied, so Caddy validated the raw config as JSON and hit a json.SyntaxError — structural JSON breakage (trailing comma, unquoted key, stray character). The byte offset of the failure is included, and the hint suggests --adapter for non-JSON inputs. This is the offset-carrying variant of the two JSON validation messages.

Source

Thrown at cmd/main.go:235

		}
		logger.Info("adapted config to JSON", zap.String("adapter", adapterName))
		for _, warn := range warnings {
			msg := warn.Message
			if warn.Directive != "" {
				msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message)
			}
			logger.Warn(msg,
				zap.String("adapter", adapterName),
				zap.String("file", warn.File),
				zap.Int("line", warn.Line))
		}
		config = adaptedConfig
	} else if len(config) != 0 {
		// validate that the config is at least valid JSON
		err = json.Unmarshal(config, new(any))
		if err != nil {
			if jsonErr, ok := err.(*json.SyntaxError); ok {
				return nil, "", "", fmt.Errorf("config is not valid JSON: %w, at offset %d; did you mean to use a config adapter (the --adapter flag)?", err, jsonErr.Offset)
			}
			return nil, "", "", fmt.Errorf("config is not valid JSON: %w; did you mean to use a config adapter (the --adapter flag)?", err)
		}
	}

	return config, configFile, adapterName, nil
}

// watchConfigFile watches the config file at filename for changes
// and reloads the config if the file was updated. This function
// blocks indefinitely; it only quits if the poller has errors for
// long enough time. The filename passed in must be the actual
// config file used, not one to be discovered.
// Each second the config files is loaded and parsed into an object
// and is compared to the last config object that was loaded
func watchConfigFile(filename, adapterName string) {
	defer func() {
		if err := recover(); err != nil {

View on GitHub (pinned to 50e54ee279)

Solutions

  1. If the file is a Caddyfile, add --adapter caddyfile (or name the file 'Caddyfile' for auto-detection)
  2. If it is JSON, jump to the reported offset and fix the syntax error
  3. Lint first: jq . config.json — jq pinpoints the same offset

Example fix

# before
caddy run --config Caddyfile.prod          # it is Caddyfile syntax, parsed as JSON

# after
caddy run --config Caddyfile.prod --adapter caddyfile
Defensive patterns

Strategy: validation

Validate before calling

// Detect non-JSON input up front and require an adapter:
if !json.Valid(rawConfig) { if adapterName == "" { return errors.New("config is not JSON; pass --adapter (e.g. caddyfile)") } }

Type guard

func looksLikeJSON(b []byte) bool { t := bytes.TrimSpace(b); return len(t) > 0 && (t[0] == '{' || t[0] == '[') }

Prevention

When it happens

Trigger: Passing a Caddyfile (or YAML etc.) as --config without --adapter; hand-edited JSON config with a syntax slip; the offset in the message maps directly to the offending byte.

Common situations: Forgetting that JSON, not Caddyfile, is the native format; tools emitting near-JSON (comments, trailing commas) that strict encoding/json rejects.

Related errors


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