Google Drive to VPS backup

Configure the run, then follow the steps.

Setup wizard

Only upfront values live here. Values discovered later appear in the relevant step.

Required values

rclone remote name
source folder in Google Drive
mounted backup disk root
01
filesystem

Prepare the VPS target

Create the two directories the runtime uses: one for rclone config, one for copied data. The config directory stays writable to rclone so refreshed OAuth token metadata can be saved; the data directory is the backup target.

vps / bash
mkdir -p "/mnt/backupdrive/rclone-config" "/mnt/backupdrive/data"

Make the config and data directories writable by 1000:1000 before running the non-root container.

vps / bash
chown -R 1000:1000 "/mnt/backupdrive/rclone-config" "/mnt/backupdrive/data"
chmod 700 "/mnt/backupdrive/rclone-config"
02
identity

Create the Google OAuth client

Create a dedicated OAuth client instead of relying on rclone's shared bundled credentials. Shared credentials are rate-limited and can produce 429 errors.

  1. Enable the Google Drive API for the Google Cloud project.
  2. Open Google credentials and create an OAuth client ID with application type Desktop app.
  3. Add http://127.0.0.1:53682/ as an authorized redirect URI, including the trailing slash.
  4. Set the OAuth consent screen publishing status to In production before minting the final token. Testing-mode Drive refresh tokens expire after 7 days.
  5. For a one-person setup, authorize through the unverified-app warning with your own Google account. Google documents this as acceptable for personal use by you or a few known users.
03
token

Mint the rclone token

Do the OAuth approval from any workstation with Docker and a browser. The headless VPS does not need browser access.

workstation / bash
docker run --rm -it -p 53682:53682 rclone/rclone authorize "drive" \
  --drive-client-id "YOUR_CLIENT_ID" \
  --drive-client-secret "YOUR_CLIENT_SECRET"

The container may print an xdg-open error because no browser exists inside it. Open the printed http://127.0.0.1:53682/auth?... URL on the workstation, approve access, and copy the full token JSON that rclone prints.

04
remote

Create the rclone remote

Run rclone config on the VPS with the config directory mounted writable. Backup runs keep it writable too, so token refreshes can be persisted without noisy save errors.

vps / bash
docker run --rm -it \
  --user "1000:1000" \
  -v "/mnt/backupdrive/rclone-config:/config/rclone" \
  rclone/rclone config
  1. n new remote, name it gdrive-backup.
  2. Storage type: drive.
  3. Paste client_id and client_secret.
  4. Scope: 2 for read-only Drive access.
  5. service_account_file: blank, Enter.
  6. Edit advanced config: n.
  7. Use auto config / browser: n.
  8. At config_token>, paste the full token JSON from step 03.
  9. Shared Drive / Team Drive: n. Use My Drive when rclone asks about Shared Drive / Team Drive.
  10. Keep this remote: y, then q to quit.

If the session exits before y and q, no config is saved.

05
verification

Verify the remote

Confirm that the remote exists and that the spelling is exactly gdrive-backup. Keep the config mount writable so rclone can save refreshed token metadata.

vps / bash
# should print: gdrive-backup:
docker run --rm \
  --user "1000:1000" \
  -v "/mnt/backupdrive/rclone-config:/config/rclone" \
  rclone/rclone listremotes

# list Drive root to confirm auth and folder spelling
docker run --rm \
  --user "1000:1000" \
  -v "/mnt/backupdrive/rclone-config:/config/rclone" \
  rclone/rclone lsd gdrive-backup:
06
execution

Run the first copy manually

Run one manual copy before scheduling. The generated commands quote the source and destination, so folder names with spaces are safe.

vps / bash / manual test
docker run --rm \
  --user "1000:1000" \
  -v "/mnt/backupdrive/rclone-config:/config/rclone" \
  -v "/mnt/backupdrive/data:/data" \
  rclone/rclone \
  copy "gdrive-backup:Drive Folder" "/data/Drive Folder" \
    --progress --transfers=4 --checkers=8

When it completes, ls -la "/mnt/backupdrive/data/Drive Folder" should show copied files for Drive Folder.

07
monitoring

Create the Healthchecks check

