Encrypted mailcow backups to Google Drive: schedule, verify and restore

Turn mailcow's manual backup_and_restore.sh into a scheduled, GnuPG-encrypted service with local and Google Drive copies, verification without restoring, and per-component restore from the admin page.

mailcow ships a solid backup script, but it is a command you run by hand. Nothing schedules it, nothing encrypts it, nothing copies it off the server, and restoring means answering its questions on the shell. That works until the day you actually need it, usually at the worst possible moment.

This tutorial walks through turning that script into a service you configure once in System → Backup: scheduled runs with mailcow's own backup_and_restore.sh, GnuPG encryption (AES-256, with integrity protection) before anything touches the disk, a local copy plus an optional Google Drive copy, verification without a restore, and per-component restore from the page. The product is Backup for mailcow; the full reference is in the user guide.

Before you start

  • mailcow-dockerized on Linux with docker compose and systemd.
  • The MediaLab Works mailcow Toolkit 0.7.0 or newer — this module is one of its products, with its own license.
  • Root access on the host to run the Toolkit installer, and outbound HTTPS to medialab.works for the license check.
  • GnuPG on the host. If it is missing: apt install gnupg.
  • Free disk space in the local folder for about two backups — one is built while the previous ones are kept.
  • Optional, for Google Drive: a Google account and an OAuth client of your own in Google Cloud.

Step 1 — Install the module

On the mailcow host, as root, run the Toolkit installer as usual:

sudo ./install.sh --mailcow-dir /opt/mailcow-dockerized --name <instance>

If the Toolkit is already installed, update it from System → Updates (Toolkit) instead. The installer copies data/web/mlw-backup/, installs the host script /usr/local/sbin/mlw-mailcow-backup-<instance>, creates the history table and installs the timer mlw-mailcow-backup-<instance>.timer, which every 5 minutes checks whether a backup is due. Then open System → Backup in the mailcow admin menu.

Step 2 — Activate the license

Open System → Updates (Toolkit) and find the Backup for mailcow card: paste the key and activate it, or start the 30-day trial. From the shell you can do the same with php /web/mlw-toolkit/cli.php activate mailcow-backup <KEY> inside php-fpm-mailcow.

Worth knowing: without a valid license the scheduled backups and Google Drive uploads stop, but verify, restore and delete keep working. Your data is never held hostage.

Step 3 — Choose the local folder

In Settings → Local folder, enter an absolute path on the host, ideally on another disk than mailcow — for example /srv/backup/mailcow or a mounted disk. The folder is created readable by root only. Leave room for about two backups.

Step 4 — Set the encryption passphrase

In Encryption, press Set with generated (recommended). The passphrase is shown once: store it outside the server, in a password manager or on paper. Or choose my own and type one of at least 12 characters twice.

Each backup becomes one .tar.gpg file plus a JSON manifest (components, sizes, SHA-256, passphrase id, mailcow and Toolkit versions). It decrypts with plain gpg too, without mailcow: gpg -d mailcow-…tar.gpg > backup.tar asks for the passphrase and gives back mailcow's usual backup folder.

Step 5 — Schedule, components and retention

Still in Settings:

  • Scheduled backups on or off, with Times in server time (several allowed, e.g. 03:15, 15:15) and Days. A backup missed while the server was off runs once when it is back, and scheduled runs use a low CPU and disk priority.
  • What is backed up: mail (vmail), encryption keys (crypt — needed to read encrypted mail), redis, rspamd, postfix and the database (SQL). All are on by default; pick what you need.
  • Local copies kept and Copies kept on Drive: the newest N stay, older ones are deleted after each backup.
  • Compression threads for mailcow's script.
  • E-mail: on failure only (default), after every backup, or never. An empty address means the Toolkit's notification address.

Press Back up now in the Status card to make the first backup; progress and the log are shown live.

Step 6 — Add Google Drive (optional)

The module uses your own Google OAuth client, so access to your Drive stays on your server:

  1. In the Google Cloud Console, create or pick a project and enable the Google Drive API.
  2. OAuth consent screen: type External, your e-mail as support and developer contact, then Publish app ("In production"). In testing mode Google expires the access after 7 days.
  3. Credentials → Create credentials → OAuth client ID, type Web application, with the redirect URI shown on the page: https://<your mailcow UI host>/mlw-backup/oauth.php.
  4. Paste the Client ID and client secret into Google Drive on the page, Save, then Connect Google Drive and allow access. Google may warn that the app is not verified — it is your own app, continue.
  5. In Settings, turn on Upload every backup to Google Drive and choose the folder name.

The scope is drive.file: the module sees only the files it created, never the rest of your Drive. Uploads go in 32 MiB parts and continue after a network error. Test lists the folder; Disconnect revokes the access and the backups stay on Drive.

Step 7 — Verify without restoring

In Backups, every backup shows where it is (local, Drive or both). Verify decrypts and reads the whole archive — downloading it from Drive if it is not local — without restoring anything. Run it after the first backup and then periodically: it is the cheapest way to find out that a passphrase or an upload went wrong.

Step 8 — Restore per component

Restore… lets you tick the components you need, type RESTORE and confirm. The archive's SHA-256 is checked against its manifest, it is decrypted and its paths are checked, then each component is restored with the steps of mailcow's own restore: the containers concerned are stopped and started again. The database stops the whole mailcow for a few minutes and takes the SQL credentials of the backup's mailcow.conf. What changed in those components after the backup date is lost. No forced mail resync is run; if a mailbox looks wrong afterwards:

docker exec $(docker ps -qf name=dovecot-mailcow) doveadm force-resync -A '*'

Moving to a new server

  1. Install mailcow and the Toolkit, then activate the license (or restore without one).
  2. Encryption → my own: type the old passphrase. On a server that already has one, tick for older backups only — a new passphrase applies to the next backups, and older ones stay restorable.
  3. Google Drive: use the same OAuth client and connect; the old backups appear, because the module sees the files its client created.
  4. Restore… the newest backup, all components.

Tips and troubleshooting

  • Keep the passphrase off the server. Without it a backup cannot be restored on another server. The module's own secrets (passphrase, Google access) survive restoring redis, but a lost server takes them with it.
  • Privacy: backups contain all your mail. They leave the server only encrypted, and only to the Google Drive account you connect; nothing is sent to medialab.works except the license check.
  • Google expires testing-mode apps after 7 days — publish the app as described above.
  • Check the history and logs: the full history of backups, uploads, verifications and restores is on the page and in the mlw_bk_run table; live progress is in data/hooks/phpfpm/.mlw-toolkit/backup/run.json and run.log.
  • By hand, as root: MLW_TK_CONF=/etc/mlw-mailcow-toolkit/<instance>.conf mlw-mailcow-backup-<instance> backup|verify NAME|list.
  • Uninstalling with uninstall.sh keeps the local backups and the ones on Drive.

Wrapping up

Set the folder and passphrase once, pick times and retention, connect Drive if you want an off-site copy, and let the timer do the rest. Verify now and then, and when you need a restore you will be ticking components on a page instead of answering questions on a shell. More in the user guide and the changelog; questions are welcome via contact or the knowledge base.

This post was written with AI assistance from our product documentation or the sources listed and checked automatically before publication.

Read next

Running Emby, Jellyfin or Nextcloud?

Our plugins and apps add Live TV, subtitles, sign-up and security to your own server, with a free trial.