neondatabase/neon · error

path is neither a directory or a file

Error message

path is neither a directory or a file

What it means

JwtAuth::from_key_path stats the configured path; if it exists but is neither a regular file nor a directory (character device, FIFO, socket, block device), it bails with this message. Ed25519 decoding keys can only be loaded from a single PEM file or a directory of PEM files.

Source

Thrown at libs/utils/src/auth.rs:168

    pub fn from_key_path(key_path: &Utf8Path) -> Result<Self> {
        let metadata = key_path.metadata()?;
        let decoding_keys = if metadata.is_dir() {
            let mut keys = Vec::new();
            for entry in fs::read_dir(key_path)? {
                let path = entry?.path();
                if !path.is_file() {
                    // Ignore directories (don't recurse)
                    continue;
                }
                let public_key = fs::read(path)?;
                keys.push(DecodingKey::from_ed_pem(&public_key)?);
            }
            keys
        } else if metadata.is_file() {
            let public_key = fs::read(key_path)?;
            vec![DecodingKey::from_ed_pem(&public_key)?]
        } else {
            anyhow::bail!("path is neither a directory or a file")
        };
        if decoding_keys.is_empty() {
            anyhow::bail!(
                "Configured for JWT auth with zero decoding keys. All JWT gated requests would be rejected."
            );
        }
        Ok(Self::new(decoding_keys))
    }

    pub fn from_key(key: String) -> Result<Self> {
        Ok(Self::new(vec![DecodingKey::from_ed_pem(key.as_bytes())?]))
    }

    /// Attempt to decode the token with the internal decoding keys.
    ///
    /// The function tries the stored decoding keys in succession,
    /// and returns the first yielding a successful result.
    /// If there is no working decoding key, it returns the last error.

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Point the config at a real Ed25519 public key PEM file or a directory of .pem files
  2. Generate a keypair if needed: openssl genpkey -algorithm ed25519, then publish the public part with openssl pkey -pubout
  3. Re-check the resolved path for typos or incomplete template expansion

Example fix

# before
--public-key-path=/dev/null
# after
openssl pkey -in ed25519.pem -pubout -out /etc/neon/public.pem
--public-key-path=/etc/neon/public.pem
Defensive patterns

Strategy: validation

Validate before calling

fn is_loadable_key_path(p: &camino::Utf8Path) -> bool {
    match p.metadata() {
        Ok(m) => m.is_file() || m.is_dir(),
        Err(_) => false,
    }
}

// run before JwtAuth::from_key_path
anyhow::ensure!(is_loadable_key_path(&key_path), "key path must be a regular file or directory");

Type guard

fn is_bad_key_path_error(err: &anyhow::Error) -> bool {
    err.to_string().contains("neither a directory or a file")
}

Try / catch

match JwtAuth::from_key_path(&key_path) {
    Err(e) if e.to_string().contains("neither a directory or a file") => {
        eprintln!("auth key path {key_path} is not a regular file or directory");
        std::process::exit(1);
    }
    other => other?,
}

Prevention

When it happens

Trigger: Configuring the JWT public key path (e.g. a safekeeper/pageserver auth config pointing at --public-key-path) with a special file such as /dev/null, a unix socket, or a named pipe.

Common situations: Using /dev/null to 'disable' auth in scripts; config templating resolving to a device node; Docker bind-mounting a socket over the key path.

Related errors


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