neondatabase/neon · error
invalid shard URL {url}: must use gRPC
Error message
invalid shard URL {url}: must use gRPC What it means
The gRPC pageserver client only talks to pageservers over gRPC. Each URL is checked with PageserverProtocol::from_connstring (libs/compute_api/src/spec.rs), which maps postgresql:// and postgres:// to Libpq and, importantly, defaults to Libpq when the string has no URL scheme at all. Any URL that does not resolve to the grpc:// scheme is rejected.
Source
Thrown at pageserver/client_grpc/src/client.rs:424
return Err(anyhow!("stripe size can't be given for unsharded tenants"));
}
// Validate the shard spec.
for (shard_id, url) in &urls {
// The shard index must match the computed shard count, even for unsharded tenants.
if shard_id.shard_count != count {
return Err(anyhow!("invalid shard index {shard_id}, expected {count}"));
}
// The shard index' number and count must be consistent.
if !shard_id.is_unsharded() && shard_id.shard_number.0 >= shard_id.shard_count.0 {
return Err(anyhow!("invalid shard index {shard_id}"));
}
// The above conditions guarantee that we have all shards 0..count: len() matches count,
// shard number < count, and numbers are unique (via hashmap).
// Validate the URL.
if PageserverProtocol::from_connstring(url)? != PageserverProtocol::Grpc {
return Err(anyhow!("invalid shard URL {url}: must use gRPC"));
}
}
Ok(Self {
urls,
count,
stripe_size,
})
}
}
/// Tracks the tenant's shards.
struct Shards {
/// Shards by shard index.
///
/// INVARIANT: every shard 0..count is present.
/// INVARIANT: shard 0 is always present.
by_index: HashMap<ShardIndex, Shard>,View on GitHub (pinned to 8f60b04da4)
Solutions
- Prefix every shard URL with grpc://, for example grpc://pageserver-0.ps.svc:7000.
- Search the spec or config for postgres://, postgresql://, and scheme-less host:port entries and rewrite them.
- If from_connstring itself failed ("invalid connstring URL"), fix the URL syntax first; that error propagates before the protocol comparison.
Example fix
// before let urls = HashMap::from([(idx, "postgresql://ps-0:6400".to_string())]); let spec = ShardSpec::new(urls, stripe)?; // -> "invalid shard URL postgresql://ps-0:6400: must use gRPC" // after let urls = HashMap::from([(idx, "grpc://ps-0:7000".to_string())]); let spec = ShardSpec::new(urls, stripe)?;
Defensive patterns
Strategy: validation
Validate before calling
// all URLs must resolve to the grpc protocol before building the spec
for url in urls.values() {
anyhow::ensure!(
PageserverProtocol::from_connstring(url)? == PageserverProtocol::Grpc,
"URL {url} must use the grpc:// scheme"
);
} Type guard
fn is_grpc_connstring(url: &str) -> bool {
url.trim_start_matches("grpc://").len() != url.len()
} Prevention
- Default connstrings to the grpc:// scheme at the config layer for gRPC clients.
- Lint specs for postgres://, postgresql://, and scheme-less host:port entries before deployment.
- Remember from_connstring defaults a scheme-less string to Libpq, not to an error.
When it happens
Trigger: ShardSpec::new with a connstring like postgresql://host:6400, or a bare host:port with no scheme (parses as relative URL, defaults to Libpq). Only grpc://host:port passes the check.
Common situations: Reusing libpq pageserver connstrings from older configurations; environment variables or specs that omit the scheme; documentation examples that predate the gRPC protocol.
Related errors
- no grpc_url for shard {shard_index}
- invalid protocol scheme: {scheme}
- Unsupported key decoded at LSN {}: {}
- only unsharded tenants are supported at this time: {}
- Error: {:?}
AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16).
Data as JSON: /api/errors/34ee678772340906.
Report an issue: GitHub.