惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
DataBreaches.Net
GbyAI
GbyAI
aimingoo的专栏
aimingoo的专栏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
月光博客
月光博客
大猫的无限游戏
大猫的无限游戏
M
MIT News - Artificial intelligence
腾讯CDC
博客园 - Franky
Engineering at Meta
Engineering at Meta
C
Check Point Blog
T
The Blog of Author Tim Ferriss
有赞技术团队
有赞技术团队
Microsoft Azure Blog
Microsoft Azure Blog
MyScale Blog
MyScale Blog
I
InfoQ
Blog — PlanetScale
Blog — PlanetScale
P
Proofpoint News Feed
The GitHub Blog
The GitHub Blog
N
Netflix TechBlog - Medium
Last Week in AI
Last Week in AI
S
SegmentFault 最新的问题
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
WordPress大学
WordPress大学

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
SSH Key Management at Scale: Generating, Rotating, and Re...
Mahafuzur Rahaman · 2026-05-29 · via DEV Community

Most teams treat SSH keys like passwords from 2010 — created once, never rotated, and scattered everywhere. Here's how to fix that.


You onboard a new engineer. They generate an SSH key, paste the public key into five servers, and get to work. Six months later they leave the company. You remember to remove their key from two of the five servers. Maybe three.

This is how breaches happen. Not through sophisticated attacks — through forgotten keys on forgotten servers, quietly waiting.

SSH key management sounds boring until it isn't. This article covers everything you need to do it properly: key generation best practices, how to organize keys across teams, rotation strategies that won't break production, and clean revocation when someone leaves.


Why SSH Key Management Breaks Down

SSH keys feel low-maintenance because they mostly work silently. That silence is the problem.

Unlike passwords, keys don't expire by default. Unlike OAuth tokens, there's no central dashboard showing you who has access to what. Unlike certificates, there's no built-in revocation mechanism.

The result is what security teams call key sprawl: hundreds of authorized_keys entries across dozens of servers, with no inventory, no ownership records, and no expiry dates. Surveys consistently find that large organizations have more SSH keys than employees — often by an order of magnitude.

Key sprawl creates three risks:

  • Orphaned access — keys belonging to former employees, contractors, or decommissioned systems still granting entry
  • Unknown exposure — no one knows which keys can reach which servers
  • Audit failure — you can't prove compliance if you can't show who had access to what, when

The fix isn't a new tool. It's a discipline — applied consistently.


Part 1: Generating Keys the Right Way

Choose the Right Algorithm

Not all SSH key types are equal in 2024. Here's where things stand:

Algorithm Key Size Recommendation
ed25519 256-bit (fixed) ✅ Use this. Fast, secure, compact.
ecdsa 256/384/521-bit ⚠️ Fine, but ed25519 is better
rsa 2048–4096-bit ⚠️ Legacy systems only. Use 4096-bit minimum.
dsa 1024-bit ❌ Never. Broken and disabled in modern OpenSSH.

For anything modern, ed25519 is the answer.

ssh-keygen -t ed25519 -a 100 -C "alice@example.com" -f ~/.ssh/id_ed25519

Flags explained:

  • -t ed25519 — algorithm
  • -a 100 — number of KDF rounds for the passphrase (higher = slower to brute-force)
  • -C "alice@example.com" — comment; use email or a descriptive label
  • -f ~/.ssh/id_ed25519 — output file path

For legacy systems that only accept RSA:

ssh-keygen -t rsa -b 4096 -a 100 -C "alice@example.com" -f ~/.ssh/id_rsa_legacy

Always Use a Passphrase

A passphrase encrypts the private key on disk. Without it, anyone who copies your key file has full access to everything that key unlocks. With a passphrase, they also need to know the secret to decrypt it.

The common objection: "but then I have to type it every time." The answer: ssh-agent.

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

ssh-agent holds your decrypted key in memory for the session. You type the passphrase once; the agent handles the rest. On macOS, keychain integration means you only type it once per login — or per reboot.

