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:

OptionEffect
from="ip"Restrict source IP
no-agent-forwardingBlock SSH agent forwarding
no-port-forwardingBlock TCP forwarding
no-X11-forwardingBlock X11 forwarding
no-ptyPrevent shell allocation (useful for command-only keys)
command="/usr/bin/backup"Force a specific command on connect
restrictEnable 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:

  1. Open a second terminal to the server
  2. Edit authorized_keys with the from restriction
  3. Test a new SSH connection from the allowed IP
  4. If it fails, your active session still works — fix the entry
  5. 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:

  1. Jump host — allow the key only from a fixed-IP jump host, then forward through it
  2. VPN — allow the key only from the VPN subnet IP range
  3. Fallback user — keep a separate user account with password auth for emergency access (strong password, 2FA enabled)
  4. 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.