SSHwatch Insights Blog

SSH Key Management Best Practices: Automate, Rotate, and Protect

In previous articles, we’ve covered how to harden SSH configurations, monitor for suspicious activity, and implement protective measures like Fail2ban. However, even with these safeguards in place, poor key management practices can undermine your entire security posture. For many Linux administrators, SSH keys are set up once and then forgotten – creating security blind spots that can persist for years.

The ubiquity of SSH keys in Linux environments makes them particularly critical to manage properly. While passwords can be changed relatively easily across systems, SSH keys often proliferate across developer workstations, automation servers, and backup systems. A single compromised key can grant an attacker persistent access to your infrastructure without triggering the usual warning signs of a breach. Most concerning, this access often persists long after employees leave an organization or change roles.

A 2023 study by the SANS Institute found that over 70% of organizations surveyed had no formal process for SSH key rotation, and nearly 40% had no inventory of existing SSH keys in their environment. This security gap exists despite the fact that SSH keys typically provide access to the most critical systems in an organization’s infrastructure.

This article outlines a comprehensive approach to SSH key management that balances security with practicality, focusing on automation techniques that work well in Linux environments. By implementing these practices, you’ll not only improve your security posture but also reduce the administrative overhead associated with manual key management processes.

The Problem with Standard SSH Key Management

Most Linux administrators are familiar with the basics of generating and deploying SSH keys. The typical workflow involves generating a key pair, adding the public key to the authorized_keys file on target servers, and securing the private key on the client. This approach has served the Linux community well for decades, providing a more secure alternative to password authentication.

However, as organizations scale their infrastructure and face increasingly sophisticated threats, the limitations of traditional SSH key management become apparent. The ease of generating and distributing SSH keys has led to an explosion of untracked keys across enterprise environments. It’s not uncommon for a single admin to have dozens of keys spread across various systems, with no documentation of which keys have access to which servers.

This unmanaged proliferation creates serious security challenges. When an administrator leaves the organization, their access should be promptly revoked—but how can you revoke access if you don’t know which keys they’ve deployed? Similarly, in the event of a suspected breach, how can you quickly identify and rotate compromised keys without disrupting critical systems?

Several common practices exacerbate these risks:

  1. Keys without expiration dates: Unlike certificates, standard SSH keys never expire, potentially granting access indefinitely
  2. Inadequate key rotation: Many organizations never rotate SSH keys, even when employees leave
  3. Poor key storage practices: Private keys often stored without encryption or with weak passphrases
  4. No centralized inventory: Most organizations lack visibility into which keys grant access to which systems
  5. Manual provisioning processes: Adding authorized keys through manual processes leads to inconsistency

The consequences of these practices extend beyond theoretical security concerns. In recent years, several high-profile breaches have been traced back to compromised SSH keys that provided attackers with persistent access to critical infrastructure. Unlike other attack vectors that might trigger alerts, SSH access using valid keys often blends in with legitimate administrative activity, allowing attackers to maintain long-term presence in compromised environments.

Setting Up Automated Key Management

The cornerstone of effective SSH key management is automation. Manual processes are not only time-consuming but also prone to inconsistency and human error. When administrators are required to manually manage keys across dozens or hundreds of servers, security practices inevitably suffer.

Automation addresses these challenges by enforcing consistent policies across your environment. It also makes secure practices like regular key rotation practical at scale. Without automation, the operational burden of key management often leads organizations to take shortcuts that compromise security.

The

Step 1: Implement SSH Certificate Authority (CA)

One of the most powerful yet underutilized features of OpenSSH is its built-in certificate authority functionality. Unlike traditional SSH key-based authentication, which requires distributing and managing individual authorized keys across all target servers, a certificate authority creates a trust relationship that scales much more efficiently.

The SSH CA model works similarly to TLS certificates on the web. Rather than trusting individual keys, servers are configured to trust a certificate authority. Users then request short-lived certificates signed by this authority. This approach provides several advantages:

  • Certificates can have explicit expiration dates
  • Access can be centrally revoked without modifying server configurations
  • Certificates can specify exactly which usernames are authorized
  • The CA private key never needs to be distributed to servers

This model also significantly simplifies the onboarding and offboarding processes. When a new employee joins, they generate their key pair locally, request a certificate from your CA, and immediately gain appropriate access to all systems. When they leave, you simply stop issuing them certificates, and their access naturally expires without requiring changes to authorized_keys files across your fleet.

