caddyserver/caddy · error

%s app module: start: %v

Error message

%s app module: start: %v

What it means

run() iterates the config's apps calling Start(); the first app whose Start() returns an error aborts startup with '<name> app module: start: %v' (e.g. 'http app module: start: ...'). Prior apps are stopped for a clean unwind. The app name is the module namespace of the app (http, tls, pk, or a plugin app).

Source

Thrown at caddy.go:458

		}
	}()

	// Start
	err = func() error {
		started := make([]string, 0, len(ctx.cfg.apps))
		for name, a := range ctx.cfg.apps {
			err := a.Start()
			if err != nil {
				// an app failed to start, so we need to stop
				// all other apps that were already started
				for _, otherAppName := range started {
					err2 := ctx.cfg.apps[otherAppName].Stop()
					if err2 != nil {
						err = fmt.Errorf("%v; additionally, aborting app %s: %v",
							err, otherAppName, err2)
					}
				}
				return fmt.Errorf("%s app module: start: %v", name, err)
			}
			started = append(started, name)
		}
		return nil
	}()
	if err != nil {
		return ctx, err
	}
	globalMetrics.configSuccess.Set(1)
	globalMetrics.configSuccessTime.SetToCurrentTime()

	// TODO: This event is experimental and subject to change.
	ctx.emitEvent("started", nil)

	// now that the user's config is running, finish setting up anything else,
	// such as remote admin endpoint, config loader, etc.
	err = finishSettingUp(ctx, ctx.cfg)
	return ctx, err

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Read the wrapped %v — for http it usually names the exact address that failed to bind.
  2. Free the conflicting listener (ss -ltnp / lsof -i :<port>) or change the site's port.
  3. For privileged ports: setcap 'cap_net_bind_service=+ep' $(which caddy) or run via a systemd unit with CAP_NET_BIND_SERVICE.
  4. Retry the load/restart after fixing; earlier apps were already unwound automatically.

Example fix

# before: port 80 taken by nginx
example.com {
  bind 0.0.0.0
}

# after: free the port or move aside
systemctl stop nginx && caddy run --config Caddyfile
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight: check every listen address is bindable before loading.
for _, addr := range listenAddrs {
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return fmt.Errorf("would fail to bind %s: %w", addr, err)
    }
    ln.Close()
}

Try / catch

if _, err := caddy.Run(cfg); err != nil {
    if strings.Contains(err.Error(), "app module: start:") {
        // named app failed; unwrap and fix its resource (port/socket/creds)
    }
}

Prevention

When it happens

Trigger: http app failing to bind configured listen addresses (port in use, permission on <1024, bad unix socket); tls app failing ACME/TLS provider setup; a plugin app whose Start errors; happens on initial load and on config reloads via unsyncedDecodeAndRun.

Common situations: Another process (or a second Caddy instance) already bound the port; binding :80/:443 as non-root without setcap; unix socket dir missing or wrong permissions; plugin resource limits (file descriptors) at startup.

Related errors


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