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

推荐订阅源

The Last Watchdog
The Last Watchdog
C
Cyber Attacks, Cyber Crime and Cyber Security
L
LINUX DO - 热门话题
G
GRAHAM CLULEY
S
Schneier on Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
S
SegmentFault 最新的问题
IT之家
IT之家
阮一峰的网络日志
阮一峰的网络日志
Recorded Future
Recorded Future
I
Intezer
云风的 BLOG
云风的 BLOG
博客园 - Franky
月光博客
月光博客
大猫的无限游戏
大猫的无限游戏
T
Tenable Blog
The Hacker News
The Hacker News
T
The Blog of Author Tim Ferriss
Attack and Defense Labs
Attack and Defense Labs
D
DataBreaches.Net
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
N
News and Events Feed by Topic
有赞技术团队
有赞技术团队
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
N
News and Events Feed by Topic
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
S
Secure Thoughts
The Register - Security
The Register - Security
B
Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
The Cloudflare Blog
Webroot Blog
Webroot Blog
W
WeLiveSecurity
H
Heimdal Security Blog
博客园 - 三生石上(FineUI控件)
V
Vulnerabilities – Threatpost
G
Google Developers Blog
O
OpenAI News
V
V2EX
罗磊的独立博客
博客园_首页
N
News | PayPal Newsroom
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
TaoSecurity Blog
TaoSecurity Blog
Cloudbric
Cloudbric
H
Hacker News: Front Page
博客园 - 叶小钗
T
Tor Project blog
AI
AI

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 Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
SSH Key Management at Scale: Generating, Rotating, and Revoking Keys Across Teams
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.