quarkusio/quarkus · critical · SecurityException

Failed to set private key file writable by owner only. This

Error message

Failed to set private key file writable by owner only. This is a critical security requirement to protect the private key.

What it means

adjustPermissions() finishes by restricting write access to the file owner only with File.setWritable(true, true). If the OS cannot apply owner-only write permission, a SecurityException is thrown because the key must never be writable by group/others. Like the read check, it fails closed to protect key material.

Source

Thrown at extensions/tls-registry/cli/src/main/java/io/quarkus/tls/cli/letsencrypt/LetsEncryptHelpers.java:329

        // Private key MUST be owner-only readable/writable (chmod 600)
        if (!keyFile.setReadable(false, false)) { // Remove group/world read
            LOGGER.warnf("Failed to set key file readable only by the owner: %s", keyFile.getAbsolutePath());
        }
        if (!keyFile.setWritable(false, false)) { // Remove group/world write
            LOGGER.warnf("Failed to set key file writable only by the owner : %s", keyFile.getAbsolutePath());
        }
        if (!keyFile.setExecutable(false, false)) { // Remove group/world execute
            LOGGER.warnf("Failed to set key file executable by owner only: %s", keyFile.getAbsolutePath());
        }

        // Then set owner-only permissions
        if (!keyFile.setReadable(true, true)) { // Owner-only read
            throw new SecurityException("Failed to set private key file readable by owner only. " +
                    "This is a critical security requirement to protect the private key.");
        }
        if (!keyFile.setWritable(true, true)) { // Owner-only write
            throw new SecurityException("Failed to set private key file writable by owner only. " +
                    "This is a critical security requirement to protect the private key.");
        }

        AUDIT.debug("Set secure permissions on private key file: " + keyFile.getAbsolutePath() + " (owner-only: rw-------)");
        LOGGER.debug("Set secure permissions on private key file (owner-only: rw-------)");
    }
}

View on GitHub (pinned to e1c734241f)

Solutions

  1. Ensure the letsencrypt directory is on a writable, POSIX-capable filesystem owned by the running user
  2. chown/chmod the file and directory so the current user can modify permissions, then rerun
  3. Remount the volume read-write if it was mounted ro
  4. Avoid storing keys on Windows/network shares; relocate to a local directory

Example fix

// before
docker run -v /etc/letsencrypt:/letsencrypt:ro ...
// after
docker run -v /etc/letsencrypt:/letsencrypt ...
# then in container: chown -R $(id -u) /letsencrypt
Defensive patterns

Strategy: validation

Validate before calling

static boolean isWritableLocationForOwnerOnly(File dir) {
    try {
        File probe = File.createTempFile("permcheck", ".tmp", dir);
        boolean ok = probe.setWritable(true, true) && probe.delete();
        return ok;
    } catch (IOException e) { return false; }
}

Type guard

if (!dir.canWrite() || Files.isSymbolicLink(dir.toPath()) && !isLocalPosixFs(dir.toPath())) { chooseDifferentDirectory(); }

Try / catch

try {
    LetsEncryptHelpers.adjustPermissions(keyFile);
} catch (SecurityException e) {
    LOGGER.error("Key file remains group/world-writable; aborting to protect key", e);
    throw new IllegalStateException("Insecure key file location: " + keyFile.getParent(), e);
}

Prevention

When it happens

Trigger: keyFile.setWritable(true, true) returns false — the filesystem does not support POSIX write bits (Windows, FAT, some mounts), the process user does not own the file, the file is read-only at the mount level, or a SecurityManager blocks the change.

Common situations: Key stored on a read-only or immutable mount, running inside a container with a volume mounted read-only, Windows host filesystem, or the key file owned by root while the CLI runs as an unprivileged user.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/afc96156bc796ea3. Report an issue: GitHub.