neondatabase/neon · error
invalid scheme {scheme}
Error message
invalid scheme {scheme} What it means
pagebench basebackup accepts exactly three scheme values after lowercasing: postgresql and postgres (LibpqClient over the page protocol) and grpc (GrpcClient). Any other scheme reaches the catch-all match arm and returns 'invalid scheme {scheme}'. A common trap: 'localhost:6400' without a prefix parses successfully with 'localhost' as the scheme, producing this error rather than a connstring parse error.
Source
Thrown at pageserver/pagebench/src/cmd/basebackup.rs:175
let mut work_senders = HashMap::new();
let mut tasks = Vec::new();
let scheme = match Url::parse(&args.page_service_connstring) {
Ok(url) => url.scheme().to_lowercase().to_string(),
Err(url::ParseError::RelativeUrlWithoutBase) => "postgresql".to_string(),
Err(err) => return Err(anyhow!("invalid connstring: {err}")),
};
for &tl in &timelines {
let (sender, receiver) = tokio::sync::mpsc::channel(1); // TODO: not sure what the implications of this are
work_senders.insert(tl, sender);
let client: Box<dyn Client> = match scheme.as_str() {
"postgresql" | "postgres" => Box::new(
LibpqClient::new(&args.page_service_connstring, tl, !args.no_compression).await?,
),
"grpc" => Box::new(
GrpcClient::new(&args.page_service_connstring, tl, !args.no_compression).await?,
),
scheme => return Err(anyhow!("invalid scheme {scheme}")),
};
tasks.push(tokio::spawn(run_worker(
client,
Arc::clone(&start_work_barrier),
receiver,
Arc::clone(&all_work_done_barrier),
Arc::clone(&live_stats),
)));
}
let work_sender = async move {
start_work_barrier.wait().await;
loop {
let (timeline, work) = {
let mut rng = rand::rng();
let target = all_targets.choose(&mut rng).unwrap();
let lsn = target.lsn_range.clone().map(|r| rng.random_range(r));View on GitHub (pinned to 8f60b04da4)
Solutions
- Prefix the target with postgresql:// (or postgres://), e.g. postgresql://postgres@localhost:6400
- Use grpc://host:port only when the pageserver's gRPC page_api listener is enabled and reachable
- Check the scheme spelling against the three accepted values
Example fix
# before pagebench basebackup --page-service-connstring 'localhost:6400' # scheme parses as 'localhost' # after pagebench basebackup --page-service-connstring 'postgresql://postgres@localhost:6400'
Defensive patterns
Strategy: validation
Validate before calling
const SUPPORTED: [&str; 3] = ["postgresql", "postgres", "grpc"];
fn resolve_scheme(connstring: &str) -> Option<String> {
let scheme = match url::Url::parse(connstring) {
Ok(u) => u.scheme().to_lowercase(),
Err(url::ParseError::RelativeUrlWithoutBase) => "postgresql".to_string(),
Err(_) => return None,
};
SUPPORTED.contains(&scheme.as_str()).then_some(scheme)
} Type guard
fn is_supported_scheme(connstring: &str) -> bool {
resolve_scheme(connstring).is_some()
} Try / catch
match scheme.as_str() {
"postgresql" | "postgres" => build_libpq_client().await,
"grpc" => build_grpc_client().await,
other => {
eprintln!("scheme '{other}' not supported; use postgresql:// or grpc://");
Err(anyhow!("invalid scheme {other}"))
}
} Prevention
- Never pass a bare host:port; always include the scheme
- Keep page_service and management-API URLs in separate, clearly named variables
- Fail fast on scheme validation before spawning worker tasks
When it happens
Trigger: Passing a bare host:port whose host name is a valid scheme token (localhost:6400, myhost:7000); using http://, https://, or tcp:// URLs copied from the management API; scheme typos such as postgressql:// or pg://.
Common situations: Forgetting the postgresql:// prefix; copy-pasting the HTTP management endpoint instead of the page_service endpoint; trying the experimental gRPC transport before enabling its listener on the pageserver.
Related errors
- invalid connstring: {err}
- Supply either config file path or --config=inline-config
- must specify exactly one target
- pageserver has less than limit_to_first_n_targets={limit} te
- pageserver connection information should be provided
AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16).
Data as JSON: /api/errors/e9e91566385ff21e.
Report an issue: GitHub.