Installing Keycloak on Ubuntu Server 22.04

What is Keycloak?

Keycloak is a versatile open source identity and access management solution. You’ll see that it is often used as an external Identity Provider (IdP).

You can put Keycloak in front of your web application (for example iTop, Awingu / Parallels Secure Workspace, …), and users would authenticate to it. Once authenticated to this (most commonly shared with other applications) identity provider; you’d be redirected to your web application where you’re then signed in.

You can put it in front of another identity provider, as I’ve documented for my employer Alludo ( Integrating a third-party identity provider in front of Microsoft ADFS with Parallels Secure Workspace ). In that scenario, I used KeyCloak as a stand-in for an external Identity Provider where users would authenticate using their national identity cards. Then, the Microsoft Active Directory Federation Services (ADFS) would transform this (hash of a) national number into the user principle name (UPN) of a Microsoft Active Directory User.

You could also do it the other way around, and put for example Microsoft ADFS in front of KeyCloak.

Installation on a fresh Ubuntu Server 20.04 LTS

Let’s elevate first, so we can execute the entire installation with elevated rights.

sudo su -

Let’s make sure we have the latest info on available packages.

root@ubuntu01:~# apt-get update

Prerequisite

Check if Java is installed. The output should look like this:

# Check Java version.
root@ubuntu01:~# java -version
openjdk version "11.0.21" 2023-10-17
OpenJDK Runtime Environment (build 11.0.21+9-post-Ubuntu-0ubuntu122.04)
OpenJDK 64-Bit Server VM (build 11.0.21+9-post-Ubuntu-0ubuntu122.04, mixed mode, sharing)

If the above command didn’t output a version, it means Java may not be installed yet.

root@ubuntu01:~# apt-get install default-jdk -y

If the above command didn’t output a version, it means Java may not be installed yet. Try:

Installing Keycloak

Unfortunately, it’s not as straight-forward as just downloading a package. Check the latest available link at https://www.keycloak.org. We’ll download the distribution from there, to our own /opt folder.

root@ubuntu01:~# cd /opt
# Download the file.
root@ubuntu01:~# wget -O keycloak.tar.gz https://github.com/keycloak/keycloak/releases/download/23.0.4/keycloak-23.0.4.tar.gz

# Create directory /opt/keycloak.
root@ubuntu01:~# mkdir keycloak

# Unpack under /opt/keycloak.
# In this example, the .tar.gz actually contained a "keycloak-23.0.4" folder.
# The --strip-components makes sure to ignore this first (1) level.
root@ubuntu01:~# tar -xzvf keycloak.tar.gz keycloak --strip-components=1
# List the files and folders in /opt/keycloak.
# In this example, the directory will contain a subdirectory with the specific version:
# /opt/keycloak/
# It should show a LICENSE.TXT, README.MD, version.txt files; and some folders: bin, conf, lib, providers, themes.
root@ubuntu01:~# ls -l /opt/keycloak

# Cleanup
root@ubuntu01:~# rm keycloak.tar.gz

Securing Keycloak

It would be a bad idea security-wise to run Keycloak as a root user.
As a good practice, we’ll create a dedicated group and user for Keycloak.

# Create a group first.
root@ubuntu01:/opt# groupadd keycloak
# Create a dedicated user account.
# This user account will be part of the group keycloak.
# Its home directory will be set to /opt/keycloak.
# There will be no shell for this user.
# Its username is keycloak.
root@ubuntu01:/opt# useradd -r -g keycloak -d /opt/keycloak -s /sbin/nologin keycloak 

Additionally, let’s adjust the folder and file permissions.

# Our Keycloak user becomes the owner of the folder /opt/keycloak and everything in it (recursively).
root@ubuntu01:/opt# chown -R keycloak: keycloak
# Set permissions.
root@ubuntu01:/opt# chmod o+x /opt/keycloak/bin 

Configuring Keycloak as a service

