If a server accepts SSH keys, you can restrict a key so it only works from specific source IP addresses. This is not a replacement for good key hygiene, but it can reduce blast radius — if an SSH private key is compromised, the attacker also needs to be coming from the right IP.
When this helps
IP-locking SSH keys is most useful for:
- Break-glass admin keys that should only work from a jump host or office VPN
- Deployment keys used by CI/CD from known IP ranges
- Keys for high-value servers where exfiltration risk is elevated
- Automation keys that should never be used from random locations
It is less useful for keys used by people who travel or work from dynamic home IPs. For those cases, pair it with certificate-based auth, SSH bastion hosts, or WireGuard VPN access.
Add a from restriction
Edit the user’s ~/.ssh/authorized_keys file. Prefix the public key with a from option:
from="203.0.113.10" ssh-ed25519 AAAA... comment
The from directive accepts:
- A single IPv4 or IPv6 address
- A subnet in CIDR notation:
from="203.0.113.0/24" - Multiple addresses separated by commas:
from="203.0.113.10,198.51.100.20" - Hostname that resolves to an IP:
from="jump.opshelp.internal"(resolved at auth time) - Wildcard domain:
from="*.opshelp.internal"
Full example with multiple options:
from="203.0.113.10,198.51.100.20",no-agent-forwarding,no-port-forwarding ssh-ed25519 AAAA... restricted-key
Combine with other restrictions
The authorized_keys file supports several useful options:
| Option | Effect |
|---|---|
from="ip" | Restrict source IP |
no-agent-forwarding | Block SSH agent forwarding |
no-port-forwarding | Block TCP forwarding |
no-X11-forwarding | Block X11 forwarding |
no-pty | Prevent shell allocation (useful for command-only keys) |
command="/usr/bin/backup" | Force a specific command on connect |
restrict | Enable all restrictions at once |
Example for a deployment key that can only run a specific script from a CI server:
from="203.0.113.99",no-agent-forwarding,no-port-forwarding,no-pty,command="/usr/local/bin/deploy.sh" ssh-ed25519 AAAA... deploy-key
Test before closing your session
The most common mistake is locking yourself out. Always:
- Open a second terminal to the server
- Edit
authorized_keyswith thefromrestriction - Test a new SSH connection from the allowed IP
- If it fails, your active session still works — fix the entry
- Only close all sessions after confirming the restricted key works
# From the allowed IP
ssh -i ~/.ssh/my_key deploy@server.example.com
# If this fails, fix from your other session
If you are on a dynamic or NAT’d IP, use curl ifconfig.me or curl icanhazip.com to confirm your current public IP before setting the restriction.
Watch dynamic IPs
Do not use from for someone with a constantly changing residential IP unless you have another access path. Common workarounds:
- Jump host — allow the key only from a fixed-IP jump host, then forward through it
- VPN — allow the key only from the VPN subnet IP range
- Fallback user — keep a separate user account with password auth for emergency access (strong password, 2FA enabled)
- Multiple authorized keys — keep one unrestricted key in secure offline storage for break-glass access
Managing multiple keys per user
A user’s authorized_keys file can contain multiple entries with different restrictions. Example:
# Developer key — restricted to office IP, full access
from="198.51.100.0/24" ssh-ed25519 AAAA... dev-key
# CI/CD key — restricted to CI runner, command-only
from="203.0.113.99",no-pty,command="/usr/local/bin/deploy.sh" ssh-ed25519 AAAA... ci-key
# Break-glass key in sealed envelope — unrestricted but monitored
ssh-ed25519 AAAA... break-glass-key
Check which keys are in use:
grep -v '^#' ~/.ssh/authorized_keys | wc -l
Audit existing keys
Review all authorized keys regularly:
for user in $(cut -d: -f1 /etc/passwd); do
authfile="/home/$user/.ssh/authorized_keys"
if [ -f "$authfile" ]; then
echo "=== $user ==="
cat "$authfile"
fi
done
Remove keys that belong to former team members, old CI pipelines, or unknown sources. An unmaintained authorized_keys file is a slow-growing security gap.
Pair with server-side controls
The from directive is a client-per-key restriction. Pair it with server-level controls in /etc/ssh/sshd_config:
PubkeyAuthentication yes
PasswordAuthentication no
PermitRootLogin no
AllowUsers deploy
AllowGroups ssh-users
MaxAuthTries 3
Test sshd config before reloading:
sshd -t && systemctl reload sshd
Monitoring and alerting
Set up logging for failed SSH attempts:
journalctl -u ssh -f
Consider setting up alerts for:
- SSH connections from unexpected IPs (even if they fail)
- Multiple failed key auth attempts from the same IP
- Successful auth from IPs not matching known ranges
Fail2ban can help with brute-force attempts, but from restrictions add a per-key layer that fail2ban cannot provide on its own.