jaegertracing/jaeger · error
server URL %q must include a scheme and host, e.g. http://ho
Error message
server URL %q must include a scheme and host, e.g. http://host:9200
What it means
newRawClient validates each configured Elasticsearch server URL after url.Parse. Because url.Parse accepts bare hosts and host:port strings as scheme-less/path-only URLs, the client explicitly rejects any URL missing a scheme or host so the connection pool always has well-formed node endpoints. The error names the offending server string and shows the expected shape (e.g. http://host:9200).
Source
Thrown at internal/storage/elasticsearch/esclient/transport.go:70
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 sets
// Content-Encoding before handing the request to base, so the auth stack
// below (notably the SigV4 signer) signs the compressed payload that
// actually goes on the wire.
transportOpts = append(transportOpts, elastictransport.WithCompression())
}View on GitHub (pinned to 806f444784)
Solutions
- Add the scheme to every server URL, e.g. change "localhost:9200" to "http://localhost:9200" (or "https://..." for TLS).
- Check the config source (CLI flag --es.servers, env var, YAML) for values missing the scheme or with empty entries.
- Ensure no template/env expansion produced an empty or truncated host before the scheme, e.g. "http://" alone is also rejected because Host is empty.
Example fix
// before servers: ["es-cluster:9200"] // after servers: ["http://es-cluster:9200"]
Defensive patterns
Strategy: validation
Validate before calling
for _, s := range cfg.Servers {
u, err := url.Parse(s)
if err != nil || u.Scheme == "" || u.Host == "" {
return fmt.Errorf("server %q must be an absolute URL, e.g. http://host:9200", s)
}
} Type guard
func isValidServerURL(s string) bool {
u, err := url.Parse(s)
return err == nil && u.Scheme != "" && u.Host != ""
} Prevention
- Always write server URLs with an explicit http:// or https:// scheme in config files and env vars.
- Add a startup config validation step that rejects scheme-less hosts before constructing the storage client.
- Lint CI configs for elasticsearch server entries missing a scheme.
- Never build server URLs by string concatenation that can drop the prefix (e.g. fmt.Sprintf("%s:9200", host)).
When it happens
Trigger: Calling NewClient (via newRawClient) with a server entry like "localhost:9200", "es-cluster", ":9200", or an empty string — any URL that parses but lacks u.Scheme or u.Host.
Common situations: Config files where SPAN_STORAGE_TYPE=elasticsearch servers are set without the http:// or https:// prefix; YAML/env config where the scheme was dropped during migration from an older client that tolerated bare hosts; template variables that rendered to an empty or partial host.
Related errors
- error generating mappings: %w
- file must begin with '['
- max spans count reached
- empty configuration
- at least one storage backend is required
AI-assisted analysis of jaegertracing/jaeger@806f444784 (2026-09-01).
Data as JSON: /api/errors/890cc69ad862e22e.
Report an issue: GitHub.