If you’re familiar with some of the more common packages such as Apache, krb5, OpenSSL, … You’ll know that most of the time, you find configuration files under the /etc directory for these services. For Keycloak, we need to create this ourselves.

Turns out, my installation was so bare minimum that I still needed to install vim. This can be a bit of a complex text editor though for newcomers to Linux. You could of course use any text editor of your choice, such as nano.

# Install vim.
root@ubuntu01:/opt# apt install vim

The basic goal is to create a service configuration file.

# Start editing using vim.
root@ubuntu01:/opt# vim /etc/systemd/system/keycloak.service

The file contents should look like this:

[Unit]
Description=The Keycloak Server
After=syslog.target network.target

# Hint: If Keycloak must be started before other services,
# specify it like this:
# Before=apache2.service

[Service]
User=keycloak
Group=keycloak
LimitNOFILE=102642
PIDFile=/run/keycloak/keycloak.pid
ExecStart=/opt/keycloak/bin/kc.sh start --optimized
StandardOutput=null

[Install]
WantedBy=multi-user.target

Just as you are following this tutorial, I was also following some pointers on the Internet. For older versions, it seems. For example, to create the service file above, it mentioned copying the configuration file from a directory which is no longer present in the Keycloak 23 package.

I also initially configured a legacy/deprecated directory for the PIDFile. Hence, in the output I’ve pasted below, you’ll see the warning about /var/run not being correct.

# Reload daemon.
root@ubuntu01:/opt/keycloak# systemctl daemon-reload

# Check Keycloak status. It will likely be shown as "inactive (dead)".
root@ubuntu01:/opt/keycloak# systemctl status keycloak 
○ keycloak.service - The Keycloak Server
     Loaded: loaded (/etc/systemd/system/keycloak.service; disabled; vendor preset: enabled)
     Active: inactive (dead)

Jan 28 13:17:45 ubuntu01 systemd[1]: /etc/systemd/system/keycloak.service:13: PIDFile= references a path below legacy directory /var/run/, updating /var/run/keycloak/keycloak.pid → /run/keycloak/keycloak.pid; please update the unit file accordingly.

# Enable Keycloak.
root@ubuntu01:/opt/keycloak# systemctl enable keycloak

# Let's start the Keycloak service.
root@ubuntu01:/opt/keycloak# systemctl start keycloak

# And let's confirm the status after our start attempt.
root@ubuntu01:/opt/keycloak# systemctl status keycloak 
× keycloak.service - The Keycloak Server
     Loaded: loaded (/etc/systemd/system/keycloak.service; enabled; vendor preset: enabled)
     Active: failed (Result: exit-code) since Sun 2024-01-28 13:19:44 UTC; 6s ago
    Process: 6427 ExecStart=/opt/keycloak/bin/kc.sh start --optimized (code=exited, status=1/FAILURE)
   Main PID: 6427 (code=exited, status=1/FAILURE)
        CPU: 522ms

Jan 28 13:19:43 ubuntu01 systemd[1]: Started The Keycloak Server.
Jan 28 13:19:44 ubuntu01 systemd[1]: keycloak.service: Main process exited, code=exited, status=1/FAILURE
Jan 28 13:19:44 ubuntu01 systemd[1]: keycloak.service: Failed with result 'exit-code'.

Well, that doesn’t look too good. I left it in here, as others may be running into the same issue. And to show that behind every post, there is often also initial failure as well. Let’s troubleshoot this by executing the action manually.

root@ubuntu01:/opt/keycloak# /opt/keycloak/bin/kc.sh start --optimized 
Exception in thread "main" java.lang.UnsupportedClassVersionError: org/keycloak/quarkus/runtime/KeycloakMain has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 55.0
	at java.base/java.lang.ClassLoader.defineClass1(Native Method)
	at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1022)
	at io.quarkus.bootstrap.runner.RunnerClassLoader.loadClass(RunnerClassLoader.java:105)
	at io.quarkus.bootstrap.runner.RunnerClassLoader.loadClass(RunnerClassLoader.java:65)
	at io.quarkus.bootstrap.runner.QuarkusEntryPoint.doRun(QuarkusEntryPoint.java:60)
	at io.quarkus.bootstrap.runner.QuarkusEntryPoint.main(QuarkusEntryPoint.java:32)

