Secrets, TLS and Hardening

This section documents Quarkus 3.39.x (current) with 3.33.x LTS as the production recommendation, on the Java 17+ baseline, as published at the official Quarkus guides — which are the reference these pages are written and verified against.

Quarkus 4 raises the baseline to Java 21 (first release candidate planned for end of September 2026, see the Quarkus blog); readers upgrading past 3.39.x/3.33.x LTS should expect that change.

This content was generated with the assistance of AI and should be verified against those official guides before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

The previous two pages cover authenticating and authorizing a request; this one covers the configuration hygiene and transport-security layer that a production Quarkus service sits on regardless of which authentication mechanism it uses — keeping credentials out of plain configuration files, the TLS registry that terminates and re-establishes encrypted connections, and a checklist of the hardening steps a service should walk through before going to production.

Keeping secrets out of application.properties

Encrypted config values

SmallRye Config supports encrypting an individual property value in place, so a checked-in application.properties can carry a database password without carrying it in plain text:

smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key=${ENCRYPTION_KEY}
quarkus.datasource.password=${aes-gcm-nopadding::wJ2xg3...encrypted-value...==}

The ${aes-gcm-nopadding::…​} expression is decrypted at config-resolution time using the key supplied separately (an environment variable, never checked in alongside the encrypted value itself) — the encrypted value is safe to commit, but the decryption key must be managed with the same care as any other secret, since possessing it is equivalent to possessing every value it protects.

The credentials provider SPI

io.quarkus.credentials.CredentialsProvider is the extension point that lets quarkus.datasource.credentials- provider, quarkus.tls.*.key-store.credentials-provider, and similar properties resolve a username/password or keystore passphrase from an external secret store at runtime, instead of from any local configuration file at all:

quarkus.datasource.credentials-provider=vault
quarkus.datasource.credentials-provider-name=db-credentials

Quarkiverse ships ready-made providers rather than requiring a hand-written SPI implementation for the common cases — quarkus-vault for HashiCorp Vault (dynamic, leased database credentials, KV secret reads, and a CredentialsProvider implementation registered under a configurable name) and quarkus-vault-kubernetes / cloud-specific Quarkiverse extensions for a provider’s own managed secret store. Only when none of those fit does implementing CredentialsProvider directly make sense.

Kubernetes Secrets and ConfigMaps via quarkus-kubernetes-config

quarkus-kubernetes-config reads Kubernetes Secret and ConfigMap resources directly from the Kubernetes API at startup, merging their keys into the normal MicroProfile Config source ladder (see Configuration) rather than requiring them to be mounted as files or projected as environment variables by the Pod spec:

./mvnw quarkus:add-extension -Dextensions=kubernetes-config
quarkus.kubernetes-config.enabled=true
quarkus.kubernetes-config.secrets=db-credentials
quarkus.kubernetes-config.config-maps=app-config
quarkus.kubernetes-config.fail-on-missing-config=true

This still requires the running Pod’s ServiceAccount to be RBAC-authorized to get those specific Secret/ ConfigMap resources; it is a convenience for how configuration reaches the application’s config sources, not a replacement for Kubernetes' own RBAC boundary around who can read a Secret in the first place.

The TLS registry

Named TLS configurations

quarkus.tls. (the *TLS registry) centralizes every TLS configuration a Quarkus application uses — the HTTP endpoint’s own server certificate, an outbound REST Client’s trust store, a Kafka client’s mutual-TLS setup — under one consistent property namespace, with the unnamed configuration used by HTTP by default and any number of additional named configurations for other components:

quarkus.tls.key-store.p12.path=server-keystore.p12
quarkus.tls.key-store.p12.password=${keystore-password}

quarkus.tls.kafka-client.trust-store.pem.certs=kafka-ca.crt
quarkus.tls.kafka-client.key-store.p12.path=kafka-client.p12
quarkus.tls.kafka-client.key-store.p12.password=${kafka-keystore-password}

A component that supports the registry (the HTTP layer, the REST Client, the reactive messaging Kafka connector, and others) references a named configuration by its tls-configuration-name property (quarkus.rest-client."MyClient".tls-configuration-name=kafka-client, for example) instead of duplicating its own separate key/trust-store properties — one certificate rotation or trust-store update then applies everywhere that configuration is referenced.

