EJBCA PKI with Systemd Quadlets (containers)

Learn how to setup an EJBCA PKI using Systemd quadlets.

With a fellow system administrators, I have spent in 2025 quite some thought on a nice PKI setup with EJBCA (community edition). Personally, I do not like EJBCA too much. It feels like an old monolithic java application with a slightly refreshed web GUI that consumes way too much resources for what it does. On top, the company backing EJBCA has not published the build scripts for their Docker container. Previously, Bitnami had an opensource build of EJBCA, but due to the Bitnami policy changes, this option is gone. I find the configuration of the EJBCA docker container is not well documented. Nevertheless, it is convenient for organisations that require some GUI for end-users to extend their certificates.

In the following, I just paste some of our configuration files to inspire others.

Setup

For this to work, you need a server with systemd and podman in a version that supports quadlets. Fedora, CoreOS, opensuse, RHEL, AlmaLinux should all support this. I tested with Fedora.

Then, the setup works without root. I assume you have specific user ejbca with $HOME=/home/ejbca. To ensure that systemd user services are launched at boot, you need to enable lingering:

sudo loginctl enable-linger ejbca

Then, copy the following files into /home/ejbca to have systemd manage two podman containers for EJBCA and its database, network configuration and backups. Make sure to put your passwords/keys in the .env file.

# file: "~/EJBCA/.env"
###################
## Generate keys:
# LC_ALL=C tr -dc '[:alnum:]' < /dev/urandom | head -c 32; echo

###################
## DB:
MYSQL_ROOT_PASSWORD=
MYSQL_DATABASE=ejbca
MYSQL_USER=ejbca
MYSQL_PASSWORD=

###################
## EJBCA Server:
DATABASE_JDBC_URL=jdbc:mariadb://ejbca-db:3306/ejbca?characterEncoding=UTF-8
DATABASE_USER=ejbca
DATABASE_PASSWORD=

## https://doc.primekey.com/ejbca-saas/ejbca-saas-configuration-guide/managing-truststores-and-keystores
# EJBCA does not work with these env variables set...
#APPSERVER_KEYSTORE_SECRET=
#APPSERVER_TRUSTSTORE_SECRET=

## https://hub.docker.com/r/keyfactor/ejbca-ce
PASSWORD_ENCRYPTION_KEY=
CA_KEYSTOREPASS=
EJBCA_CLI_DEFAULTPASSWORD=

LOG_LEVEL_APP=INFO
LOG_LEVEL_SERVER=INFO

## posible values: simple true later
TLS_SETUP_ENABLED=true

## SMTP config
SMTP_DESTINATION=host.containers.internal
#SMTP_DESTINATION_PORT=25
SMTP_FROM=home-lab@ejbca.localhost
SMTP_TLS_ENABLED=false
SMTP_SSL_ENABLED=false
## auth with arbitrary credentials: https://github.com/mailhog/MailHog/issues/266
#SMTP_USERNAME=user
#SMTP_PASSWORD=password

###################
# Unsorted:
COMPOSE_PROJECT_NAME=ejbca
# file: "~/.config/containers/systemd/new-access-bridge.network"
[Network]
Driver=bridge
# file: "~/.config/containers/systemd/new-application-bridge.network"
[Network]
Driver=bridge
# file: "~/.config/containers/systemd/ejbca-db.container"
[Container]
ContainerName=ejbca-db
EnvironmentFile=/home/ejbca/EJBCA/.env
HealthCmd=["healthcheck.sh","--connect","--innodb_initialized"]
HealthInterval=10s
HealthRetries=3
HealthStartPeriod=10s
HealthTimeout=5s
Image=docker.io/library/mariadb:11.7.2-ubi9
Network=new-application-bridge.network
SecurityLabelDisable=true
UserNS=keep-id:uid=999,gid=999
Volume=/home/ejbca/EJBCA/datadbdir:/var/lib/mysql:z,rw
Timezone=Europe/Brussels
# file: "~/.config/containers/systemd/ejbca-server.container"
[Unit]
Requires=ejbca-db.service
# Make sure that the DB starts first
# Make sure that the user session service is started before the container unit
After=ejbca-db.service systemd-user-sessions.service

[Container]
ContainerName=ejbca-server
EnvironmentFile=/home/ejbca/EJBCA/.env
HealthCmd=["curl","-f","http://127.0.0.1:8090/health"]
HealthInterval=5m
HealthRetries=3
HealthStartPeriod=10s
########################################
# Change hostname if necessary!
HostName=ejbca.localhost
########################################
Image=keyfactor/ejbca-ce:9.1.1
Network=new-access-bridge.network
Network=new-application-bridge.network
PublishPort=8080:8080
PublishPort=8443:8443
PublishPort=8442:8442
UserNS=keep-id:uid=10001,gid=10001
Volume=/home/ejbca/EJBCA/datadir:/mnt/persistent:z
Volume=/home/ejbca/EJBCA/dataconf/conf:/opt/keyfactor/ejbca/conf:z
Timezone=Europe/Brussels

[Install]
# Start by default on boot
WantedBy=multi-user.target default.target

The EJBCA logs will be managed by journald. You can find them with:

journalctl -f

Backups

The following files create daily database dumps and delete dumps older than 100 days.

# file: "~/.config/systemd/user/ejbca-db-dump.service"
[Unit]
Description=Daily MariaDB Dump
Requires=ejbca-db.service

[Service]
EnvironmentFile=/home/ejbca/EJBCA/.env
Type=oneshot
Environment=RETAIN_FILES_COUNT=100
ExecStart=/bin/sh -c 'podman exec -u 999 ejbca-db /usr/bin/mariadb-dump -u ejbca -p${MYSQL_PASSWORD} ejbca | gzip > /home/ejbca/Backups/mariadb_dump_$(date +%%FT%%T).sql.gz'
ExecStartPost=/bin/sh -c 'find /home/ejbca/Backups -type f -name "mariadb_dump*.gz" -print0 | sort -z | head -zn -${RETAIN_FILES_COUNT} | xargs -0r rm'
# file: "~/.config/systemd/user/ejbca-db-dump.timer"
[Unit]
Description=Timer for Daily MariaDB Dump

[Timer]
OnCalendar=*-*-* 12:33:00 Europe/Brussels
Persistent=true
Unit=ejbca-db-dump.service

[Install]
WantedBy=timers.target

You can check the execuation schedule with:

systemctl --user list-timers

References