Now this is more meaningful. We need a more up-to-date Java Runtime.

# Install OpenJDK 21 Java Runtime Environment (JRE)
root@ubuntu01:/opt/keycloak# apt install openjdk-21-jre 

# Confirm Java version.
root@ubuntu01:/opt/keycloak# java -version
openjdk version "21.0.1" 2023-10-17
OpenJDK Runtime Environment (build 21.0.1+12-Ubuntu-222.04)
OpenJDK 64-Bit Server VM (build 21.0.1+12-Ubuntu-222.04, mixed mode, sharing)

I tried again, but ran into the next error.

root@ubuntu01:/opt/keycloak# /opt/keycloak/bin/kc.sh start --optimized 
ERROR: Unexpected error when starting the server in (production) mode
ERROR: Failed to start quarkus
ERROR: Strict hostname resolution configured but no hostname setting provided
For more details run the same command passing the '--verbose' option. Also you can use '--help' to see the details about the usage of the particular command.

See what it tells us.

root@ubuntu01:/opt/keycloak# /opt/keycloak/bin/kc.sh start --help
Start the server.
Usage: 
kc.sh start [OPTIONS]
Use this command to run the server in production.
Options:
-h, --help           This help message.
--help-all           This same help message but with additional options.
--import-realm       Import realms during startup by reading any realm configuration file from the
'data/import' directory.
--optimized          Use this option to achieve an optimal startup time if you have previously
built a server image using the 'build' command.
Cache:
--cache <type>       Defines the cache mechanism for high-availability. By default in production
mode, a 'ispn' cache is used to create a cluster between multiple server
nodes. By default in development mode, a 'local' cache disables clustering
and is intended for development and testing purposes. Possible values are:
ispn, local. Default: ispn.
--cache-config-file <file>
Defines the file from which cache configuration should be loaded from. The
configuration file is relative to the 'conf/' directory.
--cache-stack <stack>
Define the default stack to use for cluster communication and node discovery.
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
Possible values are: tcp, udp, kubernetes, ec2, azure, google.
Database:
--db <vendor>        The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
mysql, oracle, postgres. Default: dev-file.
--db-driver <driver> The fully qualified class name of the JDBC driver. If not set, a default
driver is set accordingly to the chosen database.
--db-password <password>
The password of the database user.
--db-pool-initial-size <size>
The initial size of the connection pool.
--db-pool-max-size <size>
The maximum size of the connection pool. Default: 100.
--db-pool-min-size <size>
The minimal size of the connection pool.
--db-schema <schema> The database schema to be used.
--db-url <jdbc-url>  The full database JDBC URL. If not provided, a default URL is set based on the
selected database vendor. For instance, if using 'postgres', the default
JDBC URL would be 'jdbc:postgresql://localhost/keycloak'.
--db-url-database <dbname>
Sets the database name of the default JDBC URL of the chosen vendor. If the
`db-url` option is set, this option is ignored.
--db-url-host <hostname>
Sets the hostname of the default JDBC URL of the chosen vendor. If the
`db-url` option is set, this option is ignored.
--db-url-port <port> Sets the port of the default JDBC URL of the chosen vendor. If the `db-url`
option is set, this option is ignored.
--db-url-properties <properties>
Sets the properties of the default JDBC URL of the chosen vendor. Make sure to
set the properties accordingly to the format expected by the database
vendor, as well as appending the right character at the beginning of this
property value. If the `db-url` option is set, this option is ignored.
--db-username <username>
The username of the database user.
Transaction:
--transaction-xa-enabled <true|false>
If set to false, Keycloak uses a non-XA datasource in case the database does
not support XA transactions. Default: true.
Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage,
multi-site, par, preview, recovery-codes, scripts, step-up-authentication,
token-exchange, transient-users, update-email, web-authn.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation,
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage,
multi-site, par, preview, recovery-codes, scripts, step-up-authentication,
token-exchange, transient-users, update-email, web-authn.
Hostname:
--hostname <hostname>
Hostname for the Keycloak server.
--hostname-admin <hostname>
The hostname for accessing the administration console. Use this option if you
are exposing the administration console using a hostname other than the
value set to the 'hostname' option.
--hostname-admin-url <url>
Set the base URL for accessing the administration console, including scheme,
host, port and path
--hostname-debug <true|false>
Toggle the hostname debug page that is accessible at
/realms/master/hostname-debug Default: false.
--hostname-path <path>
This should be set if proxy uses a different context-path for Keycloak.
--hostname-port <port>
The port used by the proxy when exposing the hostname. Set this option if the
proxy uses a port other than the default HTTP and HTTPS ports. Default: -1.
--hostname-strict <true|false>
Disables dynamically resolving the hostname from request headers. Should
always be set to true in production, unless proxy verifies the Host header.
Default: true.
--hostname-strict-backchannel <true|false>
By default backchannel URLs are dynamically resolved from request headers to
allow internal and external applications. If all applications use the public
URL this option should be enabled. Default: false.
--hostname-url <url> Set the base URL for frontend URLs, including scheme, host, port and path.
HTTP/TLS:
--http-enabled <true|false>
Enables the HTTP listener. Default: false.
--http-host <host>   The used HTTP Host. Default: 0.0.0.0.
--http-max-queued-requests <requests>
Maximum number of queued HTTP requests. Use this to shed load in an overload
situation. Excess requests will return a "503 Server not Available" response.
--http-port <port>   The used HTTP port. Default: 8080.
--http-relative-path <path>
Set the path relative to '/' for serving resources. The path must start with a
'/'. Default: /.
--https-certificate-file <file>
The file path to a server certificate or certificate chain in PEM format.
--https-certificate-key-file <file>
The file path to a private key in PEM format.
--https-cipher-suites <ciphers>
The cipher suites to use. If none is given, a reasonable default is selected.
--https-client-auth <auth>
Configures the server to require/request client authentication. Possible
values are: none, request, required. Default: none.
--https-key-store-file <file>
The key store which holds the certificate information instead of specifying
separate files.
--https-key-store-password <password>
The password of the key store file. Default: password.
--https-key-store-type <type>
The type of the key store file. If not given, the type is automatically
detected based on the file name. If 'fips-mode' is set to 'strict' and no
value is set, it defaults to 'BCFKS'.
--https-port <port>  The used HTTPS port. Default: 8443.
--https-protocols <protocols>
The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2.
--https-trust-store-file <file>
The trust store which holds the certificate information of the certificates to
trust.
--https-trust-store-password <password>
The password of the trust store file.
--https-trust-store-type <type>
The type of the trust store file. If not given, the type is automatically
detected based on the file name. If 'fips-mode' is set to 'strict' and no
value is set, it defaults to 'BCFKS'.
Health:
--health-enabled <true|false>
If the server should expose health check endpoints. If enabled, health checks
are available at the '/health', '/health/ready' and '/health/live'
endpoints. Default: false.
Config:
--config-keystore <config-keystore>
Specifies a path to the KeyStore Configuration Source.
--config-keystore-password <config-keystore-password>
Specifies a password to the KeyStore Configuration Source.
--config-keystore-type <config-keystore-type>
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
Metrics:
--metrics-enabled <true|false>
If the server should expose metrics. If enabled, metrics are available at the
'/metrics' endpoint. Default: false.
Proxy:
--proxy <mode>       The proxy address forwarding mode if the server is behind a reverse proxy.
Possible values are: none, edge, reencrypt, passthrough. Default: none.
Vault:
--vault <provider>   Enables a vault provider. Possible values are: file, keystore.
--vault-dir <dir>    If set, secrets can be obtained by reading the content of files within the
given directory.
--vault-file <file>  Path to the keystore file.
--vault-pass <pass>  Password for the vault keystore.
--vault-type <type>  Specifies the type of the keystore file. Default: PKCS12.
Logging:
--log <handler>      Enable one or more log handlers in a comma-separated list. Possible values
are: console, file, gelf. Default: console.
--log-console-color <true|false>
Enable or disable colors when logging to console. Default: false.
--log-console-format <format>
The format of unstructured console log entries. If the format has spaces in
it, escape the value using "<format>". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} %
-5p [%c] (%t) %s%e%n.
--log-console-output <output>
Set the log output to JSON or default (plain) unstructured logging. Possible
values are: default, json. Default: default.
--log-file <file>    Set the log file path and filename. Default: data/log/keycloak.log.
--log-file-format <format>
Set a format specific to file log entries. Default: %d{yyyy-MM-dd HH:mm:ss,
SSS} %-5p [%c] (%t) %s%e%n.
--log-file-output <output>
Set the log output to JSON or default (plain) unstructured logging. Possible
values are: default, json. Default: default.
--log-gelf-facility <name>
The facility (name of the process) that sends the message. Default: keycloak.
--log-gelf-host <hostname>
Hostname of the Logstash or Graylog Host. By default UDP is used, prefix the
host with 'tcp:' to switch to TCP. Example: 'tcp:localhost' Default:
localhost.
--log-gelf-include-location <true|false>
Include source code location. Default: true.
--log-gelf-include-message-parameters <true|false>
Include message parameters from the log event. Default: true.
--log-gelf-include-stack-trace <true|false>
If set to true, occuring stack traces are included in the 'StackTrace' field
in the GELF output. Default: true.
--log-gelf-level <level>
The log level specifying which message levels will be logged by the GELF
logger. Message levels lower than this value will be discarded. Default:
INFO.
--log-gelf-max-message-size <size>
Maximum message size (in bytes). If the message size is exceeded, GELF will
submit the message in multiple chunks. Default: 8192.
--log-gelf-port <port>
The port the Logstash or Graylog Host is called on. Default: 12201.
--log-gelf-timestamp-format <pattern>
Set the format for the GELF timestamp field. Uses Java SimpleDateFormat
pattern. Default: yyyy-MM-dd HH:mm:ss,SSS.
--log-level <category:level>
The log level of the root category or a comma-separated list of individual
categories and their levels. For the root category, you don't need to
specify a category. Default: info.
Security:
--fips-mode <mode>   Sets the FIPS mode. If 'non-strict' is set, FIPS is enabled but on
non-approved mode. For full FIPS compliance, set 'strict' to run on approved
mode. This option defaults to 'disabled' when 'fips' feature is disabled,
which is by default. This option defaults to 'non-strict' when 'fips'
feature is enabled. Possible values are: non-strict, strict. Default:
disabled.
By default, this command tries to update the server configuration by running a
'build' before starting the server. You can disable this behavior by using the
'--optimized' option:
$ kc.sh start '--optimized'
By doing that, the server should start faster based on any previous
configuration you have set when manually running the 'build' command.

