WSL File Transfer Guide Methods Scripts and Best Practices

Moving files between Windows and WSL2 is one of those things that looks simple until you hit a slow transfer, a permission error, or a case where the file you just copied has the wrong line endings and breaks your script. This guide covers every practical WSL file transfer method — File Explorer, cp, rsync, tar, and automated sync scripts — with the exact commands, expected output, and the specific errors you will run into. Tested on WSL2 with Ubuntu 22.04 and 24.04 on Windows 11. Last updated March 2026.

If you haven’t set up WSL2 yet, start with the WSL2 installation guide first. If you are here because you need to back up your entire WSL instance, the WSL2 backup guide covers full instance export with wsl --export.


How WSL File Transfer Actually Works — The Two Filesystems

WSL2 runs a real Linux kernel in a lightweight VM. It has two separate filesystems and understanding which one your file is on determines everything about transfer speed and permission behaviour.

  • WSL native filesystem — your Linux home directory at /home/username/. Stored in a virtual disk image (ext4.vhdx) on Windows. Fast for Linux operations. Accessible from Windows at \\wsl$\Ubuntu\ in File Explorer.
  • Windows mounted drives — your Windows C: drive is accessible inside WSL at /mnt/c/. D: drive at /mnt/d/, and so on. These are your actual Windows NTFS drives, mounted into WSL.

Cross-filesystem transfers — copying from /mnt/c/ to /home/ or the reverse — are slower than transfers within the same filesystem. How much slower depends on file size and count. For a single 1GB file the difference is small. For 50,000 small source files the difference is significant. The practical rule: keep your active development files in the WSL native filesystem (/home/), and copy to /mnt/c/ only when you need Windows to read them.

Check which filesystem you are working with:

# Check where you are
pwd

# Check filesystem type of current location
df -T .

# Translate a Windows path to WSL path
wslpath 'C:\Users\Username\Documents'
# Output: /mnt/c/Users/Username/Documents

# Translate a WSL path to Windows path
wslpath -w '/home/username/projects'
# Output: \\wsl$\Ubuntu\home\username\projects

WSL File Transfer via Windows File Explorer

The simplest way to move individual files. No commands needed. Two ways to get there:

From Windows — navigate to WSL files

Type this directly in the File Explorer address bar:

\\wsl$\Ubuntu

Replace Ubuntu with your actual distribution name if different. To see all installed distributions:

\\wsl$

From here you can drag and drop files in and out exactly like any Windows folder. You can also pin it to Quick Access for faster access later.

Error you may hit: if WSL is not currently running, \\wsl$ shows as empty or throws a network error. Fix: open your WSL terminal first to start the WSL session, then navigate to \\wsl$ again.

From WSL terminal — open Explorer in current directory

# Open File Explorer at current WSL directory
explorer.exe .

This opens a File Explorer window pointing at your current Linux directory. You can then drag files in from Windows, or copy files out to Windows.

Error you may hit:

explorer.exe: command not found

This means /mnt/c/Windows/System32/ is not in your WSL PATH. Add it:

echo 'export PATH=$PATH:/mnt/c/Windows/System32' >> ~/.bashrc
source ~/.bashrc

File Explorer is fine for occasional manual transfers. For anything recurring or involving more than a handful of files, use the command line methods below.


WSL File Transfer with cp

cp is the right tool for one-time copies of specific files or directories. It does not resume interrupted transfers and does not skip unchanged files — for those use cases, rsync is better.

# Copy a single file from Windows to WSL home
cp /mnt/c/Users/Username/Documents/config.txt ~/

# Copy a directory recursively with verbose output
cp -rv /mnt/c/source/ ~/destination/

# Copy preserving timestamps and permissions
cp -rp /mnt/c/source/ ~/destination/

Expected output from cp -rv:

'/mnt/c/source/file1.txt' -> '/home/username/destination/file1.txt'
'/mnt/c/source/file2.txt' -> '/home/username/destination/file2.txt'