Here’s how to implement a basic SSH CA:

# Generate a CA key (keep this secure!)
ssh-keygen -t ed25519 -f /etc/ssh/ca_key -C "SSH CA Key"

# Configure SSH servers to trust certificates signed by your CA
echo "TrustedUserCAKeys /etc/ssh/ca_key.pub" >> /etc/ssh/sshd_config
service sshd restart

# Sign a user's public key with your CA (valid for 24 hours)
ssh-keygen -s /etc/ssh/ca_key -I "username" -n "username" -V +24h /path/to/user_key.pub

With a CA in place, you can issue time-limited certificates instead of permanently authorized keys. For users, the experience remains largely unchanged—they still use their private key for authentication, but now it’s accompanied by a CA-signed certificate that limits the scope and duration of their access.

In larger environments, you might want to integrate this CA functionality with an identity provider like LDAP or Active Directory, ensuring that only current employees with appropriate permissions can obtain valid certificates. Tools like HashiCorp Vault, Netflix’s BLESS, and Teleport provide more sophisticated implementations of this pattern for enterprise environments.

Step 2: Create a Key Inventory with Ansible

Without an accurate inventory of existing SSH keys, it’s impossible to manage your security posture effectively. This Ansible playbook helps you create a comprehensive catalog of all authorized SSH keys across your infrastructure.

# inventory_ssh_keys.yml
---
- name: Collect SSH Key Inventory
  hosts: all
  become: yes
  tasks:
    - name: Gather authorized keys for all users
      shell: "find /home -name authorized_keys -exec cat {} \\;"
      register: all_auth_keys
      changed_when: false
      
    - name: Collect key metadata
      set_fact:
        key_inventory: "{{ all_auth_keys.stdout_lines | map('regex_search', '(ssh-\\w+)\\s+([^\\s]+)\\s+(.*)') | list }}"
    
    - name: Report keys to central location
      copy:
        content: "{{ key_inventory | to_json }}"
        dest: "/var/log/ssh_keys_{{ inventory_hostname }}.json"
      delegate_to: localhost

Here’s what this playbook does:

  1. It searches across all user home directories for authorized_keys files and collects their contents.
  2. It parses each line of these files to extract key information, breaking down each key into its components (key type, key data, and comment).
  3. Finally, it saves this information to a central JSON file on your Ansible control node, with a separate file for each server. These files are named based on the server’s inventory hostname.

The result is a centralized repository of all authorized keys in your environment. You can further process these JSON files to generate reports, identify unused or outdated keys, and track key distribution across your infrastructure.

Run this playbook regularly (perhaps weekly or monthly) to maintain an up-to-date inventory of all authorized keys across your systems. Over time, this data provides valuable insights into key usage patterns and potential security issues.

Step 3: Automate Key Rotation

Regular key rotation is essential for limiting the impact of compromised credentials, but implementing rotation manually across multiple servers is error-prone and time-consuming. This script automates the process, making regular key rotation practical even in large environments.

#!/bin/bash
# rotate_ssh_keys.sh

# Generate new key
NEW_KEY_FILE="$HOME/.ssh/id_ed25519_$(date +%Y%m%d)"
ssh-keygen -t ed25519 -f "$NEW_KEY_FILE" -N "$KEY_PASSPHRASE"

# Update authorized keys on all servers (using Ansible)
ansible all -m authorized_key -a "user=$USER key={{ lookup('file', '$NEW_KEY_FILE.pub') }} state=present"

# Wait for keys to propagate, then test
sleep 10
for SERVER in $(cat server_list.txt); do
  ssh -i "$NEW_KEY_FILE" -o PasswordAuthentication=no $SERVER "echo 'Key rotation successful'" || echo "Failed: $SERVER"
done

# If successful, schedule old key for removal
echo "ansible all -m authorized_key -a \"user=$USER key={{ lookup('file', '$HOME/.ssh/id_ed25519.pub') }} state=absent\"" > remove_old_keys.sh
chmod +x remove_old_keys.sh
echo "Run ./remove_old_keys.sh after confirming all systems accessible with new key"

# Update SSH config to use new key by default
sed -i "s|IdentityFile ~/.ssh/id_ed25519|IdentityFile $NEW_KEY_FILE|g" ~/.ssh/config

