Windows Server is fine at holding files. Moving those files on a schedule to a remote Linux VPS for backup or cold storage is where the tooling gets awkward. Robocopy is excellent on the LAN. For push-to-VPS over SSH, I still prefer rsync: delta transfers, excludes, dry runs, and the same scripts I use on Linux.
The missing piece on Windows is a Unix-like environment that gives you rsync, ssh, and a shell.
That is what Cygwin is for. Below is how I set it up so a Task Scheduler job can back up a folder
to a remote VPS without anyone clicking through a GUI.
D:\Share\projects), a Linux VPS with SSH access, disk space on the VPS, and an account that can
write to the backup destination (often under /backups/...).
1. Why Cygwin here (and not “just PowerShell”)
You can copy with PowerShell, WinSCP scripting, or third-party backup agents. I reach for Cygwin when I want:
- Real
rsyncbehaviour (skip unchanged files, resume-friendly, clear exclude rules) - SSH key auth, the same way Linux boxes talk to the VPS
- A small bash script I can test by hand, then hand to Task Scheduler
- Logs that look like every other rsync job I already operate
Cygwin is not magic. Paths look different, and the service account that runs the task must see the same keys and paths you tested with. Plan for that up front.
2. Install Cygwin with the right packages
Download the installer from the official Cygwin site (setup-x86_64.exe for 64-bit).
Install to the default root unless you have a reason not to, usually:
C:\cygwin64
During package selection, search and mark these (plus whatever your site already standardises on):
rsyncopenssh(bringsssh,ssh-keygen,scp)bash(normally selected already)cygrunsrvonly if you later want Cygwin services; not required for Task Scheduler + rsyncnanoorvimif you like editing scripts inside the Cygwin terminal
Finish the install, then open Cygwin64 Terminal and confirm:
rsync --version
ssh -V
which rsync
which ssh
3. Learn the path mapping
Inside Cygwin, Windows drives show up under /cygdrive/. Examples:
# Windows # Cygwin
C:\Users\mark /cygdrive/c/Users/mark
D:\Share\projects /cygdrive/d/Share/projects
C:\cygwin64\home\mark /home/mark
Keep backup sources and scripts in places that stay stable. I usually put scripts under the Cygwin home
of the account that will run the job, for example /home/backup/bin/, and point rsync at
/cygdrive/d/... for the data.
4. Create an SSH key and trust the VPS
Do this as the Windows user that will own the scheduled task (or as a dedicated backup service account). In the Cygwin terminal:
mkdir -p ~/.ssh
chmod 700 ~/.ssh
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_backup -C "win-server-backup"
# Copy the public key to the VPS (one-time, with a password or existing access):
ssh-copy-id -i ~/.ssh/id_ed25519_backup.pub backup@vps.example.com
# Or append manually on the VPS:
# ~/.ssh/authorized_keys for user "backup"
Pin the host key and test a login with the dedicated key:
ssh -i ~/.ssh/id_ed25519_backup backup@vps.example.com 'mkdir -p /backups/win01 && df -h /backups'
If that works without a password prompt, you are ready for unattended rsync. Prefer a least-privilege VPS user that can write only under the backup tree.
5. Dry-run rsync before you schedule anything
Replace the paths and host with yours. -n is dry-run: nothing is written on the VPS.
rsync -avzn -e "ssh -i /home/backup/.ssh/id_ed25519_backup -o IdentitiesOnly=yes" \
--delete \
--exclude '.tmp/' \
--exclude '*.lock' \
/cygdrive/d/Share/projects/ \
backup@vps.example.com:/backups/win01/projects/
Read the file list carefully. Trailing slashes matter: projects/ means “contents of this folder,”
which is usually what you want. Drop -n when the preview looks right:
rsync -avz -e "ssh -i /home/backup/.ssh/id_ed25519_backup -o IdentitiesOnly=yes" \
--delete \
--exclude '.tmp/' \
--exclude '*.lock' \
/cygdrive/d/Share/projects/ \
backup@vps.example.com:/backups/win01/projects/
--delete: it makes the VPS mirror match the source (files removed locally
get removed remotely). That is great for a pure mirror. If you want a growing archive that never deletes
on the VPS, omit --delete or use a dated destination folder instead.
6. Wrap it in a small bash script
Example: /home/backup/bin/backup-projects.sh
#!/usr/bin/env bash
set -euo pipefail
LOG_DIR="/home/backup/logs"
mkdir -p "$LOG_DIR"
LOG_FILE="${LOG_DIR}/projects-$(date +%Y%m%d-%H%M%S).log"
SRC="/cygdrive/d/Share/projects/"
DEST="backup@vps.example.com:/backups/win01/projects/"
SSH_KEY="/home/backup/.ssh/id_ed25519_backup"
RSYNC_RSH="ssh -i ${SSH_KEY} -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=yes"
{
echo "=== backup start $(date -Is) ==="
rsync -avz --delete \
--exclude '.tmp/' \
--exclude '*.lock' \
-e "$RSYNC_RSH" \
"$SRC" "$DEST"
echo "=== backup ok $(date -Is) ==="
} >>"$LOG_FILE" 2>&1
Make it executable and run it once by hand:
chmod +x /home/backup/bin/backup-projects.sh
/home/backup/bin/backup-projects.sh
tail -n 50 /home/backup/logs/projects-*.log
7. Schedule it with Windows Task Scheduler
Task Scheduler should call Cygwin’s bash, not a random .bat that forgets the environment.
A reliable pattern:
- Program/script:
C:\cygwin64\bin\bash.exe - Arguments:
-lc "/home/backup/bin/backup-projects.sh" - Start in:
C:\cygwin64\bin(optional but tidy) - Run whether user is logged on or not, using the same Windows account whose
/home/...and.sshyou configured - Trigger: daily or hourly, depending on how often the data changes
The -l flag gives you a login shell so PATH includes Cygwin’s bin.
After you create the task, use Run once from Task Scheduler and confirm a new log file appears.
If the task “succeeds” but rsync never runs, the usual causes are: wrong Windows user (different Cygwin home), missing private key permissions, or Task Scheduler not allowed to network when the session is locked. Fix the account first; do not paper over it with stored passwords in the script.
8. Hardening and hygiene
- Use a dedicated VPS user and directory; do not rsync as root on the VPS.
- Keep the private key only on the Windows box that needs it; restrict NTFS ACLs on the
.sshfolder. - Rotate or prune local logs under
/home/backup/logsso disks do not fill up. - On the VPS, monitor free space and optionally snapshot or tar dated copies if you need point-in-time restore.
- Test a restore path: copy a file back with rsync or
scpbefore you trust the job in production. - If the share is huge, start with one subtree, measure runtime, then widen the source path.
9. Optional: dated folders instead of a live mirror
When you want “keep last N nights” rather than one mirror, push into a dated destination and prune old trees on the VPS with a separate cron job:
STAMP=$(date +%Y-%m-%d)
DEST="backup@vps.example.com:/backups/win01/projects/${STAMP}/"
rsync -avz -e "$RSYNC_RSH" \
--exclude '.tmp/' \
"$SRC" "$DEST"
That uses more disk, but restores are obvious: pick the date folder. Mix both styles if you need a fast mirror plus occasional dated checkpoints.
Short “done” checklist
- Cygwin installed with
rsyncandopenssh - SSH key auth to the VPS works with no password prompt
- Manual rsync dry-run and real run both succeed
- Script logs under a known folder
- Task Scheduler runs
bash -lcas the correct Windows user - You have tested at least one restore from the VPS
Once that is in place, the Windows box keeps serving files locally, and the VPS quietly holds a copy you can
reach even if the server room has a bad week. Same rsync habits as Linux, just with /cygdrive in front
of the path.