rclone
rclone for object and cloud storage: remotes and wrapper backends, copy/sync/move and when each deletes, change detection, filters, S3 and OAuth backend traps, crypt, mount and the VFS cache, serve and rc, bisync, and unattended runs.
rclone
One binary that copies, syncs, mounts, and serves files across some seventy storage backends — and the handful of defaults that decide whether it does so safely.
Overview
rclone is rsync's counterpart for storage you reach through an API rather than a shell: S3 and its many imitators, Google Drive, OneDrive, Dropbox, B2, WebDAV, SFTP, and the local disk. It has no remote agent and no delta-transfer protocol; it lists both sides, decides per object whether the destination is stale, and transfers whole objects in parallel. Everything is addressed as remote:path, where a remote is a named block of configuration.
Remotes come in two kinds. Storage backends talk to a service. Wrapper backends (crypt, union, combine, chunker, compress, alias, hasher) hold no data of their own — they transform or route operations onto another remote, and they stack.
The things that bite, each expanded below:
syncdeletes. It makes the destination identical to the source, removing whatever is not in the source.copynever deletes. Reach forcopyby default andsyncdeliberately, with--dry-runfirst.- There are no rsync-style trailing-slash semantics.
rclone copy src dstandrclone copy src/ dst/do the same thing: the contents ofsrcland indst. rclone obscureis not encryption. Passwords inrclone.confare reversibly obfuscated with a key compiled into every rclone binary. Protect the file itself.- Change detection depends on the backend. The default is size plus modification time, with a hash as tie-breaker — but which hash, and whether modtime exists at all, varies per backend.
mountdefaults to--vfs-cache-mode off, under which files cannot be opened read-write, seeked while writing, or appended to. Most applications needwritesorfull.- Distribution packages are years stale. Debian 13 ships 1.60.1; upstream is 1.75.x.
rsync vs rclone vs the provider's own CLI
| Situation | Use | Why |
|---|---|---|
| Host to host over SSH, POSIX filesystems both ends | rsync |
Delta transfer, hard links, ownership, ACLs, xattrs — none of which rclone attempts |
| Large files that change a little (VM images, append-only database dumps) | rsync |
rclone re-uploads the whole object on any change |
| Local or SFTP to object storage, or cloud to cloud | rclone |
Parallel transfers, multipart uploads, server-side copy, one tool across providers |
| Client-side encryption in front of any storage | rclone (crypt) |
Provider CLIs offer server-side encryption only |
| Consumer drives (Drive, OneDrive, Dropbox) from a server | rclone |
The vendors ship no headless sync client worth the name |
| Mounting a bucket as a filesystem | rclone mount |
With the caveat that object storage is not POSIX; see the VFS section |
| Bucket policy, lifecycle rules, IAM, replication, presigning at scale | Provider CLI (aws, gcloud storage, az) |
rclone moves objects; it does not administer the service |
| A feature released last month | Provider CLI | rclone tracks the common subset, not the newest API surface |
| One sync inside a single cloud, credentials already ambient | Either | Provider CLIs pick up instance roles with no config; rclone needs env_auth set |
Versions and Installation
rclone ships a minor release roughly every two months and backends change with their providers' APIs, so the version matters more than for most CLI tools. Check before trusting any flag in this sheet:
rclone version # Version, OS, Go version, build tags
rclone selfupdate --check # Compare installed against latest stable and beta
Debian 13 packages 1.60.1 (released November 2022). Features that package lacks, by the release that introduced them:
| Since | Feature |
|---|---|
| 1.62 | --max-delete-size |
| 1.63 | Uploads to local/FTP/SFTP go to a .partial temp name and are renamed on completion; --inplace to opt out |
| 1.64 | Multi-thread uploads (previously downloads only); rclone config redacted |
| 1.65 | rclone serve s3, rclone nfsmount / serve nfs, --metadata-mapper |
| 1.66 | bisync rework: --recover, --resilient retries, --conflict-resolve, --compare |
| 1.68 | rclone config encryption set/remove/check; S3 backend moved to AWS SDK v2 |
| 1.69 | --links as a global flag, --vfs-links |
| 1.70 | --list-cutoff, --max-buffer-memory, --hash-filter, rclone convmv |
| 1.71 | bisync promoted from beta to stable; --log-file-max-size log rotation |
| 1.72 | zstd mode in compress; archive backend |
Install from upstream instead:
# Official .deb (also .rpm, .zip) from downloads.rclone.org
curl -LO https://downloads.rclone.org/rclone-current-linux-amd64.deb
sudo dpkg -i rclone-current-linux-amd64.deb
# Upstream's install script - fetches the current zip, installs binary and man page
sudo -v ; curl https://rclone.org/install.sh | sudo bash
# Update in place. Match the format you installed with, or dpkg's idea of the
# installed version drifts from the binary on disk.
sudo rclone selfupdate --package deb
sudo rclone selfupdate --package deb --version 1.75.1 # Pin an exact version
sudo rclone selfupdate --package deb --beta # Track the beta channel
# Container image maintained by the rclone developers
docker run --rm rclone/rclone:latest version
Do not selfupdate a binary that apt owns in zip mode — the next package upgrade will silently downgrade it. The snap is strictly confined and cannot mount. On macOS the Homebrew build cannot mount: the command is listed in rclone help but exits 1 with rclone mount is not supported on MacOS when rclone is installed via Homebrew. Use rclone nfsmount, which needs no FUSE, or install the upstream binary.
The Model: Remotes and Paths
Key Concepts
- A remote is a named section of
rclone.conf: a backendtypeplus its options.s3prod:is a remote;s3prod:bucket/prefixis a path on it. - A path with no colon is local.
/srv/dataand./dataare the local backend. The remote name is only searched for up to the first/, so a local file with a colon in its name needs./in front. - On bucket-based backends the first path element is the bucket:
s3prod:lists buckets,s3prod:bucketlists a bucket. - Directories are a convention, not an object. Bucket backends cannot hold an empty directory;
rclone mkdir s3prod:bucket/emptywarns and does nothing. - Wrapper backends take a
remote =that points at another remote (or a local path), and can be stacked to any depth.
flowchart TD
CMD["rclone sync /srv/data vault:"] --> CRYPT["vault: (crypt)<br/>encrypts names and content"]
CRYPT -->|"remote = pool:enc"| UNION["pool: (union)<br/>merges upstreams, picks one for writes"]
UNION -->|"upstream 1 (writable)"| S3A["s3a:backup-bucket<br/>(storage backend: s3)"]
UNION -->|"upstream 2 (:ro)"| LOCAL["/srv/archive<br/>(storage backend: local)"]
S3A --> API[("S3 API")]
LOCAL --> DISK[("Local disk")]
Each layer sees only the one beneath it. crypt hands ciphertext to union, which neither knows nor cares; union picks an upstream by policy. The practical consequence is that features are the intersection of the stack: crypt has no hashes, so rclone check against a crypt remote falls back to sizes unless you use cryptcheck.
Directory Semantics — Not rsync's
rsync treats src and src/ differently. rclone does not: the source directory's contents go into the destination directory, always.
rclone copy /srv/data remote:backup # remote:backup/a.txt, remote:backup/sub/b.txt
rclone copy /srv/data/ remote:backup/ # Identical result
# To get remote:backup/data/..., say so:
rclone copy /srv/data remote:backup/data
The source may be a single file; the destination is still a directory:
rclone copy /srv/data/a.txt remote:backup # remote:backup/a.txt
rclone copy /srv/data/a.txt remote:backup/a.txt # remote:backup/a.txt/a.txt <- a directory named a.txt
rclone copyto /srv/data/a.txt remote:backup/b.txt # copyto names the destination object itself
copyto and moveto are the forms that rename. Missing destination directories (and, on most backends, buckets) are created without asking.
Configuration
The config file
rclone config # Interactive wizard: new, edit, delete remotes
rclone config file # Which file is in use
rclone config paths # Config file, cache dir, temp dir
rclone listremotes --long # Remote names with backend types
rclone config show s3prod # One remote, secrets in clear (obscured values as stored)
rclone config redacted s3prod # Same, with secrets replaced by XXX - safe to paste into a ticket
# Non-interactive create / update. Password-type options given in clear are
# obscured as they are written; --no-obscure if the value is already obscured.
rclone config create s3prod s3 provider=AWS env_auth=true region=eu-west-2
rclone config update s3prod storage_class=STANDARD_IA
rclone config delete s3prod
The default location is ~/.config/rclone/rclone.conf ($XDG_CONFIG_HOME/rclone/rclone.conf if set); override with --config or RCLONE_CONFIG. For system services put it somewhere like /etc/rclone/rclone.conf, mode 600, and always pass --config. OAuth backends rewrite the config file when they refresh a token — by writing a temporary file beside it and renaming — so the directory must be writable by the user rclone runs as, not just the file. The same goes for rclone config create, which prints the new section and exits 0 even when that save fails — confirm with rclone listremotes afterwards.
Environment remotes and connection strings
A remote does not have to exist in the file. Every option can come from the environment, which is how to run rclone in containers and CI without shipping a config:
# RCLONE_CONFIG_<NAME>_<OPTION> defines remote "ci" entirely in the environment
export RCLONE_CONFIG_CI_TYPE=s3
export RCLONE_CONFIG_CI_PROVIDER=AWS
export RCLONE_CONFIG_CI_ENV_AUTH=true
export RCLONE_CONFIG_CI_REGION=eu-west-2
rclone lsf ci:artefacts-bucket # "ci:" also appears in `rclone listremotes`
# Any global flag has an env equivalent: --transfers 16 == RCLONE_TRANSFERS=16
# Any backend flag too: --s3-chunk-size 64M == RCLONE_S3_CHUNK_SIZE=64M
# Connection string: an on-the-fly remote, no config at all
rclone lsf ":s3,provider=AWS,env_auth=true,region=eu-west-2:artefacts-bucket"
# Without known_hosts_file the sftp backend validates no host key at all
rclone lsf ":sftp,host=files.example.com,user=deploy,known_hosts_file=~/.ssh/known_hosts:/srv/out"
# Override options on an existing remote for one command
rclone copy ./out "s3prod,no_check_bucket=true:artefacts-bucket/out"
Quote values containing : or ,. A bare option name means =true. Precedence, first match wins: connection-string parameters, command-line flags, RCLONE_CONFIG_<NAME>_*, backend env vars (RCLONE_S3_*), the config file, built-in defaults. -vv logs each value picked up from the environment, which settles most "why is it ignoring my flag" questions.
obscure is not encryption
Password-type options (crypt passwords, SFTP/WebDAV pass) are stored obscured. That is AES-CTR with a key compiled into rclone; it stops a password being read over your shoulder and nothing else.
$ rclone obscure 'hunter2'
VG4Na0aJgQ2nTfWfVRMNjupLlPihBXM
$ rclone obscure 'hunter2' # Random IV: different output every time, both valid
z1ax2LzJ3NgBh_MSTI_T47vt01pYZ0U
$ rclone reveal VG4Na0aJgQ2nTfWfVRMNjupLlPihBXM
hunter2
Anyone who can read rclone.conf has every credential in it. Options that expect a password on the command line or in a connection string want the obscured form: pass=$(rclone obscure "$PW").
Encrypting the config file
rclone config encryption set # Prompts for a passphrase; encrypts the whole file
rclone config encryption check # Non-zero exit if not encrypted
rclone config encryption remove
# Unattended: supply the passphrase from a secret store, never from argv
export RCLONE_PASSWORD_COMMAND="pass show infra/rclone-config"
rclone listremotes # Runs the command, decrypts in memory
RCLONE_CONFIG_PASS=... rclone listremotes # Alternative: the passphrase itself in the env
rclone --ask-password=false listremotes # Fail rather than prompt (use in services)
An encrypted file carries a comment line and then an RCLONE_ENCRYPT_V0: marker at the top. --password-command is the right hook for systemd credentials, pass, op read, or a secrets-manager fetch. On packages older than 1.68, set the passphrase from the rclone config menu instead of the encryption subcommand.
copy, sync, move
What each one deletes
| Command | Transfers | Deletes at destination | Deletes at source |
|---|---|---|---|
copy src dst |
New and changed files | Nothing | Nothing |
sync src dst |
New and changed files | Everything not in src, including empty directories |
Nothing |
move src dst |
New and changed files | Nothing | Files after successful transfer; empty directories remain unless --delete-empty-src-dirs |
copyto / moveto |
One file or directory to an exact destination name | Nothing | moveto: the source |
delete path |
— | Files only (honours filters) | — |
purge path |
— | The directory and all contents (ignores filters) | — |
rclone copy /srv/data remote:backup # Additive. The safe default.
rclone sync /srv/data remote:backup # Mirror. Destination loses anything not in source.
rclone move /srv/outbox remote:inbox --delete-empty-src-dirs
# -P for interactive progress, -v to log each file, -n to rehearse
rclone sync /srv/data remote:backup -n -v
sync deletes after transferring by default (--delete-after), and skips the delete phase entirely if there were I/O errors — so a half-failed run does not also prune. --delete-during saves memory on very large trees; --delete-before frees space first at the cost of a window with neither copy.
Two reflexes worth building: swap the arguments in your head before every sync (an empty or unmounted source is a valid source — the result is an empty destination), and never point sync at a bucket root shared with other data.
Renames and incremental copies
# Detect renames by hash and move server-side instead of delete + re-upload
rclone sync /srv/data remote:backup --track-renames
# Only upload what is absent from a reference copy (the reference must be on the same remote as dst)
rclone copy /srv/data remote:incr/2026-10-05 --compare-dest remote:full/2026-10-01
# Move between two remotes of the same backend without round-tripping through this host
rclone copy s3a:bucket-a s3b:bucket-b --server-side-across-configs
--track-renames needs server-side move or copy on the destination and, with the default hash strategy, a hash both sides share — so it does not work into crypt unless you switch to --track-renames-strategy modtime,leaf.
Safety Rails
rclone sync src dst --dry-run # -n: log what would happen, change nothing
rclone sync src dst --interactive # -i: confirm each delete/overwrite (y/n/s/!/q)
rclone sync src dst --max-delete 100 # Stop once 100 files have been deleted
rclone sync src dst --max-delete-size 10G # ...or once 10 GiB has been deleted
rclone sync src dst --backup-dir remote:trash/2026-10-05 # Move, don't delete or overwrite
rclone sync src dst --suffix .bak --exclude '*.bak' # Keep the old version alongside; unexcluded, the next sync renames or deletes it
rclone copy src dst --immutable # Refuse to modify existing files; new files only
rclone copy src dst --ignore-existing # Skip anything already present, changed or not
--dry-run output is the plan, in the log at NOTICE level:
NOTICE: extra.txt: Skipped delete as --dry-run is set (size 6)
NOTICE: sub/old.txt: Skipped delete as --dry-run is set (size 2)
NOTICE: emptyd: Skipped remove directory as --dry-run is set
Deleted: 2 (files), 1 (dirs), 8 B (freed)
--max-delete Nis a brake, not a pre-flight check. rclone deletes up to N files, then fails with exit code 7 — the first N are gone. It bounds the damage of a bad run; it does not prevent it. Pair it with--dry-runin anything that matters.--backup-dirmust be on the same remote as the destination and must not overlap it (destination and parameter to --backup-dir mustn't overlap). Deleted and overwritten files are moved there with their hierarchy intact, which makes a dated--backup-dira cheap poor-man's versioning scheme on backends without it.--immutablefails loudly when an existing file differs (Source and destination exist but do not match: immutable file modified, exit 6) and leaves the destination untouched. It is the right flag for append-only archives and for catching ransomware-style rewrites of a backup source.- Without
--inplace, uploads to local, FTP, and SFTP destinations are written toname.<random>.partialand renamed on completion, so an interrupted run leaves no truncated file under the real name.
How Changes Are Detected
rclone decides per file whether the destination is up to date. The default is size, then modification time, then hash as a tie-breaker — and it never reads file content unless a hash is needed.
flowchart TD
A["File exists at destination?"] -->|"No"| X["Transfer"]
A -->|"Yes"| B{"Sizes equal?"}
B -->|"No"| X
B -->|"Yes"| M{"Mode"}
M -->|"--size-only"| S["Skip"]
M -->|"--checksum"| H{"Common hash type?"}
H -->|"Yes, equal"| S
H -->|"Yes, differ"| X
H -->|"None"| S
M -->|"default"| T{"Modtimes within<br/>--modify-window?"}
T -->|"Yes"| S
T -->|"No"| H2{"Common hash,<br/>and equal?"}
H2 -->|"Yes"| U["Update destination modtime,<br/>no transfer"]
H2 -->|"No, or no common hash"| X
Three consequences fall straight out of that flow:
- Same size, same modtime, different content is invisible by default. rclone logs
Size and modification time the sameand skips. Only--checksum(orrclone checkafterwards) catches it. - A
touchdoes not cause a re-upload where both sides share a hash: rclone compares hashes, finds them equal, and logsUpdated modification time in destination. Dropbox is the exception — its modtime can only be set by re-uploading, so atouchre-copies the file. --checksumwith no common hash degrades to size-only, with a single NOTICE in the log and a zero exit. Know your backends.
rclone sync src dst # Default: size + modtime
rclone sync src dst --checksum # -c: size + hash, modtime ignored
rclone sync src dst --size-only # Size alone - fast, misses same-size edits
rclone sync src dst --update # -u: skip files that are newer at the destination
rclone sync src dst --ignore-times # -I: transfer everything regardless
rclone sync src dst --modify-window 2s # Tolerance for coarse-timestamp filesystems (FAT, some SFTP)
rclone sync src dst --no-update-modtime # Leave destination modtime alone when content matches
Per-backend hash and modtime support
| Backend | Hashes | ModTime | Notes |
|---|---|---|---|
| Local | All (computed on demand) | Read/write | Hashing reads the whole file every run |
| S3 and compatibles | MD5 | Read/write, in object metadata | Reading modtime costs a HEAD per object |
| Backblaze B2 | SHA1 | Read/write | |
| Google Cloud Storage | MD5 | Read/write | |
| Azure Blob | MD5 | Read/write | |
| Google Drive | MD5, SHA1, SHA256 | Read/write | Google Docs have no size or hash |
| OneDrive | QuickXorHash | Read/write | |
| Dropbox | Dropbox content hash | Read/write | Setting a modtime means re-uploading the file |
| SFTP | MD5, SHA1 | Read/write | Hashes need shell access and md5sum/sha1sum on the server |
| WebDAV | SHA1 (server-dependent) | Read/write (server-dependent) | |
| FTP, HTTP, SMB | None | Varies | --checksum is size-only here |
crypt |
None | From the wrapped remote | Use cryptcheck |
The local backend can compute the provider-specific hashes (dropbox, quickxor), so local-to-Dropbox and local-to-OneDrive checksums work. Two different clouds usually share no hash at all — S3 (MD5) to B2 (SHA1) falls back to size and modtime. rclone backend features remote: prints what a remote really supports, including Hashes and timestamp Precision; rclone hashsum md5 remote:path shows what it reports.
Verifying: check and cryptcheck
A zero exit from copy means every transfer was verified against the backend's hash where one exists. check is the independent audit.
rclone check src dst # Sizes and hashes, both directions; no data changed
rclone check src dst --one-way # Only "is everything in src present and equal in dst"
rclone check src dst --size-only # Skip hashes
rclone check src dst --download # No common hash? Download both sides and compare bytes
rclone check src dst --combined report.txt # One line per file: = equal, + src only, - dst only, * differ, ! error
rclone check src dst --missing-on-dst missing.txt --differ differ.txt
rclone cryptcheck /srv/data vault: # Check plaintext against a crypt remote
$ rclone check src dst --combined -
+ c.txt
- d.txt
= a.txt
= b.txt
NOTICE: Failed to check with 2 errors: last error was: 2 differences found
check exits 1 on any difference, so it gates a pipeline as-is. Against a crypt remote plain check logs No common hash found - not using a hash for checks and compares sizes only; cryptcheck re-encrypts each local file with the nonce read from the remote object and compares that against the underlying remote's hash — a real integrity check without downloading the data.
Filtering
Key Concepts
- Rules are processed in order; the first match wins. A file matching no rule is included.
--includeimplies a final--exclude *. One--include '*.log'transfers only logs.- Do not mix
--includeand--exclude— rclone logs that the parse order is indeterminate. Use ordered--filter '+ …'/--filter '- …'rules or a--filter-fromfile. *matches within one path segment;**crosses/.?,[a-z],{a,b}work;{{regexp}}embeds a Go regular expression.- A leading
/anchors to the root of the transfer (not the filesystem). Without it, the pattern matches at any depth. - A trailing
/makes a directory rule, anddir/meansdir/**. - Filters apply to both sides. An excluded file already at the destination is left alone by
syncunless you add--delete-excluded.
rclone copy src dst --exclude '*.log' # No .log files anywhere
rclone copy src dst --exclude 'logs/' # No directory called logs at any depth
rclone copy src dst --exclude '/logs/' # Only the top-level logs directory
rclone copy src dst --include '*.{md,py}' # Only these extensions, at any depth
rclone copy src dst --include '/docs/**' # Only the top-level docs tree
rclone copy src dst --exclude-if-present .nobackup # Skip any directory containing this file
# Keep one file out of an excluded class: the + rule must come FIRST
rclone copy src dst --filter '+ logs/important.log' --filter '- *.log'
# Age and size filters (independent of the pattern rules, applied as well)
rclone copy src dst --max-age 24h # Modified in the last day
rclone delete dst --min-age 30d # Prune files older than 30 days
rclone copy src dst --min-size 1M --max-size 5G
rclone copy src dst --max-depth 1 # Top level only
# An exact list of paths, relative to src
rclone copy src dst --files-from list.txt
rclone lsf -R src --filter-from rules.txt # Test a rule file harmlessly
rclone lsf src --exclude '*.log' --dump filters # Show the regexps the rules compiled to
A --filter-from file, one rule per line:
# keep markdown under /docs, drop the rest of /docs, drop logs everywhere
+ /docs/*.md
- /docs/**
- *.log
+ *
Where it differs from rsync
| rsync | rclone | |
|---|---|---|
--include on its own |
No effect without an --exclude |
Excludes everything else |
| Including a deep file | Every parent directory must be included too (+ */) |
Parents are implied; --include '*.md' finds docs/readme.md |
| Anchor | Leading / is the transfer root |
Same |
** |
Crosses / |
Same |
| Per-directory rule files | .rsync-filter via -F |
None; --exclude-if-present is the nearest thing |
| Regular expressions | No | {{…}} |
| Deleting excluded files at the destination | --delete-excluded |
--delete-excluded |
A bare name such as --include docs |
Matches a file or directory | Matches files only; use docs/ or /docs/** |
Always rehearse filters with rclone lsf -R or --dry-run before letting them near sync or delete.
Throughput and Rate Limits
Key Concepts
--transfers(default 4) is the number of files in flight;--checkers(default 8) is the number of concurrent comparisons and listings. Many small files want both raised; a rate-limited API wants both lowered.- Multi-thread transfers split one large file into streams: files over
--multi-thread-cutoff(256Mi) use--multi-thread-streams(4) of--multi-thread-chunk-size(64Mi). Total connections are roughly transfers × streams. - Memory scales with
--buffer-size(16Mi) × transfers, plus the backend's own upload chunks × concurrency × transfers.--max-buffer-memorycaps the pool. --fast-listuses one recursive listing instead of one call per directory, on backends that support it (the bucket ones, and Drive). Fewer API calls, more memory — upstream's rule of thumb is about 1 KiB per object.
# Many small files to object storage
rclone sync /srv/data s3prod:bucket --transfers 32 --checkers 64 --fast-list
# A few huge files
rclone copy /srv/images s3prod:bucket --transfers 2 --multi-thread-streams 8 \
--s3-chunk-size 64M --s3-upload-concurrency 8
# Top up a huge destination with a handful of new files: look each one up, skip the listing
rclone copy /srv/new s3prod:bucket/archive --no-traverse --max-age 24h
# Bandwidth: flat, asymmetric (up:down), or a timetable
rclone sync src dst --bwlimit 10M
rclone sync src dst --bwlimit 10M:1M
rclone sync src dst --bwlimit "08:00,512k 12:00,10M 13:00,512k 18:00,30M 23:00,off"
rclone sync src dst --bwlimit "Mon-00:00,512 Fri-23:59,10M Sat-10:00,1M Sun-20:00,off"
# API rate: transactions per second across the whole process
rclone sync src dst --tpslimit 10 --tpslimit-burst 10
# Bound a run by time or volume (exit codes 10 and 8 respectively)
rclone sync src dst --max-duration 6h --max-transfer 500G --cutoff-mode SOFT
rclone sync src dst --check-first --order-by size,desc # Finish checking before any transfer; biggest first
--bwlimit is bytes per second with a binary suffix — 10M is 10 MiB/s, around 84 Mbit/s — and a bare number is KiB/s. --bwlimit and --tpslimit solve different problems: one protects a link, the other protects you from a provider's 429. Retries are layered: --low-level-retries (10) per operation, --retries (3) for the whole run, with --retries-sleep between attempts.
S3 Specifics
Setup and provider quirks
[s3prod]
type = s3
provider = AWS
env_auth = true
region = eu-west-2
[r2]
type = s3
provider = Cloudflare
access_key_id = ...
secret_access_key = ...
endpoint = https://<account-id>.r2.cloudflarestorage.com
no_check_bucket = true
provideris not cosmetic. It selects a table of quirks — list version, multipart ETag handling, path style, chunked encoding — for each of the dozens of S3-alikes. Set it to the real provider; useOtheronly as a last resort.env_auth = truemakes rclone use the standard AWS chain (environment, shared credentials file,AWS_PROFILE, instance or task role). The default isfalse, in which case only keys in the config are used and an instance role is ignored.no_check_bucket: before the first upload rclone tries to create the bucket (aPUT /bucket). With credentials that lackCreateBucket— scoped IAM policies, R2 object-level tokens — that fails.--s3-no-check-bucketskips it; uploads to a bucket that does not exist then fail withNoSuchBucket.- Minimum IAM for
sync:ListBucket,GetObject,PutObject,PutObjectAcl,DeleteObject;ListAllMyBucketsonly if you list buckets.
Cost: what each mode asks the API
Object modtime lives in user metadata, which listings do not return. So the default comparison costs one HEAD per object on top of the listing — on a million-object bucket that is a million billable requests per sync.
| Flags | Extra requests | Trade-off |
|---|---|---|
| (default) | HEAD per object |
Accurate modtimes |
--checksum |
None — the MD5 is in the listing | Reads every local file to hash it; the best choice S3-to-S3 |
--size-only |
None | Misses same-size changes |
--update --use-server-modtime |
None | Uses upload time as modtime; misses files whose mtime is older than their upload |
--fast-list |
Fewer LIST calls (one per 1,000 objects rather than one per directory) |
Memory |
rclone sync /srv/data s3prod:bucket --checksum --fast-list # The cheap, accurate default for S3
Hashes, multipart, storage classes
rclone copy big.img s3prod:bucket --s3-upload-cutoff 200M --s3-chunk-size 64M --s3-upload-concurrency 8
rclone copy /srv/cold s3prod:bucket/cold --s3-storage-class GLACIER_IR
rclone copy /srv/data s3prod:bucket --s3-server-side-encryption aws:kms --s3-sse-kms-key-id <key-arn>
rclone backend restore s3prod:bucket/cold -o priority=Standard -o lifetime=7 # Thaw Glacier objects
rclone backend restore-status s3prod:bucket/cold
rclone settier STANDARD_IA s3prod:bucket/old # Change class in place
Files above --s3-upload-cutoff (200Mi) are uploaded in --s3-chunk-size (5Mi) parts, --s3-upload-concurrency (4) at a time. A multipart ETag is not an MD5, so rclone stores the real one in X-Amz-Meta-Md5chksum and rclone md5sum still works on objects it uploaded. Multipart objects written by other tools have no usable hash, and --checksum treats them as size-only. Objects in Glacier classes list normally but fail to open until restored.
Versions, cleanup, server-side copy
rclone backend versioning s3prod:bucket # "Enabled" / "Suspended" / "Unversioned"
rclone lsf --s3-versions s3prod:bucket # Old versions appear with a timestamp in the name
rclone copy --s3-version-at 2026-10-01 s3prod:bucket /srv/restore # The bucket as it was
rclone backend cleanup-hidden s3prod:bucket # Delete all non-current versions
rclone backend list-multipart-uploads s3prod:bucket # Abandoned uploads still bill for storage
rclone cleanup s3prod:bucket # Abort multipart uploads older than 24h
rclone copy s3prod:bucket-a/data s3prod:bucket-b/data # Same remote: server-side, no data through this host
rclone copy s3a:x s3b:y --server-side-across-configs # Different remotes, same provider and credentials reach
On a versioned bucket sync and delete only add delete markers — the storage bill does not drop until versions are expired by a lifecycle rule or cleanup-hidden. A server-side copy logs Copied (server-side copy); anything else means the bytes went through the machine running rclone. S3 has no rename, so move within a bucket is copy-then-delete.
OAuth Backends: Drive, OneDrive, Dropbox
Setup, headless auth, and tokens
rclone config runs the OAuth dance through a local web server on 127.0.0.1:53682. On a server with no browser there are three ways through:
# 1. rclone authorize: on the headless box answer N to "Use web browser to automatically
# authenticate?"; it prints a command to run on any machine with a browser and rclone:
rclone authorize "drive" # Opens the browser, prints a token blob
rclone authorize "drive" "<client_id>" "<client_secret>" # With your own client ID
# Paste the blob back at the config_token> prompt. Use the same rclone version both ends.
# 2. SSH tunnel: forward the callback port, then answer Y and open the printed URL locally
ssh -L localhost:53682:localhost:53682 user@headless-host
# 3. Configure on a workstation and copy the file across
rclone config file # On each side, to find the path
The remote's token entry holds an access token, a refresh token, and an expiry. rclone refreshes the access token as needed and writes the new one back to the config file — if it cannot save (a read-only config directory, an environment-only remote) it logs Failed to save config after 10 tries, carries on with the token in memory, and has to refresh again on every run. When the refresh token itself is revoked or expires:
rclone config reconnect gdrive: # Re-run the OAuth flow for an existing remote
rclone config userinfo gdrive: # Who am I authenticated as
rclone about gdrive: # Quota - a cheap "is the token alive" probe for monitoring
Use your own client ID. rclone's built-in IDs are shared by every user and rate-limited collectively; for Google Drive upstream states the shared ID is being retired during 2026, so treat this as mandatory. Set client_id and client_secret on the remote.
Google Drive
- Consent screen in "Testing" expires grants after a week. Publish the app (it can stay unverified for personal use) or you will be re-authorising every seven days.
- Duplicates are legal. Drive identifies files by ID, so a directory can hold two files with one name, and interrupted uploads create them.
synclogsDuplicate object found in destination - ignoring, replaces one of the pair once, and then reports nothing to transfer;rclone dedupefixes it. - Google Docs are not files. Native Docs, Sheets, and Slides have no size (
-1in listings) and no hash; rclone exports them on download asdocx,xlsx,pptx,svg(--drive-export-formats). They sync, but cannot be verified, and read as zero bytes through a mount.--drive-skip-gdocshides them. - Shared drives are separate roots: set
team_driveto the drive ID (the wizard offers a list).rclone backend drives gdrive:enumerates them, and with-o configemits a ready-madecombineremote spanning all of them. "Shared with me" needs--drive-shared-with-me. - Quotas: roughly 750 GiB uploaded and 10 TiB downloaded per day per user, undocumented by Google.
--drive-stop-on-upload-limitturns the resulting errors into a clean stop instead of hours of retries. Per-file overhead holds throughput to about two files a second regardless of bandwidth; stay under ten transactions a second (--tpslimit 10). - Deletes go to the trash by default (
--drive-use-trash);rclone cleanup gdrive:empties it.scope = drive.filerestricts rclone to files it created. For unattended Workspace use prefer a service account (service_account_file, optionally--drive-impersonate user@example.com) over a user token.
rclone dedupe --dedupe-mode newest gdrive:path # Keep the newest of each set; default mode is interactive
rclone backend drives gdrive: -o config # Print config for every shared drive plus a combine remote
OneDrive and SharePoint
- SharePoint rewrites Office files on upload, changing size and hash, so every sync re-copies them and
checkfails. The documented workaround is--ignore-checksum --ignore-sizefor those libraries. - Limits: 250 GiB per file; the full path must be under 400 characters, which
cryptfilename encryption reaches quickly. - Versions eat quota on OneDrive for Business — every overwrite keeps the old copy.
rclone cleanup remote:pathremoves old versions;--onedrive-no-versionsremoves them as it goes. Neither works on OneDrive Personal. - Throttling on SharePoint: use your own client ID and set
--user-agent "ISV|rclone.org|rclone/v1.75.1"as Microsoft's guidance asks. An Entra client secret has an expiry — diarise it. --fast-listdoes nothing unless--onedrive-deltais set (off by default), and delta listing always walks from the drive root.invalid_grantafter an MFA or conditional-access change means reconnect.
Dropbox
- Case-insensitive (as is OneDrive):
Report.txtandreport.txtcannot coexist. Syncing both from Linux uploads one, skips the other with only aDuplicate object found in source - ignoringNOTICE, and still exits 0. - Batch mode matters. Uploads are committed in batches (
--dropbox-batch-mode syncby default); with itoff, parallel uploads hittoo_many_requests. Raise--transferswith batching on rather than turning it off. - Paper documents are exported on download (
html,mdby default) and cannot be re-imported. - Team folders live above the user root: use a leading slash —
dropbox:/TeamFolder. Own-app setup is through the Dropbox App Console with at leastfiles.metadata.write,files.content.write,files.content.read,sharing.write, andaccount_info.read.
Wrapper Backends
crypt
[vault]
type = crypt
remote = s3prod:backup-bucket/enc
password = <obscured>
password2 = <obscured>
filename_encryption = standard
directory_name_encryption = true
rclone config create vault crypt remote=s3prod:backup-bucket/enc \
password="$PW" password2="$SALT" # Stored obscured, not encrypted
rclone copy /srv/data vault: # Encrypts on the way up
rclone lsf vault: # Plaintext names
rclone lsf s3prod:backup-bucket/enc # mbcg9tah8s84vek27gngi2h714 ...
rclone cryptdecode vault: mbcg9tah8s84vek27gngi2h714 # Ciphertext name -> plaintext
rclone cryptdecode --reverse vault: report.txt # Plaintext -> ciphertext name
rclone cryptcheck /srv/data vault:
- Content is NaCl SecretBox (XSalsa20 + Poly1305) in 64 KiB chunks: a 32-byte header plus 16 bytes per chunk. A 15-byte file is 63 bytes on the remote.
- What leaks: file sizes (to within the overhead), modification times, the directory structure, and the number of files.
filename_encryption = offleaks names as well. - The keys are the two passwords, not the config file.
password2is the salt; both are needed. Record them in a password manager — a crypt remote can be recreated on any machine from those two strings, and cannot be recovered without them. - Standard filename encryption limits names to about 143 characters and lengthens paths; watch backends with path limits.
crypthas no hashes, so--checksumand hash-based--track-renamesdo not work through it.- Point
remoteat a subdirectory, never a bare bucket root you also use unencrypted.
alias, union, combine
[backup]
type = alias
remote = s3prod:company-backups/host01
[pool]
type = union
upstreams = s3a:bucket-a /srv/archive:ro
create_policy = epmfs
[all]
type = combine
upstreams = docs=gdrive:Documents media=s3prod:media-bucket
aliasis a named shortcut —backup:dailyinstead of the full path — and the cheapest way to make scripts independent of where data lives.unionoverlays upstreams into one tree, mergerfs-style. Tag upstreams:ro(read-only) or:nc(no create). Policies default toaction_policy = epall,create_policy = epmfs,search_policy = ff: a name present in several upstreams reads from the first one listed, and new files go to a writable upstream.combinemounts each upstream under its own top-level directory (all:docs/…,all:media/…). Nothing is merged, so there is no ambiguity about where a write lands.
chunker, compress, hasher
rclone config create chunked chunker remote=dropbox:big chunk_size=1G
rclone config create packed compress remote=s3prod:logs mode=gzip level=6
chunkersplits files abovechunk_size(default 2Gi) intoname.rclone_chunk.001,.002, … plus a small metadata object under the original name. It exists for backends with a per-file size limit. Readers that bypass rclone see only the pieces.compressis marked experimental upstream. Setlevelexplicitly and in range (gzip 1–9, zstd 1–4): on 1.75.1 acompressremote created without one behaves aslevel=0, which stores gzip output uncompressed, and zstd hangs at 0 or at any level outside 1–4. Each file becomes a data object plus a.jsonsidecar, so object counts double.hashercaches or adds checksums for backends that lack them (SFTP without shell access, FTP), making--checksumusable there.
mount and the VFS
Prerequisites
| Platform | Requirement |
|---|---|
| Linux | fuse3 (provides fusermount3); for --allow-other as non-root, user_allow_other in /etc/fuse.conf |
| macOS | macFUSE (kernel extension) or FUSE-T (kext-free, NFS-based) — or rclone nfsmount, which needs neither |
| Containers | /dev/fuse and CAP_SYS_ADMIN, or run the mount on the host |
rclone mount s3prod:bucket /mnt/bucket --vfs-cache-mode writes # Foreground; Ctrl-C unmounts
rclone mount s3prod:bucket /mnt/bucket --vfs-cache-mode full --daemon # Background
fusermount3 -u /mnt/bucket # Unmount (Linux)
umount /mnt/bucket # Unmount (macOS, or nfsmount)
rclone nfsmount s3prod:bucket /mnt/bucket --vfs-cache-mode writes # No FUSE: serves NFS to itself and mounts it
# nfsmount without --vfs-cache-mode is read-only; on Linux it also needs root and nfs-common (mount.nfs)
VFS cache modes
The default mode is off, and it is the single most common reason "the mount is broken".
| Mode | Reads | Writes | What fails |
|---|---|---|---|
off (default) |
Streamed from the remote | Streamed to the remote | Open for read+write; seek while writing; append; modifying an existing file without truncating; failed uploads are not retried |
minimal |
Streamed | Read+write opens buffered on disk | Seek on write-only handles; write-only open of an existing file without O_TRUNC (touch, truncate, rsync --inplace) |
writes |
Streamed | All writes buffered on disk, uploaded on close | Nothing, except a shrinking truncate (see below) — normal filesystem operations work; uploads retry |
full |
Cached on disk as sparse files, ranges fetched on demand | As writes |
Nothing, except a shrinking truncate (see below); needs the most disk |
rclone mount remote:path /mnt/x \
--vfs-cache-mode full \
--cache-dir /var/cache/rclone \
--vfs-cache-max-size 20G \
--vfs-cache-max-age 24h \
--vfs-write-back 5s \
--dir-cache-time 5m \
--poll-interval 1m \
--allow-other --umask 022
- Under
offa file can be read, or written once from start to finish — nothing else.cp,>redirects,sed -i,nano, andrsyncwithout--inplacework because they write a whole new file; appending (>>),touchon an existing file,rsync --inplace, SQLite, git, media taggers, and anything that opens read-write fail withOperation not permittedorIllegal seek. Usewritesunless the mount is read-only or strictly copy-in. - A shrinking truncate can be silently lost under
writesandfull, and underminimalthrough a read-write handle (1.75.1, still in the 1.76 beta; rclone#10047): cutting an existing file that is not yet in the cache down to a non-zero size returns 0 and is undone on close, when the cache re-downloads the object over the truncated file. Truncate-then-write leaves a mixture of old and new bytes. Truncating to zero, extending, write-then-truncate, and files created in the session are unaffected. - With a write cache,
close()returns before the upload finishes (--vfs-write-back, default 5s, then the upload itself). A crash in that window loses the file; the cache directory is state, so do not put it on tmpfs you cannot afford to lose. --vfs-cache-max-sizeis a target for the cleaner, not a hard quota — open files cannot be evicted.- Changes made on the remote by something else appear after
--dir-cache-time(5m), sooner on backends that support polling.rclone rc vfs/refreshforces it. - A mount is not a POSIX filesystem: no hard links, no meaningful ownership or permissions, symlinks only with
--links, and directory renames are copy-and-delete on bucket backends. On S3 add--no-modtimeor--use-server-modtimeto avoid aHEADper file on everyls -l.
systemd unit
rclone mount supports Type=notify: the unit becomes active only once the mount is ready, so dependants can order themselves after it.
[Unit]
Description=rclone mount: s3prod:media at /mnt/media
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
Environment=RCLONE_CONFIG=/etc/rclone/rclone.conf
ExecStart=/usr/bin/rclone mount s3prod:media /mnt/media \
--vfs-cache-mode writes \
--cache-dir /var/cache/rclone \
--allow-other \
--log-level INFO
ExecStop=/usr/bin/fusermount3 -u /mnt/media
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
systemd runs units with a minimal environment — fusermount3 must be on the unit's PATH, and there is no $HOME to find a config in, hence the explicit RCLONE_CONFIG. For an /etc/fstab or .mount-unit form, symlink the binary as /sbin/mount.rclone and use filesystem type rclone with options such as rw,_netdev,allow_other,args2env,vfs-cache-mode=writes,config=/etc/rclone/rclone.conf,cache-dir=/var/cache/rclone.
serve, rcd and the rc API
serve
serve exposes any remote over a standard protocol, which turns rclone into an adaptor: an S3 endpoint in front of SFTP, a restic repository on Google Drive, WebDAV over a crypt remote.
rclone serve s3 remote:path --addr 127.0.0.1:8080 --auth-key ACCESS_KEY,SECRET_KEY
rclone serve webdav remote:path --addr :8081 --user u --pass p # or --htpasswd FILE
rclone serve http remote:path --addr :8082 # Read-only listing and download
rclone serve sftp remote:path --addr :2022 --authorized-keys ~/.ssh/authorized_keys
rclone serve restic remote:repos --addr :8000 --append-only --private-repos --htpasswd FILE
- No auth flag means no authentication.
serve s3without--auth-keyallows anonymous access;serve webdavandserve httpwithout--user/--htpasswdare open. Bind to loopback or put TLS (--cert,--key) and credentials on anything else. serve s3treats top-level directories of the served path as buckets, so it wants a path whose children are the buckets. Clients configure it asprovider = Rclone.serve restic --append-onlygives a backup host that can add snapshots but not delete them — the cheap answer to "what if the client is compromised".- All of these sit on the VFS layer and take the same
--vfs-cache-modeflags asmount; protocols whose clients write randomly (WebDAV, SFTP) wantwrites.
rcd and the rc API
# A standalone API daemon...
rclone rcd --rc-addr 127.0.0.1:5572 --rc-user admin --rc-pass "$RC_PASS"
# ...or the same API on any long-running command
rclone sync src dst --rc --rc-addr 127.0.0.1:5572 --rc-enable-metrics
export RCLONE_RC_USER=admin RCLONE_RC_PASS="$RC_PASS" # Client side of `rclone rc`
rclone rc core/stats # Live transfer statistics as JSON
rclone rc core/bwlimit rate=1M # Change the limit on a running job
rclone rc sync/copy srcFs=/srv/data dstFs=s3prod:bucket _async=true # Returns {"jobid": N}
rclone rc job/status jobid=1
rclone rc job/list
rclone rc vfs/refresh recursive=true # On a mount started with --rc
rclone rc rc/list # Every available call
curl -s -u "admin:$RC_PASS" -X POST http://127.0.0.1:5572/core/stats # It is plain HTTP POST + JSON
The rc API can read and write every remote in the config, so treat the listener as root-equivalent: loopback by default, credentials always (unauthenticated calls get 401), and never --rc-no-auth on a reachable address. --rc-enable-metrics adds a Prometheus /metrics endpoint to the rc server; --metrics-addr serves metrics alone without exposing the API.
bisync
bisync is two-way sync with state. It was promoted from beta to stable in 1.71, but its own help text still calls it "an advanced command, so use with care", and the 1.60 that Debian ships predates the 1.66 rework entirely — do not run bisync from a distribution package.
Key Concepts
- Between runs it keeps a listing of each side in a working directory (
~/.cache/rclone/bisync/by default;--workdirto move it), named after the two paths:remote1_path..remote2_path.path1.lstand.path2.lst. Each line records size, modtime, and optionally hash. - Those listing names embed both full paths, so two local paths longer than about 120 characters each overflow the 255-byte filename limit and the run fails with
file name too long. - A run lists both sides, diffs each against its prior listing to find what is new, changed, or deleted, then propagates the deltas in both directions and saves fresh listings.
- The first run must be
--resync, which builds the listings by making both sides a superset of each other. Without prior listings a normal run aborts. - A lock file in the workdir stops overlapping runs; an interrupted run leaves it behind.
rclone bisync /srv/work gdrive:work --resync --dry-run # Rehearse the baseline
rclone bisync /srv/work gdrive:work --resync # Establish it (path1 wins ties)
rclone bisync /srv/work gdrive:work -v # Every run thereafter
# A sturdier unattended form
rclone bisync /srv/work gdrive:work \
--check-access --resilient --recover --max-lock 2m \
--conflict-resolve newer --conflict-loser num --conflict-suffix conflict \
--compare size,modtime,checksum --max-delete 10 \
--backup-dir1 /srv/.bisync-trash --backup-dir2 gdrive:.bisync-trash -v
What it does when things go wrong
| Situation | Behaviour |
|---|---|
| File changed on both sides | Default --conflict-resolve none: both are kept, renamed name.conflict1 and name.conflict2, and copied to the other side |
--conflict-resolve newer (or older, larger, smaller, path1, path2) |
The winner keeps the name; the loser is renamed per --conflict-loser (or deleted with delete) |
| More than 50% of files deleted on one side | Safety abort: too many deletes — exit 1, nothing changed. --max-delete N sets the percentage; --force overrides |
| Every file on one side changed | Safety abort: all files were changed — the classic sign of an unmounted path or clock jump |
| Listings missing or a critical error | Bisync aborted. Must run --resync to recover. — exit 7, and every later run fails the same way until you do |
| Interrupted run | Lock file blocks the next run; --max-lock 2m lets stale locks expire, --recover resumes without a full --resync |
--resyncis not a repair button. It copies in both directions without deleting, so files you deleted on one side come back from the other. Use--resync-mode newer(orpath2,older, …) to choose a winner, and rehearse with--dry-run.--check-accessrequires a sentinel file (RCLONE_TEST) on both sides and aborts if either is missing — the guard against syncing an empty mount point over real data.- Exit codes: 0 success; 1 a retryable abort (the next run may succeed); 7 critical, needing
--resync. Alert on 7. - Filters go in
--filters-file, not--filter-from, and changing them requires a--resync.
Inspection One-Liners
rclone lsd remote: # Directories (or buckets) at this level
rclone ls remote:path # Size and path, recursive
rclone lsl remote:path # Size, modtime, path, recursive
rclone tree remote:path --max-depth 2
# lsf: the scriptable one. Format letters: p path, s size, t modtime, h hash, i ID, m MIME type, T tier
rclone lsf remote:path # Names only, directories with a trailing /
rclone lsf -R --files-only remote:path
rclone lsf -R --format "tsp" --separator " " remote:path
rclone lsf --format "hp" --hash SHA1 --files-only remote:path
rclone lsf --format "pst" --csv remote:path
# lsjson: everything, for jq
rclone lsjson -R --hash remote:path | jq -r '.[] | select(.Size > 1e9) | .Path'
rclone lsjson --stat remote:path/file # One object's metadata, no listing
rclone lsjson -M remote:path/file --stat # Include backend metadata (S3: tier, content-type, mtime)
rclone size remote:path # Total objects and bytes
rclone size remote:path --json # {"count":10,"bytes":18,"sizeless":0}
rclone about remote: # Quota: total / used / free (not on bucket backends)
rclone ncdu remote:path # Interactive "what is using the space" browser
rclone hashsum sha256 remote:path # Checksums in sha256sum format; `rclone hashsum` lists types
rclone cat remote:path/file | head # Stream an object to stdout
echo data | rclone rcat remote:path/file # Stream stdin to an object
rclone dedupe --dedupe-mode newest remote:path # Resolve same-name duplicates (Drive)
rclone dedupe --by-hash --dedupe-mode list remote:path # Report identical content under different names
rclone link remote:path/file --expire 24h # Public or presigned link, where the backend can make one
rclone backend features remote: # Capabilities: hashes, precision, server-side copy, ListR ...
rclone backend help remote: # Backend-specific commands (S3: restore, cleanup-hidden, versioning ...)
rclone size and rclone ncdu list the entire tree — on a large bucket that costs time and LIST requests; add --fast-list.
Unattended Runs
Exit codes
| Code | Meaning | Typical cause |
|---|---|---|
| 0 | Success | |
| 1 | Error not otherwise categorised | rclone check found differences; remote name not in config |
| 2 | Syntax or usage error | Unknown flag, wrong argument count |
| 3 | Directory not found | Source path missing |
| 4 | File not found | |
| 5 | Temporary error — more retries might fix it | Network, throttling |
| 6 | Less serious, non-retryable error | --immutable violation |
| 7 | Fatal error | --max-delete reached; --backup-dir overlap; bisync needs --resync |
| 8 | --max-transfer limit reached |
|
| 9 | Success, but nothing transferred | Only with --error-on-no-transfer |
| 10 | --max-duration limit reached |
rclone exits non-zero if any file still failed after --retries. Treat 8 and 10 as "resume next run", 9 as a signal for "the source went quiet", and everything else non-zero as an alert.
Logging
rclone sync src dst \
--log-file /var/log/rclone/sync.log --log-level INFO \
--log-file-max-size 50M --log-file-max-backups 7 --log-file-compress \
--stats 5m --stats-one-line
rclone sync src dst --use-json-log --log-level INFO # One JSON object per line on stderr
rclone sync src dst --stats-one-line-date -v # Timestamped single-line stats
rclone sync src dst -q # Errors only
{"time":"2026-10-05T17:06:18.043112+01:00","level":"info","msg":"Copied (new)","size":2,"object":"a.log","objectType":"*local.Object","source":"operations/copy.go:380"}
The default level is NOTICE, which is silent on success. -v is INFO (one line per transferred file), -vv is DEBUG (every comparison decision — the level to use when asking "why did it re-copy that"). Under systemd, skip --log-file and let the journal take stderr; --use-json-log gives structured fields to a log shipper. --dump headers and --dump bodies log HTTP traffic, including credentials — never into a shared log.
systemd service and timer
[Unit]
Description=rclone nightly sync of /srv/data
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
User=backup
Environment=RCLONE_CONFIG=/etc/rclone/rclone.conf
ExecStart=/usr/bin/rclone sync /srv/data s3prod:company-backups/host01 \
--checksum --fast-list \
--max-delete 1000 \
--backup-dir s3prod:company-backups/host01-trash \
--log-level INFO --stats 10m --stats-one-line
Nice=10
IOSchedulingClass=idle
[Unit]
Description=Nightly rclone sync
[Timer]
OnCalendar=*-*-* 02:30:00
RandomizedDelaySec=15m
Persistent=true
[Install]
WantedBy=timers.target
A oneshot service cannot overlap itself, which is the locking rclone does not provide: two concurrent sync runs to one destination will race. Persistent=true catches up after downtime. Add OnFailure= for alerting, and a second timer that runs rclone check or cryptcheck weekly — a backup that has never been verified is a hope.
For OAuth remotes on a server, authorise once via rclone authorize (above), keep the config directory writable by the service user, and monitor rclone about remote: so an expired grant is an alert rather than a silent month of failed runs. A root-owned /etc/rclone/ as in the unit above will not do: an OAuth remote there can only save refreshed tokens if the service user can write the directory, so give OAuth configs a directory owned by the service user.
Quick Reference
| Task | Command |
|---|---|
| Which version, and is it current | rclone version ; rclone selfupdate --check |
| List remotes | rclone listremotes --long |
| Show a remote without secrets | rclone config redacted NAME |
| Additive copy | rclone copy src dst -P |
| Mirror (deletes at destination) | rclone sync src dst -n then without -n |
| Copy one file to an exact name | rclone copyto src/file dst/newname |
| Move and tidy the source | rclone move src dst --delete-empty-src-dirs |
Keep what sync would delete |
--backup-dir remote:trash/$(date +%F) |
| Cap deletions | --max-delete N / --max-delete-size 10G |
| Never modify existing files | --immutable |
| Compare by hash | --checksum |
| Verify after transfer | rclone check src dst --one-way |
| Verify a crypt remote | rclone cryptcheck src crypt: |
| Test a filter | rclone lsf -R src --filter-from rules.txt |
| Cheap S3 sync | rclone sync src s3:bkt --checksum --fast-list |
| Skip bucket creation check | --s3-no-check-bucket |
| Throttle bandwidth / API | --bwlimit 10M / --tpslimit 10 |
| Mount usably | rclone mount r: /mnt/x --vfs-cache-mode writes |
| Headless OAuth | rclone authorize "drive" on a machine with a browser |
| Re-authorise | rclone config reconnect NAME: |
| Total size | rclone size remote:path --json |
| What can this backend do | rclone backend features remote: |
| Why did it (not) copy | add -vv |
| Obscure a password for config | rclone obscure 'pw' (not encryption) |
| Encrypt the config | rclone config encryption set |
| First bisync | rclone bisync a b --resync --dry-run |
Common Issues and Solutions
| Issue | Cause | Solution |
|---|---|---|
| Destination files vanished after a run | sync used where copy was meant, or source was empty/unmounted |
Use copy by default; --dry-run, --max-delete, and --backup-dir on every sync; restore from the backup dir or bucket versions |
Files landed in dst/ rather than dst/src/ |
No trailing-slash semantics — contents are always copied | Name the destination directory explicitly: rclone copy src remote:dst/src |
copy file remote:dir/file made a directory called file |
Destination of copy is always a directory |
Use copyto |
| Same files re-copied every run | No common hash and modtimes differ; SharePoint rewriting Office files | -vv to see the reason; --checksum or --size-only; --ignore-size --ignore-checksum on SharePoint |
Duplicate object found in destination - ignoring on Drive |
Two files share one name; sync replaces one of them once, then finds nothing to transfer |
rclone dedupe |
| Changed file not copied | Same size and modtime | --checksum; audit with rclone check |
--checksum did not catch a difference |
Backends share no hash type, so it compared sizes only | rclone backend features on both; rclone check --download; or a hasher remote |
--include and --exclude give odd results |
Mixed flags are parsed in indeterminate order | Ordered --filter rules or --filter-from |
Excluded files still at the destination after sync |
Filters hide them from both sides | --delete-excluded (rehearse first) |
AccessDenied on upload with a scoped key |
rclone tried to create/check the bucket | no_check_bucket = true |
| Instance role ignored on EC2/ECS | env_auth defaults to false |
env_auth = true |
S3 bill full of HEAD requests |
Default modtime comparison reads metadata per object | --checksum or --size-only, plus --fast-list |
| S3 storage not shrinking after deletes | Versioned bucket keeps non-current versions; abandoned multipart uploads | Lifecycle rule or rclone backend cleanup-hidden; rclone cleanup |
| Application errors or corrupt files on a mount | --vfs-cache-mode off cannot seek, open read-write, or append |
--vfs-cache-mode writes or full |
| File written to a mount missing after a crash | Upload happens after close() plus --vfs-write-back |
Wait for uploads (rclone rc vfs/stats); keep the cache dir persistent |
fusermount3 errors on mount (executable file not found in $PATH, option allow_other only allowed if 'user_allow_other' is set, Permission denied) |
fuse3 missing, user_allow_other unset, or AppArmor confining fusermount3 |
Install fuse3; edit /etc/fuse.conf; check the AppArmor profile |
| OAuth remote dies after a week | Google consent screen left in "Testing" | Publish the app; rclone config reconnect |
Failed to save config after 10 tries on an OAuth remote |
Token refresh cannot be written back: rclone needs to create a temp file beside the config | Make the config directory writable by the service user |
429 / too_many_requests / User rate limit exceeded |
Shared client ID or too much parallelism | Own client ID; lower --transfers/--checkers; --tpslimit |
| Flag "does not exist" on a server | Distribution package years behind | rclone version; install the upstream .deb |
bisync: Must run --resync to recover |
Listings missing or a previous critical error | Inspect both sides, then --resync with a deliberate --resync-mode; add --resilient --recover |
bisync: prior lock file found |
Earlier run still going or was killed | Confirm nothing is running, delete the .lck file; set --max-lock |
| Lost the crypt passwords | They existed only (obscured) in a lost rclone.conf |
Unrecoverable. Store both passwords in a password manager on day one |
| Config credentials exposed | rclone.conf readable; obscured is not encrypted |
chmod 600; rclone config encryption set with --password-command; rotate the keys |
Related Topics
- rsync — the host-to-host counterpart: delta transfer, trailing-slash semantics, and filter rules that look similar and are not
- AWS — S3 bucket policy, lifecycle rules, storage classes, and IAM, which rclone uses but does not manage
- SSH & SSH Config — the transport under the SFTP backend, and the tunnel for headless OAuth
- systemd — timers,
Type=notifyservices, and mount units for running rclone unattended - Linux Storage & Filesystems — where FUSE mounts fit, and what a real filesystem guarantees that a VFS mount does not
- Vault — a source for
--password-commandand short-lived cloud credentials