This script performs several critical functions:

  1. It generates a new Ed25519 SSH key with a date-based filename, making it easy to identify when the key was created. The $KEY_PASSPHRASE environment variable should be set before running the script to provide a secure passphrase for the key.
  2. It uses Ansible’s authorized_key module to add the new public key to all servers defined in your Ansible inventory. This ensures consistent deployment across your infrastructure.
  3. It tests the new key against each server listed in server_list.txt, verifying that authentication works properly before removing the old key.
  4. It creates a separate script (remove_old_keys.sh) that will remove the old key from all servers. This script is not executed automatically, giving you time to confirm that the new key works properly across all systems.
  5. Finally, it updates your SSH config file to use the new key by default.

This approach follows the principle of “make before break” – ensuring new access is working before removing old access. This prevents accidental lockouts during key rotation. Run this script at regular intervals (monthly or quarterly) as part of your security maintenance routine.

Step 4: Enforce Key Standards with Git Hooks

For teams using Git to manage infrastructure as code, pre-commit hooks provide an excellent way to enforce security standards before code is deployed. This script prevents common SSH key security mistakes from making their way into your repositories.

#!/bin/bash
# pre-commit hook to check SSH key security

# Check for plaintext private keys
if git diff --cached --name-only | xargs grep -l "BEGIN.*PRIVATE KEY" > /dev/null; then
  echo "ERROR: Potential private key committed to repository"
  exit 1
fi

# Check for weak authorized keys (no constraints)
AUTH_KEYS=$(git diff --cached --name-only | grep -E "authorized_keys|\.pub$")
for key_file in $AUTH_KEYS; do
  if grep -v "from=" "$key_file" > /dev/null; then
    echo "WARNING: SSH key in $key_file has no source IP restriction"
  fi
  
  if grep -v "command=" "$key_file" > /dev/null; then
    echo "WARNING: SSH key in $key_file has no forced command restriction"
  fi
done

exit 0

This Git pre-commit hook performs two important security checks:

  1. It scans all files being committed for strings that look like SSH private keys (containing “BEGIN PRIVATE KEY”). If found, it blocks the commit entirely. Private keys should never be stored in repositories, as this creates a significant security risk.
  2. It identifies any authorized_keys files or public key files (.pub) being committed and checks whether they include important security constraints:
    • The from= constraint, which restricts which IP addresses can use the key
    • The command= constraint, which restricts what commands can be run with the key

If these constraints are missing, the script issues warnings to alert the developer. Depending on your security requirements, you could modify this to block the commit entirely.

To install this hook, save it as .git/hooks/pre-commit in your repository and make it executable with chmod +x .git/hooks/pre-commit. For team-wide deployment, consider using a tool like Husky or pre-commit to manage Git hooks across all developer workstations.

Step 5: Set Up Key Monitoring

Even with robust key management processes in place, unauthorized changes can still occur. This monitoring script provides an additional layer of security by alerting you to unexpected modifications to authorized keys files.

#!/bin/bash
# monitor_ssh_keys.sh

