halo-dev/halo · critical · InvalidConfigurationPropertyValueException

Private key location does not exist

Error message

Private key location does not exist

What it means

Thrown as InvalidConfigurationPropertyValueException from JwtProperties.readPrivateKey during bean construction when halo.security.oauth2.jwt.private-key-location points at a Resource whose exists()==false. The private key (PKCS#8 PEM) is required to sign issued JWTs, so startup cannot proceed.

Source

Thrown at application/src/main/java/run/halo/app/infra/properties/JwtProperties.java:119

    private RSAPublicKey readPublicKey() throws IOException {
        String key = "halo.security.oauth2.jwt.public-key-location";
        Assert.notNull(this.publicKeyLocation, "PublicKeyLocation must not be null");
        if (!this.publicKeyLocation.exists()) {
            throw new InvalidConfigurationPropertyValueException(
                    key, this.publicKeyLocation, "Public key location does not exist");
        }
        try (InputStream inputStream = this.publicKeyLocation.getInputStream()) {
            String source = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
            return RsaKeyConverters.x509().convert(new ByteArrayInputStream(source.getBytes()));
        }
    }

    private RSAPrivateKey readPrivateKey() throws IOException {
        String key = "halo.security.oauth2.jwt.private-key-location";
        Assert.notNull(this.privateKeyLocation, "PrivateKeyLocation must not be null");
        if (!this.privateKeyLocation.exists()) {
            throw new InvalidConfigurationPropertyValueException(
                    key, this.privateKeyLocation, "Private key location does not exist");
        }
        try (InputStream inputStream = this.privateKeyLocation.getInputStream()) {
            String source = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
            return RsaKeyConverters.pkcs8().convert(new ByteArrayInputStream(source.getBytes()));
        }
    }
}

View on GitHub (pinned to d2f5165f9c)

Solutions

  1. Correct halo.security.oauth2.jwt.private-key-location to the actual resource location.
  2. Mount/copy the PKCS#8 PEM private key into the runtime at the configured path.
  3. Use classpath: for packaged keys or file: for mounted secrets.
  4. Confirm the file is in PKCS#8 DER/PEM form and readable by the process.

Example fix

# before
halo.security.oauth2.jwt.private-key-location=file:./halo.pem

# after
halo.security.oauth2.jwt.private-key-location=file:/etc/halo/keys/jwt-private.pem
Defensive patterns

Strategy: validation

Validate before calling

Resource r = resourceLoader.getResource(privateKeyLocation);
if (!r.exists()) {
    throw new IllegalStateException("Private key resource missing: " + privateKeyLocation);
}

Type guard

static boolean resourceExists(Resource r) {
    return r != null && r.exists();
}

Try / catch

try {
    new JwtProperties(issuer, algo, pubRes, privRes);
} catch (InvalidConfigurationPropertyValueException e) {
    log.error("Configure halo.security.oauth2.jwt.private-key-location to an existing PKCS#8 resource", e);
    throw e;
}

Prevention

When it happens

Trigger: Startup with the private-key property set to a non-existent Resource: wrong path, unmounted volume, missing classpath entry, or a file not deployed to the container.

Common situations: Container deployment missing the mounted secrets volume; typo in path; key generated elsewhere and not copied; environment-specific path differences; file git-ignored from the build.

Related errors


AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14). Data as JSON: /api/errors/7b96d56390154e3e. Report an issue: GitHub.