Key stores, trust stores, and reloading

PEM, PKCS#12, and JKS formats are all supported for both key stores (this application’s own certificate and private key, presented to peers) and trust stores (the CA certificates used to verify a peer’s certificate):

quarkus.tls.key-store.pem.cert=server.crt
quarkus.tls.key-store.pem.key=server.key
quarkus.tls.trust-store.pem.certs=ca.crt
quarkus.tls.reload-period=1h

quarkus.tls.reload-period (or quarkus.tls."<name>".reload-period for a named configuration) makes the registry re-read the underlying key/trust-store files on that interval and swap in a renewed certificate — without a process restart — the mechanism that makes short-lived certificates issued by an internal CA or a tool like cert-manager practical to run continuously.

Post-quantum key-exchange options (3.39)

Quarkus 3.39 exposes hybrid post-quantum key-exchange groups for the TLS handshake — pairing a classical elliptic-curve exchange with a post-quantum algorithm (such as X25519MLKEM768, combining X25519 with ML-KEM) so that the connection remains secure against a classical attacker even if the post-quantum component were ever found to be flawed, while also guarding against a future quantum-capable attacker recording today’s traffic for later decryption:

quarkus.tls.key-store.p12.path=server-keystore.p12
quarkus.tls.key-store.p12.password=${keystore-password}
quarkus.tls.ssl-engine-options.enable-hybrid-key-exchange=true

Availability depends on the underlying JDK’s own TLS provider support for the named groups — this is a fast-moving area of the JDK and Quarkus’s TLS registry, so the exact property name and supported group list should always be checked against the current TLS registry reference rather than assumed to be stable across patch releases.

SSL in native executables

A native executable built with GraalVM/Mandrel does not include a JVM’s full set of TLS provider registrations by default — SSL support has to be requested explicitly at build time so the native-image build includes the necessary security providers and reachable code paths:

quarkus.native.enable-all-security-services=true
quarkus.ssl.native=true

Once TLS is used anywhere in the application (an HTTPS quarkus.http.ssl-port, a TLS-secured REST Client, quarkus.tls.* above), Quarkus’s own build-time analysis typically detects that and turns quarkus.ssl.native on automatically; setting it explicitly matters mainly for a case the analysis cannot see statically — a dynamically-selected TLS-using client, or an extension outside Quarkus’s own build-step visibility. Building without either property when TLS is actually needed at runtime produces a native executable that fails at the point of the first TLS handshake, not at build time, which is why this is worth checking explicitly rather than discovering after packaging.

Hardening checklist

A short, non-exhaustive checklist to work through before a Quarkus service reaches production, each item pointing back to where it is covered in depth:

  • Management interface separation — run /q/health, /q/metrics, and the Dev UI on a separate management port/interface (quarkus.management.enabled=true, quarkus.management.port=9000) rather than the main application port, so operational endpoints are not reachable from the same network path as application traffic; see Developer Experience for the management interface and Dev UI in general.

  • CORS — an explicit quarkus.http.cors.origins allow-list in every non-development profile; never rely on the permissive reflect-the-origin default in production (see the CORS and CSRF section of Security Overview and Architecture).

  • CSRF — quarkus-csrf-reactive (or an equivalent) on every session-cookie-authenticated, browser-facing endpoint that performs a state-changing request.

  • Security headers — Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options/frame- ancestors, and a Content-Security-Policy appropriate to the application, set globally via a jakarta.ws.rs.container.ContainerResponseFilter or a Vert.x route filter rather than left to each resource method individually.

  • Dependency scanning — a scheduled scan of the resolved dependency tree (OWASP Dependency-Check, Snyk, GitHub Dependabot/code scanning) against known CVEs, independent of and in addition to Quarkus’s own security advisories (see the vulnerability-reporting section of Security Overview and Architecture).

  • CycloneDX SBOMs — quarkus-maven-plugin’s build already produces a CycloneDX Software Bill of Materials for the application (`target/-cyclonedx.json with the quarkus.security.sbom./Maven CycloneDX plugin configuration enabled); feeding that SBOM into a scanner or an internal inventory system is what actually turns it into a hardening control, rather than a build artifact that nobody reads.