July 10, 2025

systemd Credentials in Spring Boot

systemd, Linux’s most widely used system and service manager, introduced system and service credentials with version 247. They allow for securely passing passwords and keys to applications, thereby solving a longstanding problem. Let’s find out how we can access them in Spring Boot.

Introducing systemd Credentials

In their simplest form, systemd credentials are straightforward:

[Service]
ExecStart=/path/to/my-program
LoadCredential=my-secret:/etc/my-program/credentials/some-password

In this unit file, we tell systemd to start my-program and to expose an unencrypted credential to it. The credential is stored in the file /etc/my-program/credentials/some-password.

Now, how can my-program access it? Not by opening /etc/my-program/credentials/some-password. Instead, systemd will prepare a special directory1. There, it will place the credential in a file named my-secret. systemd passes my-program the path to the special directory using the environment variable CREDENTIALS_DIRECTORY. If my-program were a shell script, it could read the secret by opening "$CREDENTIALS_DIRECTORY/my-secret".

There are many more options. It is not only possible to encrypt secrets, but even to bind them to the Trusted Platform Module of a computer. For details, please consult systemd’s documentation.

Now that we know how they work, the only thing left to do is to figure out how we can get Spring Boot to read systemd credentials from files in CREDENTIALS_DIRECTORY.

Externalized Configuration in Spring Boot

In Spring Boot, configuration is declared as properties like spring.datasource.url. Those properties are not only used to configure Spring Boot itself. You can define your own properties to make your application configurable through the very same system. Spring Boot can obtain configuration values from a multitude of sources, for example, .properties, the environment, JNDI or Kubernetes ConfigMaps.

In addition to properties, Spring Boot has dedicated SSL bundles to load keys and certificates from files.

Populating SSL Bundles From systemd Credentials

I start with the simplest case: populating SSL bundles from systemd credentials.

Without systemd credentials, the configuration to define an SSL bundle called mybundle to be used by the embedded web server looks as follows:

spring.ssl.bundle.pem.mybundle.keystore.certificate=file:/path/to/domain.crt
spring.ssl.bundle.pem.mybundle.keystore.private-key=file:/path/to/domain.key

To load the certificate and private key from a systemd credential, specify them in the unit file:

[Service]
ExecStart=/path/to/java -jar /path/to/demo-0.0.1-SNAPSHOT.jar 
LoadCredential=domain.key:/path/to/domain.key
LoadCredential=domain.crt:/path/to/domain.crt

ExecStart= defines the executable JAR to launch. The next two lines starting with LoadCredential= specify the private key (/path/to/domain.key) and certificate (/path/to/domain.crt) that will be made available to the application as domain.key and domain.crt respectively. The only step remaining is to tell Spring Boot to read both files from CREDENTIALS_DIRECTORY:

spring.ssl.bundle.pem.mybundle.keystore.certificate=file:${CREDENTIALS_DIRECTORY}/domain.crt
spring.ssl.bundle.pem.mybundle.keystore.private-key=file:${CREDENTIALS_DIRECTORY}/domain.key

Using systemd Credentials in Any Configuration Property

Now, most Spring Boot applications will probably want to access a database. And that database will most likely require a password, which can be configured with spring.datasource.password. Unfortunately, we cannot leverage our newly learnt knowledge and use the file: prefix to load the database password from a file because file: is only understood by SSL bundles. Instead, we have to repurpose a configuration tree.

A configuration tree turns files into Spring Boot properties by converting their file name into the property’s name and the file’s content into the property’s value:

[Service]
ExecStart=/path/to/java \
    -jar /path/to/demo-0.0.1-SNAPSHOT.jar \
    --spring.config.import=configtree:${CREDENTIALS_DIRECTORY}
LoadCredential=spring.datasource.password:/path/to/database-password

--spring.config.import is the magical argument that makes it all work. If you prefer, you can put spring.config.import=configtree:${CREDENTIALS_DIRECTORY} into your application.properties, instead.

If you find it too cumbersome to specify every secret individually, you can treat your entire configuration as a systemd credential and load it from CREDENTIALS_DIRECTORY:

[Service]
ExecStart=/path/to/java \
    -jar /path/to/demo-0.0.1-SNAPSHOT.jar \
    --spring.config.import=${CREDENTIALS_DIRECTORY}/application.properties
LoadCredential=application.properties:/path/to/application.properties

Credentials Do Not Belong in Environment Variables

While environment variables are handy for configuring an application, they have many drawbacks. Subprocesses inherit them, they are awkward to use with binary data, have size limitations, and can be accessed easily with various tools. ps auxe lists them and systemctl show <service> allows any user on a system to print them if they are defined in a unit file.


  1. To make it only accessible to my-program enable mount namespacing with PrivateMounts=↩︎