In our previous articles, we’ve focused extensively on securing SSH servers—from changing default ports and implementing Fail2ban to setting up Multi-Factor Authentication and Zero Trust access controls. However, securing just one end of the connection only tells half the story. Even with a perfectly hardened server, vulnerabilities in your SSH client configuration can still expose your entire infrastructure to risk.
The increasing sophistication of targeted attacks means that threat actors are now specifically hunting for administrator and developer machines as their primary entry points. Recent security incidents reveal a clear pattern: rather than directly attacking hardened infrastructure, attackers prefer to compromise the machines of privileged users who already have authorized access. By targeting these endpoints, malicious actors can piggyback on legitimate connections, bypassing perimeter defenses and security monitoring that might otherwise detect unusual access patterns.
While organizations often invest significant resources in protecting their servers, the client machines that administrators and developers use to connect to these servers frequently become the weakest link in the security chain. A compromised workstation with SSH access can give attackers the keys to your entire kingdom, regardless of how well you’ve secured the server side.
Why Client-Side Security Matters
Most security professionals focus heavily on server-side protections, but client-side vulnerabilities present equally dangerous attack vectors:
- Compromised client machines can leak private keys and credentials, giving attackers direct access to your infrastructure using your own trusted identities.
- Man-in-the-middle attacks can intercept connections before they reach your secured server, capturing sensitive data or hijacking sessions entirely.
- Client configuration weaknesses can undermine even the strongest server security measures by allowing outdated protocols, weak ciphers, or vulnerable authentication methods.
- SSH agent forwarding can expose your credentials to every server in the chain, creating a cascade of potential compromise points.
The reality is that adversaries often target administrator workstations precisely because they know these machines hold the keys to multiple systems. A single compromised laptop can provide access to dozens or even hundreds of servers that would be difficult to breach directly.
1. Secure Your SSH Keys
Your private SSH keys are the crown jewels for attackers. Protecting them should be your highest priority:
# Generate a strong key with increased complexity (Ed25519 is now preferred over RSA)
ssh-keygen -t ed25519 -a 100
# Set restrictive permissions on your private key
chmod 600 ~/.ssh/id_ed25519
# Use a strong passphrase for your key
This code block demonstrates three essential practices for SSH key security. First, we’re generating an Ed25519 key rather than the older RSA type. Ed25519 keys are based on elliptic curve cryptography, offering similar security to RSA with much shorter key lengths (256 bits vs 3072+ bits), making them more efficient and easier to manage. The -a 100 parameter increases the key derivation function rounds, making the key more resistant to brute force attacks.
The second command sets the correct file permissions on your private key file. The 600 permission means only the owner can read or write to the file, preventing other users on the same system from accessing it. This is absolutely critical – SSH will actually refuse to use private keys with overly permissive settings.
Finally, while not shown in the command itself, the ssh-keygen process will prompt you to create a passphrase. Unlike server keys which often need to operate without human intervention, client keys should always be protected by a strong passphrase – essentially encrypting your key with a password you memorize, implementing two-factor authentication (something you have and something you know).
2. Create a Bulletproof SSH Config File
Your ~/.ssh/config file isn’t just for convenience—it’s a powerful security tool when configured properly. Many users don’t realize that this configuration file can enforce security policies across all your SSH connections:
# Global options for all hosts
Host *
# Prevent hanging connections and automatically kill idle sessions
ServerAliveInterval 300
ServerAliveCountMax 2
# Disable potentially vulnerable features
HashKnownHosts yes
StrictHostKeyChecking yes
VisualHostKey yes
# Set reasonable timeouts and batch limits
ConnectTimeout 10
ConnectionAttempts 3
# Use more secure key exchange and cipher options
KexAlgorithms [email protected],diffie-hellman-group-exchange-sha256
Ciphers [email protected],[email protected]
MACs [email protected],[email protected]
# Per-host customization example
Host production-*
User admin
IdentityFile ~/.ssh/production_ed25519
# Never forward agent or X11 to production servers
ForwardAgent no
ForwardX11 no
Host jumpserver
User jumpadmin
IdentityFile ~/.ssh/jump_ed25519
# Allow agent forwarding only to trusted jump hosts
ForwardAgent yes
Let’s break down this configuration file section by section:
The first part establishes global defaults that apply to all hosts. ServerAliveInterval and ServerAliveCountMax work together to detect and close stale connections – the client will send a keep-alive packet every 300 seconds, and if it gets no response after 2 attempts, the connection is terminated. This prevents “zombie” connections that might be exploited.
Next, we enable several important security features. HashKnownHosts encrypts the hostnames and addresses in your known_hosts file, making it harder for attackers to discover what servers you connect to if they gain access to your client. StrictHostKeyChecking ensures SSH warns you when a host key changes, which could indicate a man-in-the-middle attack. VisualHostKey displays an ASCII representation of the remote host’s key, making it easier to visually identify when something has changed.
The timeout settings prevent hanging during connection attempts, reducing the window of opportunity for certain attacks and improving usability.
Most critically, the algorithm specifications explicitly configure which cryptographic primitives SSH will use. We’re restricting SSH to use only modern, secure algorithms for key exchange, encryption, and message authentication. This protects against downgrade attacks and ensures your connections use the strongest available cryptography.
The host-specific sections demonstrate how to apply different security policies to different types of servers. For production servers, we’re using a dedicated key and explicitly disabling potentially risky features like agent forwarding and X11 forwarding. For the jump server, we use yet another dedicated key and carefully enable agent forwarding only where necessary.
3. Beware the Dangers of SSH Agent Forwarding
SSH agent forwarding (-A flag or ForwardAgent yes) lets you authenticate to Server B from Server A without copying your private keys around—which sounds convenient but creates serious security risks:
# The old risky way
ssh -A jumphost
# Then from jumphost
ssh destination
# The secure way
ssh -J jumphost destination
# Or in your config file
Host destination
ProxyJump jumphost
Agent forwarding works by creating a socket on the remote server that connects back to your local SSH agent. While this seems like a sensible solution to avoid copying private keys to intermediate servers, it creates a significant security hole: if the remote server is compromised, attackers can use this socket to authenticate as you to any server you have access to as long as your SSH session remains active.
This is particularly dangerous because many administrators enable agent forwarding when using jump or bastion hosts, unaware that they’re essentially extending trust to every server they connect through.
The solution is to use SSH ProxyJump (available since OpenSSH 7.3). The first command shows the risky approach using agent forwarding, where you first connect to the jump host with -A and then initiate a second connection from there.
The second command demonstrates the secure alternative using the -J flag, which establishes an end-to-end encrypted tunnel through the jump host without exposing your credentials. SSH handles the proxying automatically, and your private key never leaves your local machine.
The third example shows how to make this configuration permanent in your SSH config file. The ProxyJump directive tells SSH to automatically route connections through the specified jump host whenever you connect to this destination, with no need for agent forwarding.
4. Use SSH Certificates for Client Authentication
Beyond traditional SSH keys, SSH certificates offer significant advantages for organizations, especially those with many users and servers:
# Create a CA key (keep this extremely secure!)
ssh-keygen -t ed25519 -f ssh_ca
# Sign a user's public key with a 1-day expiration and specific permissions
ssh-keygen -s ssh_ca -I "username" -n "username" -V +1d /path/to/user_key.pub
SSH certificates address several limitations of standard public key authentication. Unlike traditional keys which require distributing and managing authorized_keys files on every server, certificates use a centralized trust model similar to TLS certificates on the web.
The first command creates a Certificate Authority (CA) key, which becomes your master signing key. This key must be protected with extreme care, ideally stored offline or in a hardware security module, as it represents the root of trust for your entire SSH infrastructure.
The second command signs a user’s public key, transforming it into a certificate. Let’s examine the parameters:
-s ssh_caspecifies the CA key to use for signing-I "username"sets a unique identifier for this certificate-n "username"specifies the username this certificate is valid for-V +1dsets an expiration date one day in the future- The final parameter is the public key being signed
The resulting certificate can then be used by the client for authentication. On the server side, rather than maintaining authorized_keys files, you simply configure the server to trust certificates signed by your CA.
This approach provides several security benefits: certificates automatically expire, eliminating stale access; they can be limited to specific usernames or hosts; and they provide better protection against man-in-the-middle attacks than traditional host key verification.
5. Lock Down Your SSH Agent
If you use ssh-agent to avoid typing your passphrase repeatedly, configure it to reduce risk:
# Start agent with limited lifetime
ssh-agent -t 3600
# Add key with confirmation required for each use
ssh-add -c ~/.ssh/id_ed25519
# Confirm the keys loaded
ssh-add -l
The SSH agent is a convenience tool that holds your decrypted private keys in memory, allowing you to authenticate to servers without repeatedly entering your passphrase. However, this convenience creates risk: if an attacker gains access to your session or your agent is forwarded to a compromised server, they can use your cached credentials.
The first command starts the SSH agent with a time limit of 3600 seconds (one hour). This ensures that even if you forget to shut down your agent, it will automatically expire after the specified period, limiting the window of exposure.
The second command adds your private key to the agent, but with an important security feature: the -c flag enables confirmation mode. With this option, the agent will prompt for confirmation each time your key is used, providing a safeguard against unauthorized use of your cached credentials. This creates a form of transaction approval similar to what many banking apps implement.
The third command lists the keys currently loaded in your agent, allowing you to verify that only the intended keys are available. It’s good practice to check this occasionally, as some applications or scripts might add keys to your agent without your explicit knowledge.
6. Implement Client-Side Multi-Factor Authentication
While server-side MFA is common, client-side MFA can provide additional protection by requiring multiple factors to access your local machine’s SSH capabilities:
# Install the pam_ssh_agent_auth module
sudo apt install libpam-ssh-agent-auth
# Configure your local sudo to require SSH key authentication
# Add to /etc/pam.d/sudo:
auth sufficient pam_ssh_agent_auth.so file=/etc/security/authorized_keys
Most security discussions around MFA focus on authenticating to remote systems, but securing local privilege escalation is equally important. These commands implement a creative form of multi-factor authentication for local sudo access.
The first command installs a PAM (Pluggable Authentication Module) that can verify SSH key authentication. PAM is the system Linux uses to authenticate users for various services.
The second command modifies the PAM configuration for sudo, telling it to accept SSH key authentication as a valid authentication method. The file=/etc/security/authorized_keys parameter tells the module where to find the authorized public keys (similar to the SSH authorized_keys file).
With this setup, to use sudo, you’ll need both your account password (something you know) and an SSH key (something you have) that’s loaded in your agent. An attacker who manages to compromise your password would still need your SSH key to gain elevated privileges.
This creates a defense-in-depth strategy: even if a malicious actor obtains your user password through keylogging or shoulder surfing, they cannot escalate privileges without also compromising your SSH key and passphrase.
7. Audit Your Known Hosts File
Your ~/.ssh/known_hosts file is your first defense against MITM attacks, but many users never inspect or maintain it:
# Check for outdated or suspicious key entries
ssh-keygen -l -f ~/.ssh/known_hosts
# Remove a specific host entry if needed
ssh-keygen -R hostname
The known_hosts file is a critical security mechanism that protects you from man-in-the-middle attacks by storing the cryptographic fingerprints of servers you’ve previously connected to. When you connect to a server, SSH compares its current key with the stored one and alerts you if there’s a mismatch, which could indicate an attack.
However, this file tends to grow over time with entries for servers that may no longer exist or may have legitimately changed their host keys. It’s good practice to periodically review this file.
The first command lists all host keys stored in your known_hosts file along with their fingerprints. This allows you to verify that the keys match what you expect and identify any outdated or suspicious entries.
The second command removes a specific host’s entry from your known_hosts file. This is necessary when a server has legitimately changed its host key, such as after a reinstallation or migration. After removing the old entry, the next connection to that server will prompt you to verify and accept the new key.
For organizations managing many servers, you can enhance host key verification through DNS:
# Generate SSHFP records for your DNS
ssh-keygen -r hostname
This command generates SSH Fingerprint (SSHFP) DNS records for a host. When these records are published in your DNS zone and your SSH client is configured with VerifyHostKeyDNS yes, SSH can verify host keys through DNS in addition to the known_hosts file. This provides an additional verification channel that makes MITM attacks even more difficult, as an attacker would need to compromise both your DNS and your network connection.
Conclusion
SSH security is only as strong as its weakest link, which is often the client side. As sophisticated attackers increasingly focus on developer machines and endpoints rather than hardened servers, client-side security becomes even more critical. The techniques in this article might seem like additional work, but they represent essential layers of protection for modern security postures.
Many security breaches begin not with sophisticated zero-day exploits against hardened servers, but with compromised developer or administrator credentials. By implementing the seven strategies outlined above, you’re not only protecting your servers but also closing the security loop by securing the client side of your SSH connections—creating true end-to-end protection for your infrastructure.
Remember that security is a continuous process, not a one-time setup. Regularly review and update your SSH configurations on both client and server sides, keep your SSH software updated, and stay informed about evolving best practices and threats.
In our next article, we’ll explore advanced SSH logging techniques that can help you identify suspicious patterns before they evolve into full-blown security incidents, completing our comprehensive approach to SSH security monitoring.