The line endings problem: text files created on Windows use CRLF line endings (\r\n). Shell scripts copied from Windows to WSL with cp will fail with a confusing error:

bash: ./script.sh: /bin/bash^M: bad interpreter: No such file or directory

The ^M is the carriage return character. Fix it:

# Install dos2unix if not present
sudo apt install dos2unix

# Fix a single file
dos2unix script.sh

# Fix all .sh files in a directory
find ~/scripts -name "*.sh" -exec dos2unix {} \;

WSL File Transfer with rsync

rsync is better than cp for most WSL file transfer situations involving directories. It skips files that haven’t changed, resumes interrupted transfers, and gives you progress output. Install it first if needed:

sudo apt install rsync

Basic rsync transfer

# Sync directory from Windows to WSL with progress
rsync -avP /mnt/c/source/ ~/destination/

Expected output:

sending incremental file list
./
file1.txt
          1,234 100%    0.00kB/s    0:00:00 (xfr#1, to-chk=4/6)
file2.txt
          5,678 100%    5.54kB/s    0:00:01 (xfr#2, to-chk=3/6)

sent 7,123 bytes  received 54 bytes  14,354.00 bytes/sec
total size is 6,912  speedup is 0.97

The trailing slash matters in rsync — this catches people every time:

# WITH trailing slash on source — copies the CONTENTS of source into destination
rsync -av /mnt/c/source/ ~/destination/
# Result: ~/destination/file1.txt, ~/destination/file2.txt

# WITHOUT trailing slash on source — copies the directory ITSELF into destination
rsync -av /mnt/c/source ~/destination/
# Result: ~/destination/source/file1.txt, ~/destination/source/file2.txt

Neither is wrong — they do different things. Know which one you want before running it.

rsync with exclusions

# Exclude node_modules and temp files
rsync -avP \
  --exclude='node_modules/' \
  --exclude='*.tmp' \
  --exclude='.git/' \
  /mnt/c/source/ ~/destination/

rsync dry run — check before committing

Before running any rsync that deletes files or syncs a large directory, run it with -n first:

# Dry run — shows what would happen without actually doing it
rsync -avn /mnt/c/source/ ~/destination/

The output shows exactly which files would be transferred. Only run without -n once you confirm the list looks right.

Mirror sync with deletion

Use --delete when you want the destination to exactly mirror the source — files deleted from source get deleted from destination too. Always do a dry run first with this one:

# Dry run first
rsync -avn --delete /mnt/c/source/ ~/destination/

# Then run for real if the output looks correct
rsync -avP --delete /mnt/c/source/ ~/destination/

WSL File Transfer with tar — Large Directories

For large directories going to Windows, tar is often faster than rsync because it creates a single compressed file rather than doing a file-by-file transfer across the filesystem boundary.

# Create compressed archive on Windows drive
tar -czf /mnt/c/backups/projects.tar.gz ~/projects/

Expected output — tar is silent by default. Add -v to see file names as they are archived:

tar -czvf /mnt/c/backups/projects.tar.gz ~/projects/
projects/
projects/app.py
projects/config.json
projects/requirements.txt
# Extract on the other side
tar -xzf /mnt/c/backups/projects.tar.gz -C ~/restored/

Verify the archive before relying on it:

# List contents without extracting
tar -tzf /mnt/c/backups/projects.tar.gz | head -20

If you get no output or an error on the -t check, the archive is corrupt — the most common cause is running out of disk space mid-archive without getting an error message. Check available space with df -h /mnt/c/ before creating large archives.


Automated Sync Script

If you regularly sync the same directories, automate it. This script handles one-way sync from WSL to a Windows backup location, with logging and basic error handling:

#!/bin/bash
# Save as ~/scripts/sync-to-windows.sh
# Run: bash ~/scripts/sync-to-windows.sh

SOURCE="/home/$USER/projects"
DEST="/mnt/c/wsl-backup"
LOG="/home/$USER/logs/sync.log"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

mkdir -p "$DEST" "$(dirname "$LOG")"

log() {
    echo "[$TIMESTAMP] $1" | tee -a "$LOG"
}

# Check source exists
if [ ! -d "$SOURCE" ]; then
    log "ERROR: Source directory $SOURCE not found"
    exit 1
fi

# Check Windows drive is mounted
if [ ! -d "/mnt/c" ]; then
    log "ERROR: Windows drive not mounted at /mnt/c"
    exit 1
fi

log "Starting sync: $SOURCE -> $DEST"

rsync -avP \
    --exclude='node_modules/' \
    --exclude='.git/' \
    --exclude='*.tmp' \
    --exclude='__pycache__/' \
    "$SOURCE/" "$DEST/" 2>> "$LOG"

if [ $? -eq 0 ]; then
    log "Sync completed successfully"
else
    log "ERROR: Sync failed — check log for details"
    exit 1
fi

Make it executable and test it:

chmod +x ~/scripts/sync-to-windows.sh

# Test run
bash ~/scripts/sync-to-windows.sh

# Check the log
cat ~/logs/sync.log

To schedule it with cron:

crontab -e

Add this line to run it every day at 2 AM:

0 2 * * * /bin/bash /home/USERNAME/scripts/sync-to-windows.sh

Replace USERNAME with your actual username. Do not use ~ in cron paths — cron does not always expand it correctly.


Permission Errors After WSL File Transfer

The most common problem after copying files from Windows to WSL is wrong permissions. Files copied from /mnt/c/ often arrive with 777 permissions (everyone can read, write, execute), which is both a security problem and a practical one — Git, for example, will mark every copied file as modified because the executable bit changed.

# Check what permissions copied files have
ls -la ~/destination/

# Fix a directory of copied files to sensible defaults
find ~/destination -type f -exec chmod 644 {} \;
find ~/destination -type d -exec chmod 755 {} \;

# Fix ownership if files are owned by root after copy
sudo chown -R $USER:$USER ~/destination/

For shell scripts specifically — they need execute permission:

find ~/scripts -name "*.sh" -exec chmod 755 {} \;

To prevent the 777 permission problem permanently, add a [automount] section to /etc/wsl.conf:

sudo nano /etc/wsl.conf

Add these lines:

[automount]
options = "metadata"

Save and restart WSL:

# In PowerShell on Windows
wsl --shutdown
# Then reopen your WSL terminal

With metadata enabled, Linux file permissions are stored for files on Windows-mounted drives. Files you copy from Windows will respect permissions you set rather than defaulting to 777. For more on WSL configuration see the Microsoft WSL configuration documentation.


When Transfers Are Slow

If you are seeing unexpectedly slow transfers, check which direction and filesystem is involved before changing anything:

# Benchmark cross-filesystem write speed (WSL to Windows)
dd if=/dev/zero of=/mnt/c/testfile bs=1M count=512 conv=fdatasync
rm /mnt/c/testfile

# Benchmark WSL native write speed
dd if=/dev/zero of=~/testfile bs=1M count=512 conv=fdatasync
rm ~/testfile

Typical results on a decent NVMe system: WSL native is 1000–2000 MB/s, cross-filesystem to Windows is 200–500 MB/s. If your numbers are dramatically below these, the issue is likely antivirus scanning files as they cross the filesystem boundary. Windows Defender in particular can throttle WSL file transfers significantly. Excluding your WSL working directories from real-time scanning in Windows Security settings is the fix — add %LOCALAPPDATA%\Packages\CanonicalGroup* (where the WSL virtual disk lives) to the exclusion list.

For system-level Linux operations like swap and memory management alongside your WSL setup, see the Linux swap management guide — WSL2 has its own memory limits separate from host Linux swap, but the underlying concepts are the same.

🔗 Share This Post

🏷️ Tags

WSL
L

Written by Logic Encoder

Professional crypto analyst and trading expert

← Previous Post
Wrapped Dynex Richlist (0xDNX) DHIP v.2
Next Post →
How to Install WSL2 on Windows 10 and 11 (Ubuntu 2026)

Leave a Reply

Your email address will not be published. Required fields are marked *