nats-io/nats-server · error

resolver preloads only available for writeable resolver type

Error message

resolver preloads only available for writeable resolver types MEM/DIR/CACHE_DIR

What it means

Resolver preloading (`resolver_preload` in config) is only supported for resolvers that can store data locally: MEM, DIR, and CACHE_DIR. The configured account resolver is read-only (e.g. a plain URL resolver), so preloading has no place to write and startup fails. This is a configuration validation check in ProcessOptions.

Source

Thrown at server/server.go:1514

	s.accResolver = opts.AccountResolver
	if opts.AccountResolver != nil {
		// For URL resolver, set the TLSConfig if specified.
		if opts.AccountResolverTLSConfig != nil {
			if ar, ok := opts.AccountResolver.(*URLAccResolver); ok {
				if t, ok := ar.c.Transport.(*http.Transport); ok {
					t.CloseIdleConnections()
					t.TLSClientConfig = opts.AccountResolverTLSConfig.Clone()
				}
			}
		}
		if len(opts.resolverPreloads) > 0 {
			// Lock ordering is account resolver -> server, so we need to release
			// the lock and reacquire it when done with account resolver's calls.
			ar := s.accResolver
			s.mu.Unlock()
			defer s.mu.Lock()
			if ar.IsReadOnly() {
				return fmt.Errorf("resolver preloads only available for writeable resolver types MEM/DIR/CACHE_DIR")
			}
			for k, v := range opts.resolverPreloads {
				_, err := jwt.DecodeAccountClaims(v)
				if err != nil {
					return fmt.Errorf("preload account error for %q: %v", k, err)
				}
				ar.Store(k, v)
			}
		}
	}
	return nil
}

// This will check preloads for validation issues.
func (s *Server) checkResolvePreloads() {
	opts := s.getOpts()
	// We can just check the read-only opts versions here, that way we do not need
	// to grab server lock or access s.accResolver.

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Remove the `resolver_preload` block from the config if using a URL resolver
  2. Switch to a writeable resolver: `resolver: MEMORY` or `resolver: DIR /path` and keep the preloads
  3. Use CACHE_DIR resolver if you want URL fetching plus local caching and preloads

Example fix

// before
resolver = URL(http://localhost:9090)
resolver_preload = { ACC: "eyJ..." }
// after
resolver = MEMORY
resolver_preload = { ACC: "eyJ..." }
Defensive patterns

Strategy: validation

Validate before calling

// Check config consistency before handing it to the server
func preloadAllowed(resolver string, preloads map[string]string) error {
    writeable := map[string]bool{"MEM": true, "DIR": true, "CACHE_DIR": true}
    if len(preloads) > 0 && !writeable[strings.ToUpper(resolver)] {
        return fmt.Errorf("resolver_preload requires MEM/DIR/CACHE_DIR, got %q", resolver)
    }
    return nil
}

Prevention

When it happens

Trigger: Config combines `resolver = URL(...)` (read-only) with a non-empty `resolver_preload` map; opts.resolverPreloads is iterated only after ar.IsReadOnly() returns true, returning this error immediately.

Common situations: Copying a full standalone-server config (MEM resolver + preloads) into an operator/spoke setup that uses a URL resolver; forgetting that nats-server 2.x splits 'resolver' (URL) from local storage resolvers.

Related errors


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/638e291d9b7ceeb7. Report an issue: GitHub.