← Deploying Rundeck the Right Way

Chapter 3

MariaDB Setup

In this chapter
<nav id="TableOfContents" aria-label="Chapter sections"> <ul> <li><a href="#why-mariadb-not-h2">Why MariaDB, Not H2</a></li> <li><a href="#why-mariadb">Why MariaDB</a></li> <li><a href="#install-mariadb">Install MariaDB</a></li> <li><a href="#secure-the-installation">Secure the Installation</a></li> <li><a href="#create-the-rundeck-database-and-user">Create the Rundeck Database and User</a></li> <li><a href="#tune-mariadb-for-rundeck">Tune MariaDB for Rundeck</a></li> <li><a href="#verification">Verification</a></li> <li><a href="#what-can-go-wrong">What Can Go Wrong</a></li> </ul> </nav>

What you’ll accomplish: Install, secure, and configure MariaDB as Rundeck’s persistent backend — eliminating the H2 database corruption risk entirely.

Why MariaDB, Not H2

This is the single most important decision in your Rundeck deployment, and it’s not close.

Rundeck ships with H2, an embedded Java database. H2 is convenient — zero configuration, no extra services, just start Rundeck and go. It’s also a time bomb. H2 writes data to disk asynchronously. If Rundeck shuts down uncleanly — power outage, OOM kill, even a systemctl stop that lands during a heavy write — the database file can corrupt. When it does, your job definitions, execution history, project configuration, and key storage are gone. Not degraded. Gone.

This isn’t theoretical. The Rundeck GitHub repository has a graveyard of issues from users who lost everything:

  • #6003 — “File corrupted while reading record” after server restart
  • #7764 — Rundeck fails to start, H2 database unrecoverable
  • #3044 — Data loss after power failure
  • #3868 — Corrupted H2 with no recovery path

Beyond corruption, H2 has practical limitations that matter even day-to-day:

  • Performance degrades under minimal load. H2 uses database-level locking. Users report sluggish UI with as few as one job defined.
  • No hot backups. The database is a file inside Rundeck’s data directory. You can’t back it up without stopping the service first.
  • Migration is painful. If you start with H2 and decide to switch to a real database later, you’ll need to export jobs, recreate projects manually, and accept that all execution history is lost.

Start with MariaDB. You’ll set it up once, and it will never be a problem again.

Why MariaDB

MariaDB is in Rocky Linux’s base repos — no extra repositories needed, no GPG key juggling, just dnf install and go. It’s a drop-in replacement for MySQL, fully compatible with the same SQL syntax, the same mysql CLI tools, and the same Ansible community.mysql modules. It’s what I’ve tested this entire deployment against, and it’s what the bundled Ansible playbooks use.

Note: If you prefer MySQL, you’ll need to enable the MySQL community repo (it’s not in Rocky’s base repos). The changes from this guide would be: the JDBC driver (com.mysql.cj.jdbc.Driver instead of org.mariadb.jdbc.Driver), the Hibernate dialect (MySQL8Dialect instead of MariaDBDialect), and the service name (mysqld instead of mariadb). Everything else — database creation, user setup, tuning — is identical.

Install MariaDB

Install the server, the client tools, and the Python MySQL library (needed by Ansible’s mysql_* modules if you’re automating this later):

sudo dnf install -y mariadb-server mariadb python3-PyMySQL

Start the service and enable it to survive reboots:

sudo systemctl enable --now mariadb

Verify it’s running:

sudo systemctl status mariadb

Expected output (key lines):

● mariadb.service - MariaDB 10.5 database server
     Active: active (running) since ...

Secure the Installation

A fresh MariaDB install has permissive defaults that are fine for development and dangerous for anything else. Run the hardening script:

sudo mysql_secure_installation

This is an interactive script. It will ask for the current root password first — since you just installed MariaDB, press Enter (there is no password yet). Then answer the prompts:

