Keep a copy of important local state that survives losing the machine: archive it, encrypt the archive with age, and upload only the encrypted file to S3. This is off-site disaster recovery, for data that must come back after a disk failure, reinstall, or wipe. It is a different job from moving files between two machines you own, which is a local transfer (see the brief comparison at the end).
When to use this
Reach for an encrypted S3 archive when the data should survive local machine loss: important app state, anything you would need after a reinstall, or sensitive files that must restore even if every machine you own is gone. Good candidates are browser profile state, app state (Zotero, Claude/Codex), an SSH key archive, and private config. Plain documents, notes, and datasets do not need this; source code belongs in Git.
Encrypt before upload, not just in transit
A network transfer over SSH is encrypted while it crosses the wire, but the file
is plain once written to disk. Cloud storage is the same: S3 may encrypt at rest
on its side, but you do not control that copy and it is readable by whoever holds
the bucket. So encrypt the archive yourself with age before it leaves the machine,
and upload only the .age file. Never upload raw secrets (tokens, cookies, SSH
keys, .env, cloud credentials, the age identity, or session state).
Create the encrypted archive
Stop the app first if it has live state. Compress the source, encrypt the archive with your public age recipient, then delete the plaintext archive. Placeholders: SOURCE_PATH (folder to back up), BACKUP_NAME (archive name), AGE_RECIPIENT (public age recipient).
# compress
tar --zstd -cf BACKUP_NAME.tar.zst -C "$(dirname SOURCE_PATH)" "$(basename SOURCE_PATH)"
# encrypt, then drop the plaintext archive
age -r AGE_RECIPIENT -o BACKUP_NAME.tar.zst.age BACKUP_NAME.tar.zst
rm -f BACKUP_NAME.tar.zst
ls -lh BACKUP_NAME.tar.zst.ageUpload to S3
Copy only the encrypted file to the bucket, then confirm it landed.
aws s3 cp BACKUP_NAME.tar.zst.age s3://S3_BUCKET/S3_PREFIX/BACKUP_NAME.tar.zst.age
aws s3 ls s3://S3_BUCKET/S3_PREFIX/Restore from S3
Download the encrypted archive, decrypt it with your age identity, list it to confirm the contents, then extract into a chosen parent folder.
aws s3 cp s3://S3_BUCKET/S3_PREFIX/BACKUP_NAME.tar.zst.age .
age -d -i ~/.config/age/identity.txt -o BACKUP_NAME.tar.zst BACKUP_NAME.tar.zst.age
# inspect before extracting
tar -tf BACKUP_NAME.tar.zst | head -50
# extract, then clean up the plaintext archive
tar -xf BACKUP_NAME.tar.zst -C DEST_PARENT_PATH
rm -f BACKUP_NAME.tar.zst
# verify
du -sh DEST_PARENT_PATHDo not live-sync active state
Browser profiles (cookies, sessions, history, extensions), Zotero databases, and app state for tools like Claude or Codex are live databases, not flat files. Continuous sync (Syncthing, a shared cloud-synced profile) while the app is open can corrupt or conflict them, and running the same profile on two machines at once makes it worse. Back them up the archive-and-encrypt way instead: stop the app, archive the state, encrypt it, and restore only when needed.
Compared to rsync over SSH
When both machines are reachable and you just want to move data between them, this encrypted-archive flow is overkill. Use rsync over SSH: the transfer is encrypted in transit, and if the destination disk is LUKS-encrypted the result is fine without a separate age step. The short form, run from inside the source folder:
cd SOURCE_PATH
rsync -avh --progress ./ "$USER"@192.168.1.17:DEST_PATH/Use S3 + age when the copy must outlive the machines; use rsync over SSH for moving data between machines you own right now. Full transfer flow: Transfer files between machines with rsync over SSH.
Safety rules
- Encrypt sensitive data before any off-site upload; store only encrypted archives in S3.
- Stop an app before archiving its state; verify a restore before deleting the original.
- Keep the age identity safe and out of Git; store recovery info in a password manager.
- Never commit secrets: .env, SSH private keys, cloud credentials, the age identity, browser profiles, or app tokens and session state.