Monitoring is enabled. Create the Healthchecks.io check before installing the script, then paste the check's base ping URL here.

  1. Period: 1 hour.
  2. Grace: 20-30 min.
  3. Integrations: add whichever alert channel will reach the operator.
Paste the base ping URL from Healthchecks.io, then apply it. The script in the next step regenerates with this value.

The generated script will ping /start, the base success URL, and /fail with a log tail.

08
automation

Install the script and cron

Create /mnt/backupdrive/backup.sh after the manual copy succeeds and the Healthchecks URL has been applied in step 07. The script wraps rclone, writes logs, and reports start, success, or failure.

/mnt/backupdrive/backup.sh
#!/usr/bin/env bash
set -u -o pipefail

REMOTE_NAME="gdrive-backup"
REMOTE_FOLDER="Drive Folder"
BASE="/mnt/backupdrive"
HC="https://hc-ping.com/YOUR_UUID"
DOCKER_IMAGE="rclone/rclone"
RUN_AS="1000:1000"
TRANSFERS="4"
CHECKERS="8"

DOCKER_USER_ARGS=()
if [ -n "$RUN_AS" ]; then
  DOCKER_USER_ARGS=(--user "$RUN_AS")
fi

curl -fsS -m 10 --retry 3 "$HC/start" >/dev/null 2>&1 || true

docker run --rm \
  "${DOCKER_USER_ARGS[@]}" \
  -v "$BASE/rclone-config:/config/rclone" \
  -v "$BASE/data:/data" \
  "$DOCKER_IMAGE" \
  copy "$REMOTE_NAME:$REMOTE_FOLDER" "/data/$REMOTE_FOLDER" \
    --log-file=/data/rclone.log \
    --log-level INFO \
    --transfers="$TRANSFERS" \
    --checkers="$CHECKERS"

EXIT=$?
if [ $EXIT -eq 0 ]; then
  curl -fsS -m 10 --retry 3 "$HC" >/dev/null 2>&1 || true
else
  curl -fsS -m 10 --retry 3 \
    --data-raw "$(tail -c 10000 "$BASE/data/rclone.log" 2>/dev/null)" \
    "$HC/fail" >/dev/null 2>&1 || true
fi
exit $EXIT
vps / bash
chmod +x "/mnt/backupdrive/backup.sh"
"/mnt/backupdrive/backup.sh"

Cron starts an ephemeral container. flock -n prevents overlap: if one run is still active, the next scheduled run exits immediately instead of starting a second writer.

schedule
vps / bash
crontab -e

Paste this line into the crontab:

crontab entry
# Google Drive backup, lock-guarded
0 * * * * /usr/bin/flock -n "/mnt/backupdrive/.backup.lock" "/mnt/backupdrive/backup.sh" >> "/mnt/backupdrive/cron.log" 2>&1

A skipped overlapping run sends no ping, but the long run that caused it still pings success when it finishes.

09
rotation

Set log rotation and review failure modes

weekly with rotate 4 keeps bounded history.

vps / bash / install logrotate config
cat > /etc/logrotate.d/gdrive-backup <<'EOF'
/mnt/backupdrive/data/rclone.log /mnt/backupdrive/cron.log {
    weekly
    rotate 4
    compress
    missingok
    notifempty
    copytruncate
}
EOF
vps / bash / verify logrotate
# dry-run: show actions, change nothing
logrotate -d /etc/logrotate.d/gdrive-backup

# force a real rotation now
logrotate -f /etc/logrotate.d/gdrive-backup
Rotate exposed client secrets

OAuth tokens include the client ID and client secret. If the secret has been exposed, delete it in Google Cloud Console, generate a new one, and rewrite the rclone config.

Testing-mode tokens expire

Drive refresh tokens expire after seven days while an external OAuth app is in Testing. For unattended use, move the OAuth consent screen to In production before minting the final token. If you already minted a Testing token, mint a new token and update the rclone remote.

Remote names are exact

gdrive-backup must match in the config, commands, and script. If unsure, listremotes is canonical.

copy is not history

copy adds and updates files but never deletes local files. Add restic, borg, or --backup-dir if point-in-time recovery matters.