hashicorp/terraform · error

can not read private key from: '%s', Error: %q

Error message

can not read private key from: '%s', Error: %q

What it means

Returned by PrivateRSAKey() when os.ReadFile fails to read the PEM file at private_key_path. The backend resolves the path via expandPath, attempts to read the file, and wraps the underlying I/O error. The file must contain a PEM-encoded RSA private key.

Source

Thrown at internal/backend/remote-state/oci/auth.go:147

	fingerprint, err := p.KeyFingerprint()
	if err != nil {
		return "", err
	}
	return fmt.Sprintf("%s/%s/%s", tenancy, user, fingerprint), nil
}

func (p ociAuthConfigProvider) PrivateRSAKey() (key *rsa.PrivateKey, err error) {

	if p.privateKey != "" {
		keyData := strings.ReplaceAll(p.privateKey, "\\n", "\n") // Ensure \n is replaced by actual newlines
		return common.PrivateKeyFromBytesWithPassword([]byte(keyData), []byte(p.privateKeyPassword))
	}

	if p.privateKeyPath != "" {
		resolvedPath := expandPath(p.privateKeyPath)
		pemFileContent, readFileErr := os.ReadFile(resolvedPath)
		if readFileErr != nil {
			return nil, fmt.Errorf("can not read private key from: '%s', Error: %q", p.privateKeyPath, readFileErr)
		}
		return common.PrivateKeyFromBytesWithPassword(pemFileContent, []byte(p.privateKeyPassword))
	}

	return nil, fmt.Errorf("can not get private_key or private_key_path from Terraform configuration")
}

func (p ociAuthConfigProvider) getConfigProviders() ([]common.ConfigurationProvider, error) {
	var configProviders []common.ConfigurationProvider
	logger := logWithOperation("AuthConfigProvider")
	logger.Debug(fmt.Sprintf("Using %s authentication", p.authType))
	switch strings.ToLower(p.authType) {
	case strings.ToLower(AuthAPIKeySetting):
		// No additional config providers needed
	case strings.ToLower(AuthInstancePrincipalSetting):

		logger.Info("Attempting to authenticate using instance principal credentials")
		if p.region == "" {

View on GitHub (pinned to d32a084675)

Solutions

  1. Use an absolute path for private_key_path to avoid working-directory ambiguity.
  2. Verify the file exists and is readable: ls -la /full/path/to/key.pem; chmod 600 if needed.
  3. If running in a container or CI, ensure the key file is mounted/copied into the environment and the path matches.
  4. Switch to the inline 'private_key' attribute (with \n-escaped PEM) if file access is unreliable in the environment.
  5. Check that expandPath resolves ~ correctly — if unsure, replace ~ with the full home directory path.

Example fix

// before
backend "oci" {
  private_key_path = "~/keys/oci_key.pem"
}

// after
backend "oci" {
  private_key_path = "/home/user/keys/oci_key.pem"
}

// or inline:
backend "oci" {
  private_key = "-----BEGIN RSA PRIVATE KEY-----\nMIIE...\n-----END RSA PRIVATE KEY-----"
}
Defensive patterns

Strategy: validation

Validate before calling

func validatePrivateKeyPath(path string) error {
    if path == "" {
        return nil // handled by other validation
    }
    abs, err := filepath.Abs(expandPath(path))
    if err != nil {
        return fmt.Errorf("cannot resolve private_key_path: %w", err)
    }
    info, err := os.Stat(abs)
    if err != nil {
        return fmt.Errorf("private_key_path not accessible: %w", err)
    }
    if info.IsDir() {
        return fmt.Errorf("private_key_path is a directory, not a file")
    }
    if info.Mode().Perm()&0400 == 0 {
        return fmt.Errorf("private_key_path is not readable")
    }
    return nil
}

Try / catch

// Validate key file before terraform init:
if err := validatePrivateKeyPath(cfg.PrivateKeyPath); err != nil {
    log.Fatal(err)
}

Prevention

When it happens

Trigger: Backend block has 'private_key_path' set but the file does not exist, is not readable (permission denied), or the path is wrong (relative path resolves to an unexpected directory, ~ not expanded, wrong working directory).

Common situations: Path uses ~ but expandPath doesn't resolve it in the user's context; path is relative to a different working directory than where terraform runs; file permission is too restrictive; path was correct in CI but wrong locally; typo in the path string; file is outside the container/volume mount in containerized runs.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/be28f173b497cc8d. Report an issue: GitHub.