Let’s try and specify a hostname.

root@ubuntu01:/opt/keycloak# /opt/keycloak/bin/kc.sh start --hostname=ubuntu01
2024-01-28 13:52:48,149 INFO  [org.keycloak.quarkus.runtime.hostname.DefaultHostnameProvider] (main) Hostname settings: Base URL: <unset>, Hostname: ubuntu01, Strict HTTPS: true, Path: <request>, Strict BackChannel: false, Admin URL: <unset>, Admin: <request>, Port: -1, Proxied: false
2024-01-28 13:52:50,414 WARN  [io.quarkus.agroal.runtime.DataSources] (main) Datasource <default> enables XA but transaction recovery is not enabled. Please enable transaction recovery by setting quarkus.transaction-manager.enable-recovery=true, otherwise data may be lost if the application is terminated abruptly
2024-01-28 13:52:51,729 WARN  [org.infinispan.PERSISTENCE] (keycloak-cache-init) ISPN000554: jboss-marshalling is deprecated and planned for removal
2024-01-28 13:52:52,070 INFO  [org.infinispan.CONTAINER] (keycloak-cache-init) ISPN000556: Starting user marshaller 'org.infinispan.jboss.marshalling.core.JBossUserMarshaller'
2024-01-28 13:52:52,328 INFO  [org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000088: Unable to use any JGroups configuration mechanisms provided in properties {}. Using default JGroups configuration!
2024-01-28 13:52:52,670 INFO  [org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000078: Starting JGroups channel `ISPN`
2024-01-28 13:52:52,673 INFO  [org.jgroups.JChannel] (keycloak-cache-init) local_addr: f84cf898-bda1-4458-a2b9-0afa365204b2, name: ubuntu01-33680
2024-01-28 13:52:52,679 WARN  [org.jgroups.protocols.UDP] (keycloak-cache-init) JGRP000015: the send buffer of socket MulticastSocket was set to 1MB, but the OS only allocated 212.99KB
2024-01-28 13:52:52,680 WARN  [org.jgroups.protocols.UDP] (keycloak-cache-init) JGRP000015: the receive buffer of socket MulticastSocket was set to 20MB, but the OS only allocated 212.99KB
2024-01-28 13:52:52,681 WARN  [org.jgroups.protocols.UDP] (keycloak-cache-init) JGRP000015: the send buffer of socket MulticastSocket was set to 1MB, but the OS only allocated 212.99KB
2024-01-28 13:52:52,683 WARN  [org.jgroups.protocols.UDP] (keycloak-cache-init) JGRP000015: the receive buffer of socket MulticastSocket was set to 25MB, but the OS only allocated 212.99KB
2024-01-28 13:52:52,718 INFO  [org.jgroups.protocols.FD_SOCK2] (keycloak-cache-init) server listening on *.23781
2024-01-28 13:52:54,730 INFO  [org.jgroups.protocols.pbcast.GMS] (keycloak-cache-init) ubuntu01-33680: no members discovered after 2007 ms: creating cluster as coordinator
2024-01-28 13:52:54,737 INFO  [org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000094: Received new cluster view for channel ISPN: [ubuntu01-33680|0] (1) [ubuntu01-33680]
2024-01-28 13:52:54,743 INFO  [org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000079: Channel `ISPN` local address is `ubuntu01-33680`, physical addresses are `[192.168.0.220:39317]`
2024-01-28 13:52:54,755 WARN  [org.infinispan.CONFIG] (keycloak-cache-init) ISPN000569: Unable to persist Infinispan internal caches as no global state enabled
2024-01-28 13:52:55,189 INFO  [org.infinispan.CLUSTER] (main) ISPN000080: Disconnecting JGroups channel `ISPN`
2024-01-28 13:52:55,284 ERROR [org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler] (main) ERROR: Failed to start server in (production) mode
2024-01-28 13:52:55,284 ERROR [org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler] (main) ERROR: Key material not provided to setup HTTPS. Please configure your keys/certificates or start the server in development mode.
2024-01-28 13:52:55,285 ERROR [org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler] (main) For more details run the same command passing the '--verbose' option. Also you can use '--help' to see the details about the usage of the particular command.
root@ubuntu01:/opt/keycloak# /opt/keycloak/bin/kc.sh start --hostname=ubuntu01

Another error. Yes, I’m indeed trying to launch Keycloak without using a certificate. It’s important to note here that I’m just quickly testing, and trying to get it up and running. Before going live with a production environment, of course this would need to be addressed properly.

Let’s try to run it again, this time with HTTP enabled.

root@ubuntu01:/opt/keycloak# /opt/keycloak/bin/kc.sh start --hostname=ubuntu01 --http-enabled=true
2024-01-28 13:57:17,080 INFO  [org.keycloak.quarkus.runtime.hostname.DefaultHostnameProvider] (main) Hostname settings: Base URL: <unset>, Hostname: ubuntu01, Strict HTTPS: true, Path: <request>, Strict BackChannel: false, Admin URL: <unset>, Admin: <request>, Port: -1, Proxied: false
2024-01-28 13:57:18,939 WARN  [io.quarkus.agroal.runtime.DataSources] (main) Datasource <default> enables XA but transaction recovery is not enabled. Please enable transaction recovery by setting quarkus.transaction-manager.enable-recovery=true, otherwise data may be lost if the application is terminated abruptly
2024-01-28 13:57:20,485 WARN  [org.infinispan.PERSISTENCE] (keycloak-cache-init) ISPN000554: jboss-marshalling is deprecated and planned for removal
2024-01-28 13:57:20,623 INFO  [org.infinispan.CONTAINER] (keycloak-cache-init) ISPN000556: Starting user marshaller 'org.infinispan.jboss.marshalling.core.JBossUserMarshaller'
2024-01-28 13:57:20,813 INFO  [org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000088: Unable to use any JGroups configuration mechanisms provided in properties {}. Using default JGroups configuration!
2024-01-28 13:57:21,215 INFO  [org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000078: Starting JGroups channel `ISPN`
2024-01-28 13:57:21,228 INFO  [org.jgroups.JChannel] (keycloak-cache-init) local_addr: 6e1ad1ef-2284-44ed-b58f-3f7dd14e9aa5, name: ubuntu01-34727
2024-01-28 13:57:21,241 WARN  [org.jgroups.protocols.UDP] (keycloak-cache-init) JGRP000015: the send buffer of socket MulticastSocket was set to 1MB, but the OS only allocated 212.99KB
2024-01-28 13:57:21,243 WARN  [org.jgroups.protocols.UDP] (keycloak-cache-init) JGRP000015: the receive buffer of socket MulticastSocket was set to 20MB, but the OS only allocated 212.99KB
2024-01-28 13:57:21,244 WARN  [org.jgroups.protocols.UDP] (keycloak-cache-init) JGRP000015: the send buffer of socket MulticastSocket was set to 1MB, but the OS only allocated 212.99KB
2024-01-28 13:57:21,244 WARN  [org.jgroups.protocols.UDP] (keycloak-cache-init) JGRP000015: the receive buffer of socket MulticastSocket was set to 25MB, but the OS only allocated 212.99KB
2024-01-28 13:57:21,264 INFO  [org.jgroups.protocols.FD_SOCK2] (keycloak-cache-init) server listening on *.32758
2024-01-28 13:57:23,288 INFO  [org.jgroups.protocols.pbcast.GMS] (keycloak-cache-init) ubuntu01-34727: no members discovered after 2004 ms: creating cluster as coordinator
2024-01-28 13:57:23,308 INFO  [org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000094: Received new cluster view for channel ISPN: [ubuntu01-34727|0] (1) [ubuntu01-34727]
2024-01-28 13:57:23,333 INFO  [org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000079: Channel `ISPN` local address is `ubuntu01-34727`, physical addresses are `[192.168.0.220:48294]`
2024-01-28 13:57:23,355 WARN  [org.infinispan.CONFIG] (keycloak-cache-init) ISPN000569: Unable to persist Infinispan internal caches as no global state enabled
2024-01-28 13:57:25,751 INFO  [org.keycloak.quarkus.runtime.storage.legacy.liquibase.QuarkusJpaUpdaterProvider] (main) Initializing database schema. Using changelog META-INF/jpa-changelog-master.xml
UPDATE SUMMARY
Run:                        117
Previously run:               0
Filtered out:                 0
-------------------------------
Total change sets:          117
2024-01-28 13:57:31,584 INFO  [org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory] (main) Node name: ubuntu01-34727, Site name: null
2024-01-28 13:57:31,711 INFO  [org.keycloak.broker.provider.AbstractIdentityProviderMapper] (main) Registering class org.keycloak.broker.provider.mappersync.ConfigSyncEventListener
2024-01-28 13:57:31,788 INFO  [org.keycloak.services] (main) KC-SERVICES0050: Initializing master realm
2024-01-28 13:57:34,400 INFO  [io.quarkus] (main) Keycloak 23.0.4 on JVM (powered by Quarkus 3.2.9.Final) started in 19.120s. Listening on: http://0.0.0.0:8080
2024-01-28 13:57:34,401 INFO  [io.quarkus] (main) Profile prod activated. 
2024-01-28 13:57:34,401 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, jdbc-h2, jdbc-mariadb, jdbc-mssql, jdbc-mysql, jdbc-oracle, jdbc-postgresql, keycloak, logging-gelf, micrometer, narayana-jta, reactive-routes, resteasy-reactive, resteasy-reactive-jackson, smallrye-context-propagation, smallrye-health, vertx]

Alright, this seems to work! On this server, when navigating to http://127.0.0.1:8080/ , you should be able to see the Keycloak UI now.

Explaining how to get a proper SSL certificate (not a self-signed one) will lead us too far off topic. So for the purpose of this post, I did create a self-signed SSL certificate.

# Create directory to store the certificate and the key.
root@ubuntu01:/opt/keycloak# mkdir /opt/keycloak/certs
# Use openssl to generate a self-signed certificate.
root@ubuntu01:/opt/keycloak# openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout mycert.key -out mycert.crt 

Let’s try to run the Keycloak server with the self-signed certificate now. I’ve also specified the HTTPS port on which I expect Keycloak to listen: 8443.

root@ubuntu01:/opt/keycloak# /opt/keycloak/bin/kc.sh start --hostname=ubuntu01 --https-certificate-file=/opt/keycloak/cert/mycert.crt --https-certificate-key-file=/opt/keycloak/cert/mycert.key --https-port=8443

This time, when navigating on this server to https://127.0.0.1:8443/ , I did get a certificate warning (since it’s self-signed). But it does work.

Final step for the service configuration: update the configuration file again to reflect this new instruction.

I did this:

root@ubuntu01:/opt/keycloak# systemctl daemon-reload
root@ubuntu01:/opt/keycloak# systemctl restart keycloak
root@ubuntu01:/opt/keycloak# systemctl status keycloak

It worked. For a few seconds. Then it failed. To properly debug it, I changed the “StandardOutput” line from the service configuration.

StandardOutput=file:/var/log/keycloak/standard.log
StandardError=file:/var/log/keycloak/error.log

Other lessons I learned: Make sure to create the log directory and set the permissions (in this case, change the owner to the keycloak user):

# Create log directory.
root@ubuntu01:/opt/keycloak# mkdir /var/log/keycloak
# Change the owner to the keycloak user (defined in the service configuration).
root@ubuntu01:/opt/keycloak# chown -R /var/log/keycloak

The reason it had crashed, turned out to be folder and file permissions. While configuring, I ran some commands as the root user (named “root”). As a result, under /opt/keycloak, the data and certs directories were both having the root user as an owner.

# Set owner of folder and files of the entire Keycloak directory again to the keycloak user.
root@ubuntu01:/opt/keycloak# chown -R keycloak: /opt/keycloak

Finally, it was up and running.

Bonus hint: for users, their account page (sign in) in Keycloak 23 will for example be: https://ubuntu01:8443/realms/iTop/account .
So: https://{fqdn}/realms/{realmName}/account

Scroll to Top