jaegertracing/jaeger · error
invalid server URL %q: %w
Error message
invalid server URL %q: %w
What it means
newRawClient parses each configured server address with net/url before building the connection pool. This error means url.Parse itself rejected the string (control characters, invalid percent-escapes, malformed IPv6 brackets, etc.), so the client cannot be constructed at all. It is returned from NewClient and surfaces at storage initialization time, not at request time.
Source
Thrown at internal/storage/elasticsearch/esclient/transport.go:65
// discoverNodes enables one-shot node discovery (sniffing) at startup.
discoverNodes bool
// logLevel selects the client log-level (debug/info/error); empty disables
// client logging. logger is the destination for those logs.
logLevel string
logger *zap.Logger
}
// newRawClient builds a rawClient that round-robins requests across servers,
// sending each through base.
func newRawClient(ctx context.Context, base http.RoundTripper, opts rawClientOptions) (*rawClient, error) {
if len(opts.servers) == 0 {
return nil, errors.New("no servers specified")
}
urls := make([]*url.URL, 0, len(opts.servers))
for _, server := range opts.servers {
u, err := url.Parse(server)
if err != nil {
return nil, fmt.Errorf("invalid server URL %q: %w", server, err)
}
// url.Parse accepts host:port or bare hosts as scheme/path-only URLs; the
// pool needs a scheme and host, so reject those up front with a clear error.
if u.Scheme == "" || u.Host == "" {
return nil, fmt.Errorf("server URL %q must include a scheme and host, e.g. http://host:9200", server)
}
urls = append(urls, u)
}
// Retry is disabled to preserve the current admin-client behavior; the data
// plane can opt into the pool's read retry when it adopts rawClient in Stage B.
transportOpts := []elastictransport.Option{
elastictransport.WithURLs(urls...),
elastictransport.WithTransport(base),
elastictransport.WithDisableRetry(),
}
if opts.compressRequestBody {
// Defaults to gzip.DefaultCompression, matching the level the olivere
// client used before it was retired. The pool gzips the body and setsView on GitHub (pinned to 806f444784)
Solutions
- Print the exact server string from the error message and inspect it for stray characters (spaces, quotes, newlines, commas).
- Fix the URL to a valid form, e.g. http://host:9200 or http://[::1]:9200 for IPv6, and URL-escape any special characters.
- If passing multiple servers, provide them as separate list elements (or split on commas) rather than one concatenated string.
- If %-escapes are intended, percent-encode them correctly (e.g. %25 for a literal %).
Example fix
// before servers: ["http://es-1:9200,http://es-2:9200"] // after servers: - http://es-1:9200 - http://es-2:9200
Defensive patterns
Strategy: validation
Validate before calling
for _, s := range servers {
u, err := url.Parse(strings.TrimSpace(s))
if err != nil {
return fmt.Errorf("invalid server URL %q: %w", s, err)
}
if u.Scheme == "" || u.Host == "" {
return fmt.Errorf("server URL %q must include scheme and host", s)
}
} Type guard
func isValidServerURL(s string) bool {
u, err := url.Parse(strings.TrimSpace(s))
return err == nil && u.Scheme != "" && u.Host != ""
} Try / catch
client, err := esclient.NewClient(...)
if err != nil {
if strings.Contains(err.Error(), "invalid server URL") {
log.Fatalf("bad ES server config: %v", err) // fail fast at startup, not at request time
}
return err
} Prevention
- Validate every server URL with url.Parse (plus scheme/host check) in config parsing, before the client is constructed.
- Trim whitespace and strip surrounding quotes from env-var/YAML config values.
- Pass multiple servers as separate list elements, never as one comma-joined string.
- Use bracketed IPv6 form http://[::1]:9200 and percent-encode special characters.
- Fail fast: surface the raw string from the error message when logging the config at startup.
When it happens
Trigger: Calling NewClient (or the tests that build raw clients) with a server string net/url cannot parse — e.g. a URL containing raw spaces or control characters, invalid %-encoding like http://host:9200/%zz, or unbalanced brackets in an IPv6 literal like http://[::1:9200. Comma-separated or quoted strings leaked in from misparsed config will also fail here.
Common situations: Env var SPAN_STORAGE_TYPE elasticsearch config with stray whitespace/newlines or surrounding quotes pasted into the servers list; Kubernetes env interpolation introducing control characters; hand-edited YAML producing 'http://es:9200,http://es2:9200' as a single element; shell quoting stripping characters like [ or ] from IPv6 addresses.
Understand the failure class
Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.
Related errors
- server with TLS enabled can not use same host ports for gRPC
- service/operation reads require a searcher, but this storage
- error creating Elasticsearch client: %w
- failed to create Elasticsearch client: %w
- invalid HTTP server host:port: %w
AI-assisted analysis of jaegertracing/jaeger@806f444784 (2026-09-01).
Data as JSON: /api/errors/27ca262c41c7fcdc.
Report an issue: GitHub.