The Error
You run an SSH command โ connecting to a remote server, cloning a Git repo, or deploying code โ and you get this:
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions for 'id_rsa' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/c/Users/YourName/.ssh/id_rsa": bad permissions
Permission denied (publickey).
SSH won't touch the key. Connection dead.
OpenSSH checks file permissions before loading any private key. If another account can read it, SSH considers the key compromised and refuses to proceed. On Linux, one command solves this: chmod 600 ~/.ssh/id_rsa. Windows uses NTFS ACLs instead of Unix permission bits, so the fix looks different.
Why This Happens on Windows
Copying a private key to Windows is usually where things go sideways. Whether it came from a download, a USB drive, or another machine, the file tends to inherit permissions from its parent folder. That means the Users group, SYSTEM, and Administrators may all have read access. OpenSSH sees that and refuses to load the key.
Fix: Using icacls in PowerShell
icacls is the right tool for this. Open PowerShell (no admin rights needed) and run:
# Adjust path if your key is stored elsewhere
$key = "$env:USERPROFILE\.ssh\id_rsa"
# Step 1: Remove all inherited permissions
icacls $key /inheritance:r
# Step 2: Remove broad group permissions
icacls $key /remove "NT AUTHORITY\SYSTEM"
icacls $key /remove "BUILTIN\Administrators"
icacls $key /remove "BUILTIN\Users"
# Step 3: Grant only your user read access
icacls $key /grant:r "${env:USERNAME}:R"
Run these in order. Each strips a layer of permissions โ the last adds yours back, read-only.
One-liner version
Prefer one line? Chain them all together:
icacls "$env:USERPROFILE\.ssh\id_rsa" /inheritance:r /remove "NT AUTHORITY\SYSTEM" /remove "BUILTIN\Administrators" /remove "BUILTIN\Users" /grant:r "${env:USERNAME}:R"
Alternative Fix: Using PowerShell ACL Objects
icacls covers most cases. If you're scripting a machine setup or need finer control, you can go lower-level with .NET's System.Security.AccessControl classes directly:
$key = "$env:USERPROFILE\.ssh\id_rsa"
# Load the current ACL
$acl = Get-Acl $key
# Disable inheritance and remove inherited entries
$acl.SetAccessRuleProtection($true, $false)
# Remove all existing access rules
$acl.Access | ForEach-Object { $acl.RemoveAccessRule($_) }
# Add a rule: current user gets Read access only
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
$env:USERNAME, "Read", "Allow"
)
$acl.AddAccessRule($rule)
# Apply the updated ACL back to the file
Set-Acl -Path $key -AclObject $acl
Write-Host "Permissions updated for $key"
Fix via GUI (File Explorer)
Prefer not to touch the command line? File Explorer works too:
- Right-click
id_rsain File Explorer โ Properties - Go to the Security tab โ click Advanced
- Click Disable inheritance โ choose Remove all inherited permissions
- Click Add โ Select a principal โ type your Windows username โ OK
- Check Read only โ OK โ Apply
When done, only your user account should appear in the permission list.
Verify the Fix
Before testing SSH, confirm the ACL looks right:
icacls "$env:USERPROFILE\.ssh\id_rsa"
You want to see only your username:
C:\Users\YourName\.ssh\id_rsa YourName:(R)
Successfully processed 1 files; Failed processing 0 files
Now test the connection:
ssh -T git@github.com
Or connect directly to your server:
ssh -i ~/.ssh/id_rsa user@your-server.com
No more warning block. If SSH still fails now, it'll be an authentication error โ not a permissions one โ which means the key loaded successfully.
Fix for Git Bash / WSL Users
Inside WSL or Git Bash, if the key lives in the WSL filesystem, chmod works fine:
# Inside WSL or Git Bash
chmod 600 ~/.ssh/id_rsa
chmod 700 ~/.ssh
Accessing a Windows-path key from WSL (like /mnt/c/Users/YourName/.ssh/id_rsa)? chmod won't touch the NTFS ACLs. Use icacls from a PowerShell window for those.
Apply to All Keys at Once
Got multiple private keys in your .ssh folder? Fix them all in one shot:
Get-ChildItem "$env:USERPROFILE\.ssh" -File | Where-Object { $_.Name -notmatch '\.pub$|config|known_hosts' } | ForEach-Object {
$path = $_.FullName
icacls $path /inheritance:r /remove "NT AUTHORITY\SYSTEM" /remove "BUILTIN\Administrators" /remove "BUILTIN\Users" /grant:r "${env:USERNAME}:R"
Write-Host "Fixed: $path"
}
Tips
- Set permissions at key creation time. Running
ssh-keygenon Windows sets the ACL correctly by default. Problems almost always come from copying keys between machines. - Keep keys inside your user profile. Shared or system directories keep pulling in broad permissions. Stick to
C:\Users\YourName\.ssh\. - Cross-platform permission translation. If you work across Windows and Linux and need to understand what
chmod 600actually maps to in ACL terms, the Unix Permissions Calculator on ToolCraft makes it visual โ runs entirely in the browser, no data sent anywhere. - Public keys don't need strict permissions.
id_rsa.pubis fine to leave readable. But while you're here, confirm your entire.sshdirectory is owned only by your account.