PromptAnswerWhy
Enter current password for rootPress EnterNo password set on fresh install
Switch to unix_socket authentication?YKeeps root access locked to the local root OS user — more secure than a password for local-only access
Change the root password?nNot needed — unix_socket auth handles root access. The Rundeck database user gets its own password.
Remove anonymous users?YAnonymous users are a security hole
Disallow root login remotely?YRoot should only connect from localhost
Remove test database and access to it?YThe test database is world-readable
Reload privilege tables now?YApply changes immediately

Note: With unix_socket authentication, you connect as root by running sudo mysql — no password needed. This is MariaDB’s recommended default on Rocky Linux and is what the bundled Ansible playbooks use. The Rundeck application connects with its own rundeck database user (which uses password auth), so this doesn’t affect Rundeck’s operation.

Warning: Don’t skip mysql_secure_installation. An unsecured MariaDB instance on your network is an open door, even in a home lab. It takes 30 seconds.

Create the Rundeck Database and User

Connect to MariaDB as root:

sudo mysql

Create the database. Rundeck needs utf8mb4 character encoding to handle the full range of Unicode characters in job output and logs:

CREATE DATABASE rundeck_db
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

Create a dedicated user for Rundeck. Never use the root account for application connections:

CREATE USER 'rundeck'@'localhost'
  IDENTIFIED BY 'your-password-here';

Replace your-password-here with a strong, random password. This password goes into your Ansible vault as vault_rundeck_db_password — you’ll reference it as {{ vault_rundeck_db_password }} in the Rundeck configuration in Chapter 4.

Grant the rundeck user full access to the rundeck_db database and nothing else:

GRANT ALL PRIVILEGES ON rundeck_db.* TO 'rundeck'@'localhost';
FLUSH PRIVILEGES;

Exit the MySQL shell:

EXIT;

Tune MariaDB for Rundeck

The default MariaDB configuration works, but a few settings make a measurable difference for Rundeck’s workload. Rundeck’s database access pattern is write-heavy (every job execution logs metadata) with periodic read bursts (loading the web UI, querying execution history).

Create a Rundeck-specific configuration file:

sudo vi /etc/my.cnf.d/rundeck.cnf

Add these settings:

[mysqld]
# -- Bind address: restrict MariaDB to loopback only.
# Rundeck connects over localhost; there's no reason to expose 3306 to the network.
bind-address = 127.0.0.1

# -- Buffer pool: cache for InnoDB data and indexes.
# Default is 128M, which is too small. Set to ~25% of available RAM.
# For a 4 GB host: 1G. For 8 GB: 2G.
innodb_buffer_pool_size = 1G

# -- Max connections: Rundeck uses ~2-3 JDBC connections per concurrent job.
# 100 connections supports ~30 concurrent jobs, which is more than enough.
# Default is 151, so this is a slight reduction that's still generous.
max_connections = 100

# -- Character set: enforce utf8mb4 server-wide so new tables
# inherit it automatically. Prevents encoding mismatches.
character_set_server = utf8mb4
collation_server = utf8mb4_unicode_ci

# -- Log file size: larger log files mean fewer checkpoints and
# better write performance. Default is 48M.
innodb_log_file_size = 256M

These five settings cover 90% of what matters for a Rundeck workload. Don’t over-tune — MariaDB’s defaults are sensible for most other parameters.

Restart MariaDB to apply the changes:

sudo systemctl restart mariadb

Verify the settings took effect:

sudo mysql -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"
sudo mysql -e "SHOW VARIABLES LIKE 'max_connections';"
sudo mysql -e "SHOW VARIABLES LIKE 'character_set_server';"

Expected output:

+--------------------------+------------+
| Variable_name            | Value      |
+--------------------------+------------+
| innodb_buffer_pool_size  | 1073741824 |
+--------------------------+------------+
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| max_connections | 100   |
+-----------------+-------+
+----------------------+---------+
| Variable_name        | Value   |
+----------------------+---------+
| character_set_server | utf8mb4 |
+----------------------+---------+

