Skip to content
Commitpedia
Log in Join & edit

Schedule PostgreSQL and MySQL dumps with systemd timers

Write one dump script for PostgreSQL and MySQL, run it from a systemd service and timer instead of cron, keep two weeks of dumps, and read the result with systemctl and journalctl.

Drafted with AI from official documentation. Scripts were run in a test environment and repository links verified. Awaiting a human check. Did it work for you? Last edited Outdated?

Edit history of Schedule PostgreSQL and MySQL dumps with systemd timers: 1 recorded edit
Edit history · 1 edit
Type
Article
Edits
1
Editors
1
First issued
15 Oct 2026
Last edit
15 Oct 2026
Reading
4 min

A systemd timer does what a cron line does, with three advantages: the output lands in the journal with the service name attached, Persistent=true runs a missed job after a reboot, and systemctl list-timers shows you exactly when the next dump will happen. This guide builds a single script that dumps every PostgreSQL and MySQL database on the host, then wires it to a nightly timer.

Step 1: credentials without passwords in the script

Run the service as root and let each database authenticate the usual way. PostgreSQL on Debian and Ubuntu trusts the local postgres system user through peer authentication, so the script switches to that user with sudo -u postgres. For MySQL and MariaDB put the credentials in /root/.my.cnf, readable only by root:

[client]
user=backup
password=change-me

Check before you run this

  • Runs with administrator rights. Make sure you understand every line.
sudo chmod 600 /root/.my.cnf
sudo mysql -e "CREATE USER 'backup'@'localhost' IDENTIFIED BY 'change-me'; GRANT SELECT, SHOW VIEW, TRIGGER, LOCK TABLES, EVENT, PROCESS, RELOAD ON *.* TO 'backup'@'localhost';"

A dedicated read-only backup user means a leaked dump credential cannot modify data. Test both logins before going further: sudo -u postgres psql -c 'select 1' and sudo mysql -e 'select 1'.

Step 2: the dump script

Save this as /usr/local/bin/db-dump.sh and make it executable with chmod 755. It writes one file per database plus the PostgreSQL globals (roles and tablespaces), compresses them, and removes dumps older than 14 days.

Check before you run this

  • Runs with administrator rights. Make sure you understand every line.
#!/usr/bin/env bash
set -euo pipefail

DEST=/var/backups/db
KEEP_DAYS=14
STAMP=$(date +%F_%H%M)
mkdir -p "$DEST/postgresql" "$DEST/mysql"

if command -v pg_dump >/dev/null && systemctl is-active --quiet postgresql; then
  sudo -u postgres pg_dumpall --globals-only | gzip > "$DEST/postgresql/globals_$STAMP.sql.gz"
  for db in $(sudo -u postgres psql -At -c "SELECT datname FROM pg_database WHERE NOT datistemplate AND datname <> 'postgres'"); do
    sudo -u postgres pg_dump --format=custom --file "$DEST/postgresql/${db}_$STAMP.dump" "$db"
    echo "postgresql: $db dumped"
  done
fi

if command -v mysqldump >/dev/null && (systemctl is-active --quiet mysql || systemctl is-active --quiet mariadb); then
  for db in $(mysql -N -e "SHOW DATABASES" | grep -Ev '^(information_schema|performance_schema|sys)$'); do
    mysqldump --single-transaction --routines --triggers --events "$db" | gzip > "$DEST/mysql/${db}_$STAMP.sql.gz"
    echo "mysql: $db dumped"
  done
fi

find "$DEST" -type f -mtime +"$KEEP_DAYS" -print -delete
echo "dump finished: $(du -sh "$DEST" | cut -f1) in $DEST"
  • pg_dump --format=custom produces a compressed archive that pg_restore can restore selectively (one table, schema only, data only).
  • pg_dumpall --globals-only saves roles and passwords, which per-database dumps do not contain.
  • mysqldump --single-transaction gives a consistent snapshot of InnoDB tables without locking them; --routines --triggers --events include stored code that is skipped by default.
  • Careful: the find ... -delete line permanently removes every file under $DEST older than KEEP_DAYS. Run the script once with -print only (remove -delete) if you are unsure what it will match.

