DevTools Hub

Search tools

Search for a developer tool

authorized_keys Explained

Part of the Encryption Toolkit

Every server that accepts SSH key logins has a file — usually ~/.ssh/authorized_keys — listing exactly which public keys are allowed to authenticate as that account. It looks simple: one key per line. In practice it accumulates keys the same way permissions and firewall rules accumulate cruft, and the options that can restrict what each individual key is allowed to do are easy to never learn about at all, since the file works fine without them. This post covers the format in full, the restriction options most people never set, and the mistakes that show up once a file has more than a couple of entries.

The anatomy of one line

A complete authorized_keys entry has up to three parts, in order:

[options] algorithm base64-key [comment]

The middle two are the actual key — the same algorithm base64-key pair a .pub file contains, whether that's ssh-ed25519, ssh-rsa, or one of the ecdsa-sha2-* variants. The comment at the end is a free-text label, usually user@host, carrying no cryptographic weight — its only job is helping a human tell keys apart once there's more than one in the file. The options at the front are the part most authorized_keys files never use at all, and the part that actually controls what a given key is allowed to do beyond "log in."

The restriction options worth knowing

  • from="10.0.0.0/24,203.0.113.5" — restricts this key to connecting only from the listed source addresses or CIDR ranges. A stolen key that can only be used from inside a specific network is a meaningfully smaller problem than one usable from anywhere.
  • command="/usr/local/bin/deploy.sh" — forces this key to run exactly one command, ignoring whatever command the connecting client actually requested. This is how a single-purpose deploy key or backup key gets scoped to do only its one job, even if the private key is later used to connect interactively.
  • restrict — a shorthand (added in OpenSSH 7.2) that disables port forwarding, agent forwarding, X11 forwarding, and pty allocation all at once, rather than listing each no-* flag individually. The individual flags (no-agent-forwarding, no-port-forwarding, no-pty, no-X11-forwarding) still work and are equivalent to what restrict bundles together.
  • permitopen="internal-db:5432" — when port forwarding is allowed at all, restricts which specific host and port the key is allowed to forward to, rather than granting unrestricted tunneling.

Combine several with commas: from="10.0.0.0/24",command="/usr/local/bin/deploy.sh",no-agent-forwarding ssh-ed25519 AAAA... — a key that can only connect from one network, can only run one script, and can't use agent forwarding to reach anywhere else from there.

Why unrestricted keys aren't automatically wrong

Plenty of legitimate personal keys carry no restrictions at all, and that's fine — a developer's own key logging into their own account doesn't typically need from= or command= to be reasonable. The case where it's worth a second look is service and automation accounts: a CI runner's deploy key, a backup script's key, anything whose entire legitimate purpose is one narrow task. Those are exactly the keys where command= and from= cost nothing to add and meaningfully limit what a leaked credential could actually do.

What accumulates in a real file over time

A shared or long-lived account's authorized_keys file tends to pick up the same kinds of issues a firewall rule set or a sudoers file does: duplicate entries (the same key added twice by different onboarding scripts), keys with no comment at all (nobody can tell whose it is anymore, so nobody wants to be the one to remove it), and old keys using deprecated algorithms — ssh-dss in particular, disabled by default in OpenSSH since version 7.0, sometimes still sitting in a file from years ago without anyone noticing it stopped mattering.

None of these break anything on their own — an unused duplicate key doesn't cause a malfunction, it just sits there as unnecessary attack surface and unnecessary confusion for whoever eventually has to clean the file up.

A worked example

A realistic file for a small team's shared deploy account might look like:

# Alice — personal laptop
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... alice@laptop

# CI runner — restricted to the office network, one command only
from="203.0.113.0/24",command="/usr/local/bin/deploy.sh",no-agent-forwarding,no-pty ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... ci-deploy

Alice's personal key is unrestricted, matching how she actually uses the account — interactively, from wherever she happens to be working. The CI key is scoped tightly to exactly what automation needs and nothing more, which is the meaningful distinction: not every key needs restrictions, but the ones that plausibly could get exfiltrated from a less-trusted environment benefit the most from having them.

At scale, SSH certificates replace the file entirely

Everything above assumes managing individual public keys directly, which becomes a real operational burden past a certain number of servers and people — adding a new team member means touching every server's authorized_keys file, and offboarding someone means finding and removing their key from all of them, reliably, every time. SSH certificates (a distinct concept from TLS certificates, despite the shared name) solve this by having a trusted certificate authority sign short-lived certificates for each user's key; servers then trust the CA once (via TrustedUserCAKeys in sshd_config) instead of maintaining a per-server list of individual keys at all. It's a meaningfully larger operational setup than a plain authorized_keys file, and worth reaching for specifically once the overhead of the file-per-server model — not the file format itself — becomes the actual problem.

The permissions the file itself needs

OpenSSH refuses to trust authorized_keys at all if it — or its containing .ssh directory, or the home directory above that — is writable by anyone other than its owner. This isn't a suggestion; a permissions mistake here makes SSH silently behave as if the file doesn't exist, with no error shown to the connecting client, which makes it a confusing failure to debug the first time it happens:

chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

The usual symptom is a user certain their key is correctly installed, getting rejected anyway with nothing in the client's output suggesting why — worth checking permissions on the file and its parent directories before assuming the key itself is the problem. Linux Permissions Calculator confirms exactly what a given octal or symbolic permission actually grants if the current value is in question.

Common mistakes

  • Never adding restrictions to automation keys. A CI or deploy key usually has one job; scoping it with command= and from= costs one line and meaningfully limits the blast radius of that specific key leaking.
  • Leaving stale keys in the file indefinitely. Without comments identifying whose key is whose, nobody wants to be the person who removes a line that might still be needed — which is exactly why comments matter from the start.
  • Assuming file permissions on authorized_keys itself don't matter. OpenSSH checks that the file (and its containing .ssh directory) aren't writable by anyone but the owner, and silently ignores the file entirely if they are — a permissions mistake here can look like SSH simply forgot every key in the file.

FAQ

Is it safe to share the contents of an authorized_keys file?

The keys themselves, yes — authorized_keys only ever contains public keys, which are meant to be shared. Any options attached to a key, like a command= restriction, might reveal internal details (an internal hostname, a script path) worth considering before posting the file publicly, even though none of it is a secret in the cryptographic sense.

What happens if I put a private key in authorized_keys by mistake?

SSH will reject it — authorized_keys is parsed specifically as a list of public keys, and a private key's format doesn't match what the parser expects. It's not a security check catching the mistake, just a format mismatch, so don't rely on this as a safeguard; the safeguard is never generating or handling this confusion in the first place.

Can one authorized_keys file have keys with different restrictions?

Yes — restrictions are per-line, not file-wide. One line can carry a from= restriction while another in the same file has none at all; each key's own options apply only to that key.

Does the order of keys in the file matter?

No — SSH tries each key in the file until one is accepted or the list is exhausted. Unlike ssh_config's first-match-wins precedence, authorized_keys doesn't stop at the first line; every key gets a chance.

How is authorized_keys different from known_hosts?

They're inverses of each other. authorized_keys, on a server, lists the public keys allowed to log in as a given account. known_hosts, on a client, lists the public keys of servers the client has already verified and trusts. Both are plain-text lists of public keys, serving opposite directions of the same authentication mechanism.

Try it yourself

SSH authorized_keys Auditor checks an entire file at once for exactly the issues covered here — duplicates, missing comments, deprecated algorithms, and keys with no restrictions — entirely in your browser. For how the underlying key authentication works, see SSH Keys Explained.

Related tools