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

  1. Prefix every shard URL with grpc://, for example grpc://pageserver-0.ps.svc:7000.
  2. Search the spec or config for postgres://, postgresql://, and scheme-less host:port entries and rewrite them.
  3. 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

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


AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16). Data as JSON: /api/errors/34ee678772340906. Report an issue: GitHub.