If you’re running MariaDB on RHEL, Rocky Linux, AlmaLinux, or any other EL8+ distribution, you may eventually want to move your MariaDB data directory from the default location:

/var/lib/mysql

to somewhere under /home, for example:

/home/mysql

This is a very common requirement when:

  • Your /home mount has much more free space
  • You are using separate volumes for data and OS
  • You want a clean separation of system and application data

In this post, we’ll walk through a safe, script-driven method to move your MariaDB datadir on EL8+ systems, with:

  • Full backup of your existing data
  • Automatic rsync-based migration
  • Systemd override with ProtectHome and ReadWritePaths
  • SELinux context fix for /home/mysql
  • Automatic rollback if MariaDB fails to start from the new location

⚠️ Warning: This is a low-level data operation. Always make sure you have external backups (snapshot / rsync / offsite backups) before you proceed.


Why not just disable ProtectHome?

On many modern EL-based systems, MariaDB runs under a systemd sandbox with options like:

ProtectHome=read-only

At first glance, this looks like a problem when moving data under /home, and it’s tempting to disable this protection completely with:

ProtectHome=false

However, a much safer and more elegant solution is to:

  • Keep ProtectHome=read-only (preserve security)
  • Use ReadWritePaths=/home/mysql to allow only /home/mysql to be writable

This way, MariaDB cannot touch other user home directories, but it can read and write in /home/mysql where your data lives.

We’ll implement exactly that in our systemd override.


Requirements and Assumptions

This guide assumes:

  • You are on EL8 or newer (RHEL 8+, Rocky 8+, Alma 8+)
  • MariaDB is installed and running
  • The default data directory is /var/lib/mysql
  • You want to move data to /home/mysql
  • You are comfortable using the root shell

We also assume MariaDB’s systemd service is either:

  • mariadb.service or
  • mysqld.service

The script below automatically detects the correct one.


Step 1 – Create the Migration Script

Create the following script on your server, for example:

nano /root/move-mariadb-to-home-el8.sh

Paste the script below into the file:

#!/bin/sh
#
# Move MariaDB data directory from /var/lib/mysql to /home/mysql on EL8+ (RHEL / Rocky / Alma)
# with backups, fault tolerance, SELinux fixes, and basic auto-correction.
#
# Must be run as root.

set -eu

OLD_DATADIR="/var/lib/mysql"
NEW_DATADIR="/home/mysql"
BACKUP_SUFFIX="$(date +%Y%m%d-%H%M%S)"

log() {
    echo "[*] $*"
}

err() {
    echo "[!] $*" >&2
}

# --- Safety checks ---------------------------------------------------------

if [ "$(id -u)" != "0" ]; then
    err "This script must be run as root."
    exit 1
fi

if ! command -v systemctl >/dev/null 2>&1; then
    err "systemctl not found. This script is intended for systemd-based systems."
    exit 1
fi

# Detect MariaDB service name on EL8+ (can be mariadb or mysqld)
SERVICE_NAME=""
if systemctl list-unit-files | grep -q '^mariadb.service'; then
    SERVICE_NAME="mariadb"
elif systemctl list-unit-files | grep -q '^mysqld.service'; then
    SERVICE_NAME="mysqld"
else
    err "Neither mariadb.service nor mysqld.service found. Are you sure MariaDB is installed?"
    exit 1
fi

log "Detected MariaDB systemd unit: ${SERVICE_NAME}.service"

if [ -L "$OLD_DATADIR" ]; then
    log "$OLD_DATADIR is already a symlink. Aborting to avoid nested moves."
    ls -ld "$OLD_DATADIR"
    exit 1
fi

if [ ! -d "$OLD_DATADIR" ]; then
    err "Old data directory $OLD_DATADIR does not exist or is not a directory."
    exit 1
fi

# --- Stop MariaDB ---------------------------------------------------------

log "Stopping ${SERVICE_NAME}..."
if ! systemctl stop "$SERVICE_NAME"; then
    err "Failed to stop ${SERVICE_NAME}.service. Aborting."
    exit 1
fi

# --- Prepare target directory ---------------------------------------------

log "Creating new data directory at $NEW_DATADIR if needed..."
mkdir -p "$NEW_DATADIR"