Run it by hand once: sudo /usr/local/bin/db-dump.sh. Fix any authentication error now, while you can see it.

Step 3: the service and the timer

Create /etc/systemd/system/db-dump.service:

[Unit]
Description=Dump PostgreSQL and MySQL databases
After=postgresql.service mysql.service mariadb.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/db-dump.sh
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7

And /etc/systemd/system/db-dump.timer:

[Unit]
Description=Nightly database dump

[Timer]
OnCalendar=*-*-* 02:30:00
RandomizedDelaySec=15min
Persistent=true

[Install]
WantedBy=timers.target
  • Type=oneshot tells systemd the service runs to completion rather than staying up.
  • OnCalendar=*-*-* 02:30:00 is every day at 02:30; check any expression with systemd-analyze calendar '*-*-* 02:30:00'.
  • Persistent=true runs the job at boot if the machine was off at 02:30.
  • RandomizedDelaySec spreads the start so several hosts do not hit shared storage in the same second.
  • Nice and the IO scheduling settings keep the dump from starving the database it is dumping.

Step 4: enable it and check

Check before you run this

  • Runs with administrator rights. Make sure you understand every line.
sudo systemctl daemon-reload
sudo systemctl enable --now db-dump.timer
systemctl list-timers db-dump.timer
sudo systemctl start db-dump.service
journalctl -u db-dump.service -n 50 --no-pager
ls -lh /var/backups/db/postgresql /var/backups/db/mysql

list-timers shows NEXT and LAST; starting the service (not the timer) runs the dump immediately so you do not have to wait until 02:30. The journal shows every line the script printed, and a non-zero exit marks the unit as failed, which the Server health check script and most monitoring agents pick up through systemctl --failed.

Keep the dumps off the server

Dumps on the same disk as the database protect you from a bad DELETE, not from a dead disk. Add a second timer, or a second line in the script, that pushes /var/backups/db to another machine with rsync, rclone or a restic repository. Then verify the newest file with the verify-latest-backup script and restore one dump a month as described in the restore drill article.

Official documentation

Did this work for you?

No votes yet. Be the first.

If it did not work, the report form below tells the editors what went wrong.

See also

  • 0198 Install PostgreSQL and connect with psql on Debian, Ubuntu and RHEL Install the PostgreSQL server from your distribution or the official PGDG repositories, start it, and open your first psql session as the… Beginner · Step by step · Linux Article Install & connect 15 Oct 2026
  • 0199 psql meta-commands cheat sheet The backslash commands you use every day in psql: connecting, listing databases, tables and roles, describing objects, timing queries, expanded output and… Beginner · Cheat sheet · Any system Article Install & connect · Roles & administration 15 Oct 2026
  • 0200 PostgreSQL roles, privileges and default privileges Create login and group roles, grant access to databases, schemas and tables, and use ALTER DEFAULT PRIVILEGES so tables created later get… Intermediate · Step by step · Linux Article Roles & administration · Security & access 15 Oct 2026
  • 0201 pg_hba.conf and PostgreSQL authentication methods pg_hba.conf decides who may connect from where and how they must prove who they are. Rules are checked top to bottom and… Intermediate · Explains an idea · Linux Article Install & connect · Security & access 15 Oct 2026
  • 0202 Back up and restore PostgreSQL with pg_dump, pg_restore and pg_dumpall Make logical backups of one database with pg_dump, of roles and the whole cluster with pg_dumpall, and restore them with pg_restore or… Beginner · Step by step · Linux Article Backup & restore 15 Oct 2026
  • 0203 Physical backups and point-in-time recovery with pg_basebackup and WAL archiving A base backup plus a continuous archive of WAL files lets you restore a PostgreSQL cluster to any moment after the backup,… Advanced · Explains an idea · Linux Article Backup & restore · Replication & high availability 15 Oct 2026

Repos in the wild

History

Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.

  1. Dominik +5,606 chars

Report a problem

Something wrong, unsafe or copied? Tell the editors.
What is wrong?