temporalio/temporal · error

unable to load config: %w

Error message

unable to load config: %w

What it means

This error is returned by serverOptions.loadAndValidate in the temporal server SDK when loading the server configuration fails (temporal/server_options.go:94). It wraps the underlying error from loadConfig, which itself loads YAML config files via common/config.Load. It is thrown because the server cannot start without a valid, fully-loaded configuration, so it aborts before validation. The wrapped message (typically "could not load config file: ...") identifies the actual cause.

Source

Thrown at temporal/server_options.go:94

	}
	for _, opt := range opts {
		opt.apply(so)
	}

	return so
}

func (so *serverOptions) loadAndValidate() error {
	for serviceName := range so.serviceNames {
		if !slices.Contains(Services, string(serviceName)) {
			return fmt.Errorf("invalid service %q in service list %v", serviceName, so.serviceNames)
		}
	}

	if so.config == nil {
		err := so.loadConfig()
		if err != nil {
			return fmt.Errorf("unable to load config: %w", err)
		}
	}

	err := so.validateConfig()
	if err != nil {
		return fmt.Errorf("config validation error: %w", err)
	}

	return nil
}

func (so *serverOptions) loadConfig() error {
	if so.configFilePath != "" {
		if so.env != "" || so.configDir != "" || so.zone != "" {
			return errors.New("env, config, zone can not be set if configFilePath is set")
		}
		cfg, err := config.Load(
			config.WithConfigFile(so.configFilePath),

View on GitHub (pinned to bde624efd1)

Solutions

  1. Read the wrapped cause after 'unable to load config: ' and fix that root error first (missing file, bad YAML, invalid path).
  2. Verify the file passed to temporal.WithConfigFilePath exists and is valid YAML (temporal server config format with top-level keys like persistence, services, global).
  3. If using WithConfigDir/WithEnv/WithZone, confirm <configDir>/<env>.yaml (or base.yaml + <env>.yaml) exists; default configDir is ./config and default env is 'development'.
  4. Do not set Env/ConfigDir/Zone together with ConfigFilePath — that yields a distinct 'env, config, zone can not be set if configFilePath is set' error wrapped here.
  5. Alternatively pass a fully built config programmatically so config loading is skipped entirely.

Example fix

// before
server, err := temporal.NewServer(
    temporal.WithConfigFilePath("/etc/temporal/config.yaml"), // file does not exist
)
// after
server, err := temporal.NewServer(
    temporal.WithConfigFilePath("/etc/temporal/config/config_template.yaml"), // verified path
)
Defensive patterns

Strategy: validation

Validate before calling

path := "/etc/temporal/config/config_template.yaml"
if _, err := os.Stat(path); err != nil {
    return fmt.Errorf("config file %s missing before starting server: %w", path, err)
}

Try / catch

server, err := temporal.NewServer(opts...)
if err != nil {
    var cfgErr interface{ Unwrap() error }
    if strings.Contains(err.Error(), "unable to load config") {
        log.Fatalf("config load failed: %v", err) // inspect wrapped cause
    }
    return err
}

Prevention

When it happens

Trigger: Calling temporal.NewServer / temporal.NewServerFgBackgroundService (which calls loadAndValidate) with options where so.config is nil and config.Load fails: e.g. WithConfigFilePath pointing at a nonexistent/malformed YAML file, or WithConfigDir/WithEnv/WithZone resolving to a missing config file, or an env/configDir/zone combined with configFilePath.

Common situations: Typo'd or missing config file path, running the server in a container without mounting /etc/temporal/config, using an env name (development/production/docker) whose config directory does not exist, YAML syntax errors, or accidentally setting both ConfigFilePath and Env.

Related errors


AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01). Data as JSON: /api/errors/9fbfa72b4a4635b8. Report an issue: GitHub.