# Backup existing NEW_DATADIR if it is non-empty
if [ "$(find "$NEW_DATADIR" -mindepth 1 -maxdepth 1 2>/dev/null | wc -l)" -gt 0 ]; then
    NEW_BACKUP="${NEW_DATADIR}.preexist-backup-${BACKUP_SUFFIX}"
    log "$NEW_DATADIR is not empty. Moving its contents to $NEW_BACKUP"
    mkdir -p "$NEW_BACKUP"
    mv "$NEW_DATADIR"/* "$NEW_BACKUP"/ 2>/dev/null || true
fi

# --- Backup old data dir --------------------------------------------------

OLD_BACKUP="${OLD_DATADIR}.backup-${BACKUP_SUFFIX}"
log "Backing up old data directory $OLD_DATADIR to $OLD_BACKUP ..."
cp -a "$OLD_DATADIR" "$OLD_BACKUP"

# --- Move data to new location (rsync for safety) -------------------------

log "Syncing data from $OLD_DATADIR to $NEW_DATADIR ..."
if ! command -v rsync >/dev/null 2>&1; then
    err "rsync not found. Install rsync and run again."
    # Restore from backup (best effort)
    log "Restoring from backup..."
    rm -rf "$NEW_DATADIR"/*
    cp -a "$OLD_BACKUP"/* "$OLD_DATADIR"/ 2>/dev/null || true
    exit 1
fi

rsync -aHAX --delete "$OLD_DATADIR"/ "$NEW_DATADIR"/

# --- Fix ownership & perms ------------------------------------------------

log "Setting ownership and permissions on $NEW_DATADIR ..."
chown -R mysql:mysql "$NEW_DATADIR"
# Make sure /home is executable (otherwise mysql won't traverse)
chmod 755 /home
find "$NEW_DATADIR" -type d -exec chmod 750 {} \; 2>/dev/null || true
find "$NEW_DATADIR" -type f -exec chmod 640 {} \; 2>/dev/null || true

# --- Replace OLD_DATADIR with symlink ------------------------------------

log "Replacing $OLD_DATADIR with symlink to $NEW_DATADIR ..."

# Move original out of the way (we already backed it up above)
OLD_ORIG="${OLD_DATADIR}.orig-${BACKUP_SUFFIX}"
mv "$OLD_DATADIR" "$OLD_ORIG"

# Create symlink
ln -s "$NEW_DATADIR" "$OLD_DATADIR"

# --- systemd override: ProtectHome & ReadWritePaths ----------------------

OVERRIDE_DIR="/etc/systemd/system/${SERVICE_NAME}.service.d"
OVERRIDE_CONF="$OVERRIDE_DIR/override.conf"

log "Configuring systemd override at $OVERRIDE_CONF ..."
mkdir -p "$OVERRIDE_DIR"

cat > "$OVERRIDE_CONF" <<EOF
[Service]
ProtectHome=read-only
ReadWritePaths=$NEW_DATADIR
EOF

log "Reloading systemd daemon..."
systemctl daemon-reload

# --- SELinux adjustments (typical on EL8+) --------------------------------
# If SELinux is enforcing or permissive, label /home/mysql as mysqld_db_t
# and persist with semanage, if available.

SELINUX_MODE="disabled"
if command -v getenforce >/dev/null 2>&1; then
    SELINUX_MODE="$(getenforce || echo Disabled)"
fi

case "$SELINUX_MODE" in
    Enforcing|Permissive)
        log "SELinux is $SELINUX_MODE. Adjusting context for $NEW_DATADIR ..."
        if command -v semanage >/dev/null 2>&1; then
            # Add a persistent rule
            semanage fcontext -a -t mysqld_db_t "${NEW_DATADIR}(/.*)?"
        else
            log "semanage not found. Install policycoreutils-python-utils for persistent SELinux rules."
        fi
        # Apply context immediately
        if command -v restorecon >/dev/null 2>&1; then
            restorecon -Rv "$NEW_DATADIR" || true
        else
            log "restorecon not found; cannot relabel $NEW_DATADIR automatically."
        fi
        ;;
    *)
        log "SELinux is $SELINUX_MODE; skipping SELinux context adjustments."
        ;;
esac

# --- Start MariaDB and verify --------------------------------------------

log "Starting ${SERVICE_NAME} with new data location..."
if ! systemctl start "$SERVICE_NAME"; then
    err "${SERVICE_NAME} failed to start. Attempting rollback..."

    # Stop again just in case
    systemctl stop "$SERVICE_NAME" || true

    # Remove symlink and restore original dir
    rm -f "$OLD_DATADIR"
    mv "$OLD_ORIG" "$OLD_DATADIR"

    # Restore original override (best effort: just move file away)
    if [ -f "$OVERRIDE_CONF" ]; then
        mv "$OVERRIDE_CONF" "${OVERRIDE_CONF}.failed-${BACKUP_SUFFIX}" || true
        systemctl daemon-reload || true
    fi

    # Try starting MariaDB from original location
    systemctl start "$SERVICE_NAME" || true

    err "Rollback attempted. ${SERVICE_NAME} may be running from the original location."
    err "Check 'systemctl status ${SERVICE_NAME}' and logs with 'journalctl -u ${SERVICE_NAME}.service'."
    err "Backup of original data: $OLD_BACKUP"
    exit 1
fi

# --- Final checks ---------------------------------------------------------

log "${SERVICE_NAME} started successfully."

# Test that mysql user can write to new dir
if su -s /bin/sh -c "touch '$NEW_DATADIR/.write_test'" mysql 2>/dev/null; then
    rm -f "$NEW_DATADIR/.write_test"
    log "Verified mysql user can write to $NEW_DATADIR."
else
    err "Warning: mysql user could not write test file in $NEW_DATADIR."
fi

log "Old original datadir preserved at: $OLD_ORIG"
log "Backup copy of original data at:  $OLD_BACKUP"
log "New data directory in use via symlink: $OLD_DATADIR -> $NEW_DATADIR"

echo "[+] Migration complete."
exit 0

Save and make it executable:

chmod +x /root/move-mariadb-to-home-el8.sh

Step 2 – Run the Migration Script

Now simply run:

sudo /root/move-mariadb-to-home-el8.sh

The script will:

  1. Detect whether your service is mariadb or mysqld
  2. Stop the MariaDB service
  3. Create /home/mysql if needed
  4. Backup the old datadir as:
    • /var/lib/mysql.backup-<timestamp>
  5. Rsync the data to /home/mysql
  6. Move /var/lib/mysql out of the way to:
    • /var/lib/mysql.orig-<timestamp>
  7. Create a symlink:
    • /var/lib/mysql -> /home/mysql
  8. Create a systemd override: [Service] ProtectHome=read-only ReadWritePaths=/home/mysql
  9. Apply SELinux context for /home/mysql as mysqld_db_t
  10. Start MariaDB again
  11. Run a small test to confirm that the mysql user can write into /home/mysql

Step 3 – Verify MariaDB and SELinux

After the script completes, verify that MariaDB is running:

systemctl status mariadb
# or
systemctl status mysqld

Check the datadir inside MariaDB:

mysql -u root -p -e "SHOW VARIABLES LIKE 'datadir';"

You should see something like:

+---------------+------------------+
| Variable_name | Value            |
+---------------+------------------+
| datadir       | /var/lib/mysql/  |
+---------------+------------------+

Even though it shows /var/lib/mysql, remember that this is now a symlink pointing to /home/mysql.

To verify SELinux context:

ls -ldZ /home/mysql
ls -lZ /home | grep mysql

You should see context mysqld_db_t applied to /home/mysql.

If needed, you can reapply it manually:

semanage fcontext -a -t mysqld_db_t "/home/mysql(/.*)?"
restorecon -Rv /home/mysql

What if something goes wrong?

If MariaDB fails to start from the new path, the script will:

  • Stop MariaDB again (best effort)
  • Remove the symlink /var/lib/mysql
  • Restore the original directory from /var/lib/mysql.orig-<timestamp>
  • Move the systemd override file aside
  • Attempt to start MariaDB from the original location

You’ll see log messages pointing you to:

  • The backup:
    /var/lib/mysql.backup-<timestamp>
  • The restored original directory:
    /var/lib/mysql.orig-<timestamp> (or moved back to /var/lib/mysql)

And you can inspect the logs with:

journalctl -u mariadb.service
# or
journalctl -u mysqld.service

Why this approach is production-friendly

This method is designed with production environments in mind:

  • No manual editing of my.cnf paths required
  • Uses a symlink + systemd override instead of invasive changes
  • Keeps systemd sandboxing enabled (ProtectHome=read-only)
  • Uses ReadWritePaths to give MariaDB only the access it needs
  • Plays nicely with SELinux, labeling /home/mysql as mysqld_db_t
  • Includes backups and a rollback path if something fails

If you already followed the Debian version of this guide, this EL8+ variant is essentially the same idea, with SELinux-specific adjustments and a small difference in how the service name is detected.


Conclusion

By combining:

  • systemd sandboxing (ProtectHome=read-only)
  • targeted write access (ReadWritePaths=/home/mysql)
  • SELinux labeling (mysqld_db_t)

you can safely move your MariaDB data directory to /home/mysql on RHEL / Rocky / AlmaLinux 8+ without weakening your server’s security posture.

If you run this on your staging or production servers and notice any distro-specific quirks, consider documenting them for your own internal runbooks or sharing them with the community—this pattern is reusable for many other services that need to write under /home while still benefiting from systemd and SELinux hardening.

Last Update: December 2, 2025

Tagged in:

, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,