for user_home in /home/*; do
  username=$(basename "$user_home")
  auth_keys_file="$user_home/.ssh/authorized_keys"
  
  if [ -f "$auth_keys_file" ]; then
    # Calculate hash of current authorized_keys file
    current_hash=$(sha256sum "$auth_keys_file" | awk '{print $1}')
    stored_hash_file="/var/cache/ssh_keys_${username}.hash"
    
    if [ -f "$stored_hash_file" ]; then
      stored_hash=$(cat "$stored_hash_file")
      
      if [ "$current_hash" != "$stored_hash" ]; then
        echo "ALERT: SSH authorized_keys changed for user $username" 
        diff <(cat "$auth_keys_file") <(echo "$stored_hash" | xargs -I{} grep -A 1000 {} /var/cache/ssh_keys_backup_${username})
        # Send alert via your preferred notification system
      fi
    fi
    
    # Store current state for future comparison
    echo "$current_hash" > "$stored_hash_file"
    cp "$auth_keys_file" "/var/cache/ssh_keys_backup_${username}"
  fi
done

This script implements a simple but effective file integrity monitoring system specifically for SSH authorized_keys files:

  1. It iterates through each user’s home directory, checking for the presence of an .ssh/authorized_keys file.
  2. For each file found, it calculates a SHA-256 hash, which serves as a digital fingerprint of the file’s contents.
  3. It compares this hash against a previously stored hash (if available) from the /var/cache/ssh_keys_${username}.hash file.
  4. If the hashes don’t match, indicating the file has changed, it:
    • Generates an alert message
    • Produces a diff showing exactly what changed in the file
    • Can be configured to send notifications via your preferred method (email, Slack, etc.)
  5. Finally, it updates the stored hash and creates a backup of the current file state for future reference.

This approach catches unauthorized additions to authorized_keys files, which could indicate an attacker attempting to establish persistence by adding their own key.

Set this script to run as a daily cron job:

# Add to /etc/crontab
0 0 * * * root /usr/local/bin/monitor_ssh_keys.sh >> /var/log/ssh_key_monitor.log 2>&1

For more robust monitoring, consider integrating this with your existing security information and event management (SIEM) system or a file integrity monitoring tool like AIDE or Tripwire.

Advanced Key Security Techniques

While basic key management practices like rotation and inventory tracking provide a solid foundation, sophisticated attackers continuously develop new techniques to compromise SSH keys. Organizations handling sensitive data or facing targeted threats should consider implementing more advanced protections.

The techniques described below may require additional technical investment but provide significantly enhanced security for high-value systems. Not every organization needs to implement all of these measures, but understanding these options helps you make informed decisions based on your specific threat model.

Implement Key Constraints

For service accounts or automated processes, it’s vital to restrict the scope of SSH keys to minimize damage if they’re compromised. OpenSSH provides several powerful constraints that can be applied to authorized keys:

from="10.0.0.0/8,192.168.1.0/24",command="/usr/local/bin/backup_script.sh",no-port-forwarding,no-X11-forwarding ssh-ed25519 AAAAC3Nz... [email protected]

This example demonstrates several important security constraints:

  1. from="10.0.0.0/8,192.168.1.0/24": Restricts the key to only work when the connection originates from specific IP ranges (in this case, private IP addresses in the 10.0.0.0/8 and 192.168.1.0/24 networks). This prevents an attacker who steals the key from using it outside your network.
  2. command="/usr/local/bin/backup_script.sh": Forces the SSH session to execute only this specific command, regardless of what command the user attempts to run. This is perfect for automation tasks where the key should only perform a single function.
  3. no-port-forwarding,no-X11-forwarding: Disables SSH tunneling features that could be used to bypass network restrictions.

Additional useful constraints include:

  • no-agent-forwarding: Prevents forwarding the SSH agent connection
  • no-pty: Prevents allocation of a pseudo-terminal, useful for keys that only run specific commands
  • permitopen="host:port": Restricts port forwarding to specific destinations
  • expiry-time="YYYYMMDD": Sets an expiration date for the key (requires OpenSSH 9.7+)

For any key that doesn’t need full shell access, applying these constraints provides significant security benefits. They’re especially important for service accounts, automation scripts, and any situation where the key might be stored on multiple systems.

Use ssh-agent with Security Extensions

The SSH agent provides a convenient way to use SSH keys without entering your passphrase for each connection. However, this convenience can become a security liability if not configured properly. Modern Linux distributions support enhanced ssh-agent security features that strike a better balance between usability and security:

# Limit how long keys are held in memory
ssh-add -t 4h ~/.ssh/id_ed25519

# Confirm each use of your key
ssh-add -c ~/.ssh/id_ed25519

# Use PKCS11 tokens for hardware-backed keys
ssh-add -s /usr/lib/opensc-pkcs11.so

Let’s examine each of these options:

  1. ssh-add -t 4h: The -t flag sets a time limit (in this case, 4 hours) after which the key will be automatically removed from the agent. This reduces the window of opportunity for an attacker if they gain access to your session. Without this flag, keys remain in the agent until it’s stopped or the system reboots.
  2. ssh-add -c: The -c flag enables confirmation mode, which prompts you each time your key is used for authentication. This provides an additional layer of protection against unauthorized use of your keys by malicious processes or if your session is hijacked.
  3. ssh-add -s: The -s flag loads keys from a PKCS#11 provider, typically a hardware security token like a YubiKey, Smart Card, or TPM. This keeps your private key material on the secure hardware device, where it cannot be extracted or copied.

For the highest level of security, consider using a hardware security key like a YubiKey that supports the FIDO2 standard. These keys can be used directly with SSH starting with OpenSSH 8.2:

# Generate a FIDO2 key that requires physical presence to use
ssh-keygen -t ed25519-sk -O resident -f ~/.ssh/id_ed25519_sk

# Add to ssh-agent (still requires touching the device for each use)
ssh-add ~/.ssh/id_ed25519_sk

This configuration ensures that even if an attacker gains access to your system, they cannot use your SSH keys without physical access to your hardware security token.

Audit Key Usage with Enhanced Logging

Proper logging is essential for detecting unauthorized access and understanding how SSH keys are being used in your environment. By configuring more verbose SSH logging, you can track key usage and identify potential security issues:

# Add to /etc/ssh/sshd_config
LogLevel VERBOSE

With this configuration, the SSH server will log detailed information about each authentication attempt, including the fingerprint of the key used. This allows you to correlate key usage with your inventory and identify unauthorized keys.

To extract key usage information from your logs, you can use a simple command:

grep "authentication" /var/log/auth.log | grep "publickey" | awk '{print $9, $11, $13}'

This command:

  1. Searches for authentication events in the auth.log file
  2. Filters for events related to public key authentication
  3. Extracts the username, source IP, and key fingerprint from each log entry

For more comprehensive analysis, you can parse these logs into a database or SIEM system. This allows you to:

  • Generate reports of which keys are actively being used
  • Identify keys that haven’t been used in months (candidates for removal)
  • Detect unusual access patterns, such as a key being used from an unexpected location
  • Track when specific keys are being used outside of normal hours

Consider extending this monitoring to include alerts for suspicious activity, such as:

  • Failed authentication attempts using valid usernames
  • Successful authentication from unusual geographic locations
  • Multiple rapid authentication attempts from different source IPs
  • Authentication attempts during off-hours

By combining enhanced logging with active monitoring, you create a comprehensive view of SSH key usage across your environment, making it easier to detect and respond to potential security incidents.

Real-world Implementation Strategy

Transitioning from unmanaged SSH keys to a fully automated system requires careful planning. For most organizations, a phased approach works best:

  1. Assessment Phase: Begin by creating an inventory of existing SSH keys and identifying which systems they access. This baseline understanding is critical before making any changes.
  2. Policy Development: Establish clear policies for key generation, rotation, and access requirements. Document these policies and ensure they align with your broader security standards.
  3. Tool Selection: Based on your environment size and complexity, select appropriate tools. For smaller environments, custom scripts may suffice. Larger organizations should consider dedicated solutions like HashiCorp Vault, Teleport, or Smallstep.
  4. Pilot Implementation: Select a non-critical subset of systems and users to pilot your new key management approach. This allows you to refine processes before wider deployment.
  5. Gradual Rollout: Implement the new system gradually across your infrastructure, carefully validating access at each stage. Begin with development environments before moving to production.
  6. Education and Training: Ensure all users understand the new processes, particularly aspects that differ from their previous workflow. Emphasize the security benefits to encourage adoption.
  7. Continuous Improvement: Regularly audit and test your key management system, looking for opportunities to enhance security or reduce friction.

Remember that perfect security often comes at the expense of usability. The most successful SSH key management strategies balance security requirements with operational needs, creating a system that administrators will actually use consistently.

Conclusion

SSH key management doesn’t have to be a manual, error-prone process. By implementing the automation techniques described in this article, Linux administrators can significantly improve security while reducing the operational burden of key management.

The key aspects of modern SSH security go beyond the basic recommendations to “use long keys” and “disable password authentication.” While those practices are important, they’re just the starting point. True SSH security requires treating keys as sensitive credentials that need lifecycle management, monitoring, and regular rotation.

For organizations just beginning their journey toward better SSH key management, start small. Focus first on inventorying existing keys and implementing basic monitoring. These steps alone will provide visibility into your current security posture and highlight areas for improvement. From there, gradually implement key rotation and more advanced techniques like certificate-based authentication.

The security landscape continues to evolve, with attackers constantly developing new techniques to compromise systems. By treating SSH keys as temporary credentials that require regular rotation and careful monitoring—not permanent access tokens that can be set and forgotten—you’ll eliminate one of the most common vectors for long-term unauthorized access.

In our next article, we’ll explore how to integrate these SSH key management practices with common identity providers like LDAP, Active Directory, and OAuth2, creating a fully centralized authentication system for your Linux environment that aligns SSH access with your organization’s broader identity strategy.

Secure Your Infrastructure Today!

Sign up now to gain comprehensive insights into your SSH access logs. Start monitoring, alerting, and analyzing your entire infrastructure effortlessly.
Get started for free

Book a demo

Fill in the form below to book a demo without obligation.
Request a demo