If you’re running MariaDB (or MySQL compatible) on a Debian server, by default your database files live under:
/var/lib/mysql
That’s fine until your root disk starts filling up, or you want your databases on a larger or faster volume mounted under /home.
In this guide, we’ll:
- Move MariaDB’s data directory from
/var/lib/mysqlto/home/mysql - Use a POSIX sh shell script that:
- Creates backups automatically
- Handles permissions and systemd hardening
- Adjusts AppArmor if needed
- Rolls back if something goes wrong
You can use this on Debian 12 (and similar) with mariadb.service running under systemd.
Why Move MariaDB to /home/mysql?
Common reasons:
- Your
/(root) filesystem is small or almost full - You’ve attached a bigger disk and mounted it at
/home - You want a clean separation of OS files and data files
- You’re planning for better performance and easier backup strategies
Instead of manually fighting with permissions, systemd, and AppArmor every time, we’ll let a script do the heavy lifting.
Prerequisites
Before you start, make sure:
- You are on a system using
systemd - MariaDB is installed and managed as
mariadb.service - You can run commands as
root(or viasudo) rsyncis installed:
apt install rsync -y
Important: Always back up your databases before any migration. The script itself creates backups of the data directory, but having your own
mariadb-dumpis wise.
Example full backup:
mariadb-dump --add-drop-table --all-databases | gzip > /home/alldatabases.sql.gz
What the Script Does
The script:
- Verifies you are root and that
mariadb.serviceexists - Stops MariaDB
- Backs up the existing data directory
/var/lib/mysql - Creates
/home/mysql(and backs up its contents if it’s non-empty) - Uses
rsyncto copy all data from/var/lib/mysqlto/home/mysql - Sets correct ownership and permissions
- Moves the original
/var/lib/mysqlaside and replaces it with a symlink to/home/mysql - Creates a
systemdoverride formariadb.service:ProtectHome=read-onlyReadWritePaths=/home/mysql
- Updates AppArmor (if a mysqld profile exists) to allow
/home/mysql - Starts MariaDB again
- If starting fails, it rolls back:
- Restores the original directory
- Removes the override
- Tries to start MariaDB from the original location
This gives you:
- A safer migration
- Automatic rollback in case something breaks
- Backups kept with timestamps
Step-by-Step: Using the Script
1. Create the Script File
Log in as root (or use sudo -i) and create a new file:
nano /root/move-mariadb-to-home.sh
Paste the full script below (in the next section) into that file, then save and exit.
2. Make the Script Executable
chmod +x /root/move-mariadb-to-home.sh
3. Run the Script
Run it as root:
/root/move-mariadb-to-home.sh
You’ll see logs like:
[*] Stopping MariaDB...
[*] Backing up old data directory /var/lib/mysql to /var/lib/mysql.backup-20251129-171500 ...
[*] Syncing data from /var/lib/mysql to /home/mysql ...
[*] Replacing /var/lib/mysql with symlink to /home/mysql ...
[*] Configuring systemd override at /etc/systemd/system/mariadb.service.d/override.conf ...
[*] Starting MariaDB with new data location...
[*] MariaDB started successfully.
If something fails, the script will attempt a rollback and tell you where the backups are.
Verifying the New Setup
After running the script, verify:
Check MariaDB status
systemctl status mariadb
You should see active (running).
Check directories and symlink
ls -ld /var/lib/mysql
ls -ld /home/mysql
Expected:
/var/lib/mysqlis a symlink →/home/mysql/home/mysqlis owned bymysql:mysql
Check datadir value
MariaDB will still report /var/lib/mysql as the data directory:
mysql -e "SHOW VARIABLES LIKE 'datadir';"
Even though it says /var/lib/mysql/, the actual files now live in /home/mysql thanks to the symlink.
Full Script: move-mariadb-to-home.sh
Paste this into /root/move-mariadb-to-home.sh:
#!/bin/sh
#
# Move MariaDB data directory from /var/lib/mysql to /home/mysql on Debian 12
# with backups, fault tolerance, 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
if ! systemctl list-unit-files | grep -q '^mariadb.service'; then
err "mariadb.service not found. Are you sure MariaDB is installed?"
exit 1
fi
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 MariaDB..."
if ! systemctl stop mariadb; then
err "Failed to stop mariadb.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"
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/mariadb.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
# --- AppArmor tweak (if profile exists) ----------------------------------
if command -v aa-status >/dev/null 2>&1; then
if aa-status 2>/dev/null | grep -q "usr.sbin.mysqld"; then
log "AppArmor profile for mysqld detected. Updating paths..."
APPARMOR_PROFILE="/etc/apparmor.d/usr.sbin.mysqld"
if [ -f "$APPARMOR_PROFILE" ]; then
# Only append if not already present
if ! grep -q "$NEW_DATADIR/" "$APPARMOR_PROFILE" 2>/dev/null; then
cat >> "$APPARMOR_PROFILE" <<EOF
# Added by move-mariadb-to-home.sh
$NEW_DATADIR/ r,
$NEW_DATADIR/** rwk,
EOF
log "Reloading AppArmor..."
systemctl reload apparmor || true
else
log "AppArmor profile already contains $NEW_DATADIR rules."
fi
else
log "AppArmor profile file $APPARMOR_PROFILE not found; skipping."
fi
else
log "No enforcing AppArmor profile for mysqld reported; skipping AppArmor changes."
fi
else
log "AppArmor tools not installed; skipping AppArmor changes."
fi
# --- Start MariaDB and verify --------------------------------------------
log "Starting MariaDB with new data location..."
if ! systemctl start mariadb; then
err "MariaDB failed to start. Attempting rollback..."
# Stop again just in case
systemctl stop mariadb || 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 mariadb || true
err "Rollback attempted. MariaDB may be running from the original location."
err "Check 'systemctl status mariadb' and logs with 'journalctl -u mariadb.service'."
err "Backup of original data: $OLD_BACKUP"
exit 1
fi
# --- Final checks ---------------------------------------------------------
log "MariaDB started successfully."
# Test that mysql user can write to new dir
if sudo -u mysql touch "$NEW_DATADIR/.write_test" 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
Conclusion
With this script and guide, you now have a repeatable, fault-tolerant way to move MariaDB’s data directory from /var/lib/mysql to /home/mysql on Debian:
- Safer than manual copy/paste operations
- Automatically handles systemd hardening and AppArmor
- Easy to run again on future servers
Feel free to share or adapt this script in your own infrastructure tooling or DevOps playbooks.