caddyserver/caddy · error
module value cannot be null
Error message
module value cannot be null
What it means
After decoding, LoadModuleByID rejects an explicitly null module value: 'module value cannot be null'. Because New() always returns a non-nil pointer, this triggers when the JSON config declares the module as null and the loader treats the resulting state as an attempted null module — which is almost always a config smell rather than a legitimate setup.
Source
Thrown at context.go:402
" using new(Type) or &Type notation in your module's New() function.", id)
val = reflect.New(rv.Type()).Elem().Addr().Interface().(Module)
}
// fill in its config only if there is a config to fill in
if len(rawMsg) > 0 {
err := StrictUnmarshalJSON(rawMsg, &val)
if err != nil {
return nil, fmt.Errorf("decoding module config: %s: %v", modInfo, err)
}
}
if val == nil {
// returned module values are almost always type-asserted
// before being used, so a nil value would panic; and there
// is no good reason to explicitly declare null modules in
// a config; it might be because the user is trying to achieve
// a result the developer isn't expecting, which is a smell
return nil, fmt.Errorf("module value cannot be null")
}
var err error
// if this is an app module, keep a reference to it,
// since submodules may need to reference it during
// provisioning (even though the parent app module
// may not be fully provisioned yet; this is the case
// with the tls app's automation policies, which may
// refer to the tls app to check if a global DNS
// module has been configured for DNS challenges)
if appModule, ok := val.(App); ok {
ctx.cfg.apps[id] = appModule
defer func() {
if err != nil {
ctx.cfg.failedApps[id] = err
}
}()View on GitHub (pinned to 50e54ee279)
Solutions
- Remove the null entry entirely from the JSON config instead of setting it to null
- If templating, emit conditional blocks that omit keys rather than render "key": null
- Validate with `caddy validate` to catch it before reload/deploy
Example fix
// before
{ "apps": { "http": null } }
// after
{ "apps": { } } // or omit the key entirely Defensive patterns
Strategy: validation
Validate before calling
// Strip explicit nulls from templated configs before applying
func stripNulls(v any) any {
switch t := v.(type) {
case map[string]any:
for k, val := range t {
if val == nil { delete(t, k) } else { t[k] = stripNulls(val) }
}
case []any:
for i := range t { t[i] = stripNulls(t[i]) }
}
return v
} Try / catch
if _, err := ctx.LoadModuleByID(id, raw); err != nil { return err } // 'cannot be null' means remove the key Prevention
- Omit unused module blocks instead of nulling them
- In templates, render blocks conditionally rather than as null
- Run `caddy validate` on templated output
When it happens
Trigger: A JSON config contains "some_module": null where a module object is expected (e.g. an app or inline module set to null), or a map entry whose value is JSON null passed through to LoadModuleByID with empty rawMsg handling.
Common situations: Templated configs (Helm, envsubst) rendering optional blocks as null instead of omitting them; disabling a feature by nulling it out; JSON merge patches leaving explicit nulls.
Related errors
- decoding request body: %w, at offset %d
- decoding request body: %w
- final element is not an array
- position %d: %v
- key %s: %v
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/f8df2663ab92d489.
Report an issue: GitHub.