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
- Remove the `resolver_preload` block from the config if using a URL resolver
- Switch to a writeable resolver: `resolver: MEMORY` or `resolver: DIR /path` and keep the preloads
- 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
- Only set resolver_preload with resolver: MEMORY or DIR/CACHE_DIR
- Keep separate config templates for leaf/spoke (URL resolver) vs standalone (MEM) setups
- Lint configs in CI to reject preload+URL-resolver combinations
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
- account jwt not found
- failed to create mapping transform for stream import subject
- store operation not supported for URL Resolver
- delete must be enabled in server config
- Fetch timeout %v is too smal
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/638e291d9b7ceeb7.
Report an issue: GitHub.