One Key Per Context, Not One Key for Everything

A single key that unlocks every server is a single point of failure. Instead, scope keys to contexts:

~/.ssh/
├── id_ed25519_personal       # Personal projects
├── id_ed25519_work           # Work infrastructure
├── id_ed25519_client_acme    # Client: ACME Corp
├── id_ed25519_deploy         # CI/CD deploy key (no passphrase, scoped permissions)
└── id_ed25519_prod           # Production servers (extra strong passphrase)

Wire these up in ~/.ssh/config so the right key is used automatically:

Host *.acme.internal
    IdentityFile ~/.ssh/id_ed25519_client_acme
    IdentitiesOnly yes

Host bastion.prod.example.com
    IdentityFile ~/.ssh/id_ed25519_prod
    IdentitiesOnly yes

IdentitiesOnly yes prevents SSH from trying other keys in your agent — important when servers have MaxAuthTries set low.


Part 2: Organizing Keys Across a Team

The Baseline: A Git-Managed Key Registry

For small to medium teams (under ~50 engineers), a Git repository containing public keys and server manifests is a practical starting point.

ssh-keys/
├── users/
│   ├── alice.pub
│   ├── bob.pub
│   └── carol.pub
├── servers/
│   ├── web-prod.txt       # Lists which users have access
│   ├── db-prod.txt
│   └── bastion.txt
└── deploy-keys/
    ├── github-actions.pub
    └── jenkins.pub

Rules:

  • Public keys only — never commit private keys
  • Every key has an owner and a date in a comment: ssh-ed25519 AAAA... alice@example.com 2024-01
  • PRs required to add or remove keys — creates an audit trail
  • A simple script syncs authorized_keys on servers from the registry

This isn't enterprise-grade, but it's infinitely better than ad-hoc key distribution with no inventory.

Structuring authorized_keys With Restrictions

authorized_keys supports per-key restrictions that limit what a key can do, even after it's been granted access. Use them.

# Full access
ssh-ed25519 AAAA... alice@example.com

# Read-only deploy key — can only run one specific command
command="/usr/local/bin/deploy.sh",no-pty,no-agent-forwarding,no-x11-forwarding ssh-ed25519 AAAA... deploy-key

# Tunnel-only key — can only forward one specific port
restrict,port-forwarding,permitopen="db.internal:5432" ssh-ed25519 AAAA... tunnel-key

# IP-restricted key
from="203.0.113.0/24" ssh-ed25519 AAAA... office-access-key

These restrictions are enforced server-side, regardless of what the client attempts.

Tools for Larger Teams

Once you're managing keys across dozens of servers and dozens of engineers, manual management doesn't scale. Consider:

HashiCorp Vault SSH Secrets Engine
Vault can act as an SSH Certificate Authority, issuing signed, short-lived certificates instead of static keys. Engineers authenticate to Vault, receive a certificate valid for (say) 8 hours, and use it to access servers. No long-lived keys. No key sprawl. Full audit log. This is the gold standard for larger teams.

Teleport
Open-source access plane for SSH, Kubernetes, and databases. Handles key/certificate lifecycle, session recording, and access policies in one tool.

AWS EC2 Instance Connect / GCP OS Login
Cloud-native solutions that push temporary public keys to instances for the duration of a connection. No persistent authorized_keys at all.

Smallstep
Open-source certificate authority with SSH support. Easier to self-host than Vault if certificates are the only goal.


Part 3: Rotation — The Step Most Teams Skip

Key rotation means replacing existing keys with new ones on a scheduled basis. It limits the exposure window if a key is compromised without your knowledge.

When to Rotate

  • Scheduled: Annually at minimum, quarterly for sensitive systems
  • Triggered: After a security incident, after a team member's access level changes, after a laptop is lost or stolen, after a suspected compromise
  • On offboarding: Always — see Part 4

How to Rotate Without Breaking Things

Rotation fails when it's done carelessly. The safe approach is additive first, then remove.

