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
/homemount 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
ProtectHomeandReadWritePaths - 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/mysqlto allow only/home/mysqlto 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.serviceormysqld.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:
- Detect whether your service is
mariadbormysqld - Stop the MariaDB service
- Create
/home/mysqlif needed - Backup the old datadir as:
/var/lib/mysql.backup-<timestamp>
- Rsync the data to
/home/mysql - Move
/var/lib/mysqlout of the way to:/var/lib/mysql.orig-<timestamp>
- Create a symlink:
/var/lib/mysql -> /home/mysql
- Create a systemd override:
[Service] ProtectHome=read-only ReadWritePaths=/home/mysql - Apply SELinux context for
/home/mysqlasmysqld_db_t - Start MariaDB again
- Run a small test to confirm that the
mysqluser 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.cnfpaths required - Uses a symlink + systemd override instead of invasive changes
- Keeps systemd sandboxing enabled (
ProtectHome=read-only) - Uses
ReadWritePathsto give MariaDB only the access it needs - Plays nicely with SELinux, labeling
/home/mysqlasmysqld_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.