Billionmail/BillionMail · error

Failed to create directory: {}

Error message

Failed to create directory: {}

What it means

This error is thrown in ApplySSLWithExistingServer when os.MkdirAll fails to create the savePath directory that will hold the issued certificate files. It wraps the underlying OS error (permission denied, path is a file, parent missing on read-only FS, etc.) via LangCtx so it is localized before being returned to callers like Apply or StartRenew.

Source

Thrown at core/internal/service/acme/acme.go:474

	// Submit application
	request := certificate.ObtainRequest{
		Domains: domains,
		Bundle:  true,
	}

	// Get certificate
	certificates, err := client.Certificate.Obtain(request)
	if err != nil {
		return "", "", errors.New(public.LangCtx(ctx, "Failed to apply for SSL certificate: {}", err.Error()))
	}

	// Save certificate files if path is provided
	if savePath != "" {
		// Create directory if it doesn't exist
		if !public.FileExists(savePath) {
			err = os.MkdirAll(savePath, 0750)
			if err != nil {
				return "", "", errors.New(public.LangCtx(ctx, "Failed to create directory: {}", err.Error()))
			}
		}

		// Save certificate and private key files
		certificateFile := filepath.Join(savePath, "certificate.pem")
		privateKeyFile := filepath.Join(savePath, "private_key.pem")

		_, err = public.WriteFile(certificateFile, string(certificates.Certificate))
		if err != nil {
			return "", "", errors.New(public.LangCtx(ctx, "Failed to save certificate file: {}", err.Error()))
		}

		_, err = public.WriteFile(privateKeyFile, string(certificates.PrivateKey))
		if err != nil {
			return "", "", errors.New(public.LangCtx(ctx, "Failed to save private key file: {}", err.Error()))
		}
	}

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Check the wrapped OS error in the message (permission denied, not a directory, read-only file system) and fix the corresponding filesystem condition
  2. Ensure the parent directory of savePath exists and is writable by the process user (chown/chmod or mkdir -p manually)
  3. Verify savePath is a directory path, not a path to an existing file; rename or remove the conflicting file
  4. In Docker, mount the certificate volume read-write instead of read-only

Example fix

// before
savePath := "/etc/letsencrypt/live/example.com" // parent may not exist, mounted ro
_, _, err := svc.ApplySSLWithExistingServer(ctx, domains, keyType, cert, key, savePath)
// after
if err := os.MkdirAll("/etc/letsencrypt/live", 0750); err != nil { log.Fatal(err) }
_, _, err := svc.ApplySSLWithExistingServer(ctx, domains, keyType, cert, key, savePath)
Defensive patterns

Strategy: validation

Validate before calling

info, err := os.Stat(savePath)
if err == nil && !info.IsDir() { return fmt.Errorf("%s is a file, not a directory", savePath) }
if os.IsNotExist(err) {
    if err := os.MkdirAll(filepath.Dir(savePath), 0750); err != nil {
        return fmt.Errorf("cannot create parent of %s: %w", savePath, err)
    }
}
if err := unix.Access(filepath.Dir(savePath), unix.W_OK); err != nil { return err }

Try / catch

if _, _, err := svc.ApplySSLWithExistingServer(ctx, d, kt, c, k, savePath); err != nil {
    if strings.Contains(err.Error(), "mkdir") {
        log.Printf("cert dir issue for %s: %v", savePath, err)
        os.MkdirAll(savePath, 0750) // attempt recovery
    }
}

Prevention

When it happens

Trigger: Calling ApplySSLWithExistingServer with a non-empty savePath that does not exist, where os.MkdirAll(savePath, 0750) fails — e.g. parent directory is missing on a read-only filesystem, a file already exists at savePath, or the process lacks write permission on the parent.

Common situations: Deployments where the configured cert save path points into a volume not mounted or mounted read-only; savePath accidentally set to an existing file; container running as non-root user without ownership of /etc/letsencrypt-like directories.

Understand the failure class

Background: mkdir permission denied (EACCES): failed to create directory errors explained — this error's family across 32 libraries.

Related errors


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/053b0feba2a6a122. Report an issue: GitHub.