Step 1: Generate the new key

ssh-keygen -t ed25519 -a 100 -C "alice@example.com-2024-rotation" -f ~/.ssh/id_ed25519_new

Step 2: Add the new key alongside the old one

cat ~/.ssh/id_ed25519_new.pub | ssh user@server "cat >> ~/.ssh/authorized_keys"

Step 3: Verify the new key works

ssh -i ~/.ssh/id_ed25519_new user@server "echo connected"

Step 4: Remove the old key

# On the server, edit ~/.ssh/authorized_keys and delete the old key's line
ssh -i ~/.ssh/id_ed25519_new user@server "sed -i '/OLD_KEY_COMMENT/d' ~/.ssh/authorized_keys"

Step 5: Update all references~/.ssh/config, CI/CD secrets, documentation.

Automating Rotation at Scale

For many servers, do this with Ansible:

- name: Add new SSH key
  authorized_key:
    user: "{{ item.user }}"
    key: "{{ lookup('file', 'keys/new/{{ item.user }}.pub') }}"
    state: present
  loop: "{{ team_members }}"

- name: Remove old SSH key
  authorized_key:
    user: "{{ item.user }}"
    key: "{{ lookup('file', 'keys/old/{{ item.user }}.pub') }}"
    state: absent
  loop: "{{ team_members }}"

Run the "add" play first, verify access, then run the "remove" play. Never both in a single run without testing in between.


Part 4: Revocation — When Someone Leaves

This is where key management most visibly fails. An engineer leaves; their key stays. Weeks or months later, an audit finds it still granting access to production systems.

The Offboarding Checklist

When anyone loses access (resignation, termination, end of contract):

[ ] Identify all keys belonging to this person
[ ] List all servers and services they had access to
[ ] Remove keys from all authorized_keys files
[ ] Rotate any shared/service account keys they had access to
[ ] Revoke access to key management tools (Vault, etc.)
[ ] Remove from any team-level access groups
[ ] Document the revocation with timestamp

The hardest part is step two: knowing everywhere they had access. This is why the Git-managed key registry matters — it's your inventory.

Doing It Fast With Ansible

# Revoke a specific user's key everywhere
ansible all -m authorized_key -a "user=ubuntu key='{{ lookup('file', 'keys/alice.pub') }}' state=absent"

Run against your entire inventory. Done in seconds.

Using authorized_keys Comments as Metadata

Make revocation easier by putting searchable metadata in key comments:

ssh-ed25519 AAAA... alice@example.com|team:backend|added:2024-01-15|expires:2025-01-15

A simple script can scan all authorized_keys files and flag keys past their expiry date — giving you automated rotation reminders and an audit trail.


Part 5: Auditing What You Have

Before you can manage your keys, you need to know what exists.

Scan Your Servers

# Find all authorized_keys files on a server
find /home /root -name "authorized_keys" 2>/dev/null

# List all keys with their fingerprints
while read key; do
    echo "$key" | ssh-keygen -l -f -
done < ~/.ssh/authorized_keys

Inventory Your Local Keys

# List all key fingerprints in your local .ssh directory
for key in ~/.ssh/*.pub; do
    echo -n "$key: "
    ssh-keygen -l -f "$key"
done

Check Key Age

If your keys have date metadata in comments, a quick grep tells you what's overdue:

grep -r "authorized_keys" /home/*/  | awk -F'|' '/expires/ {print $4, $0}' | sort


The One Habit That Changes Everything

Audit your SSH keys on a schedule. Put it in the calendar. Once a quarter, run through every server, list every authorized key, verify every key has a known owner, and remove anything that doesn't.

It takes an hour. It's the single highest-value SSH security activity most teams never do.

The goal isn't a perfect system from day one — it's incremental improvement: better key generation today, an inventory this week, automated revocation next month.

SSH key management isn't exciting. But discovering a former employee's key on a production database server at 2 AM definitely is.


Found this useful? Follow for more practical deep-dives into security and infrastructure fundamentals.