The innodb_buffer_pool_size value is shown in bytes — 1073741824 is 1 GB.

Verification

Run these checks to confirm everything is working before moving on.

1. Connect as the rundeck user and verify the database exists:

mysql -u rundeck -p -e "SHOW DATABASES;"

Enter the password you set earlier. Expected output:

+--------------------+
| Database           |
+--------------------+
| information_schema |
| rundeck_db         |
+--------------------+

You should see rundeck_db in the list. If you only see information_schema, the GRANT statement didn’t work — go back and re-run it.

2. Verify the rundeck user can create tables in the database:

mysql -u rundeck -p rundeck_db -e "CREATE TABLE _test (id INT); DROP TABLE _test;"

This should complete silently with no errors. If you get Access denied, the privileges weren’t granted correctly.

3. Verify MariaDB is listening only on localhost:

sudo ss -tlnp | grep 3306

Expected output:

LISTEN 0      128       127.0.0.1:3306      0.0.0.0:*    users:(("mariadbd",pid=...,fd=...))

The key detail is 127.0.0.1:3306 — MariaDB is bound to loopback. If you see 0.0.0.0:3306, it’s listening on all interfaces. Edit /etc/my.cnf.d/rundeck.cnf, add bind-address = 127.0.0.1 under [mysqld], and restart MariaDB.

4. Verify the character set:

mysql -u rundeck -p -e "SELECT DEFAULT_CHARACTER_SET_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = 'rundeck_db';"

Expected output:

+----------------------------+
| DEFAULT_CHARACTER_SET_NAME |
+----------------------------+
| utf8mb4                    |
+----------------------------+

All four checks pass? MariaDB is ready. Rundeck’s data has a proper home.

What Can Go Wrong

“Access denied for user ‘rundeck’@’localhost’”

The most common cause is a typo in the password during CREATE USER or a mismatch between what you set and what you’re typing. Drop and recreate the user:

sudo mysql -e "DROP USER 'rundeck'@'localhost'; FLUSH PRIVILEGES;"
sudo mysql -e "CREATE USER 'rundeck'@'localhost' IDENTIFIED BY 'your-new-password-here';"
sudo mysql -e "GRANT ALL PRIVILEGES ON rundeck_db.* TO 'rundeck'@'localhost'; FLUSH PRIVILEGES;"

MariaDB won’t start after changing innodb_log_file_size

If you changed innodb_log_file_size on an existing installation (not a fresh one), MariaDB may refuse to start because the existing log files don’t match the new size. Check journalctl -u mariadb for the error. The fix: stop MariaDB, delete the old InnoDB log files (/var/lib/mysql/ib_logfile0 and ib_logfile1), and start MariaDB again. It will recreate them at the new size. On a fresh install, this won’t happen.

“Can’t connect to local MySQL server through socket”

MariaDB isn’t running. Check the service status:

sudo systemctl status mariadb
sudo journalctl -u mariadb --since "5 minutes ago" --no-pager

Common causes: a syntax error in /etc/my.cnf.d/rundeck.cnf (MariaDB will refuse to start), insufficient disk space, or a port conflict if something else is already on 3306.

Character set shows latin1 instead of utf8mb4

The database was created before you set character_set_server in rundeck.cnf. You have two options: drop and recreate the database with the correct character set, or alter it:

sudo mysql -e "ALTER DATABASE rundeck_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"

Since the database is empty at this point, either approach works. If Rundeck has already created tables, use ALTER DATABASE — it affects new tables, and Rundeck will create its schema correctly on next startup.


MariaDB is installed, secured, tuned, and verified. In the next chapter, we’ll install Rundeck itself and point it at this database.

Want the automation code? Get the production-ready Ansible playbooks that deploy everything in this guide in ~10 minutes.

Get Playbooks — $14