HomeTopicsSSH Key Management › Authorized Keys

Distributing and Controlling authorized_keys

Every SSH key that can log into your server is listed in a plain text file, one key per line. That file is called authorized_keys, and it is the entire server-side gate: a public key on the list gets in, a public key not on the list does not. There is no central database behind it, no expiry clock inside it, and — by default — nothing stopping the file from quietly accumulating lines for years. Whoever controls that file controls access to the account.

That makes authorized_keys the most consequential small file a transfer administrator manages, and this article treats it with matching seriousness. You will learn how the server actually uses the file, how to add keys without the classic silent failures, how to attach restriction options that limit what each key may do — the difference between "this key can do anything the account can" and "this key can run one transfer from one address" — and how to keep the file under change control so access grants stop happening informally. This is part of our SSH Key Management series; it assumes you know what a key pair is (SSH keys explained) and have a properly generated key in hand (generating and storing keys).

What the File Is and How the Server Uses It

On OpenSSH servers, each account has its own list at ~/.ssh/authorized_keys — that is, inside the home directory of the account being logged into. When a client offers a public key during login, the server scans that account's file line by line looking for a match. If it finds one, it issues the challenge; if the client proves possession of the matching private key, the session opens with whatever powers the matched line allows. Three properties follow, and each drives a practice later in this article:

  • The file is per-account, not per-server. A key authorized for transfer-acme says nothing about transfer-globex. This is what makes one-account-per-partner and one-account-per-job patterns enforceable.
  • Presence equals access. There is no approval step beyond the line existing. Adding a line is granting access; deleting it is revoking access (for new sessions — an already-open session continues until it disconnects).
  • Each line can carry its own conditions. Options at the start of a line restrict that one key without affecting the others. Two keys in the same file can have completely different powers.

The anatomy of a line, then, is worth memorizing. Up to four parts, separated by spaces:

[options]  key-type  base64-key-material  comment

# simplest form - no options, full account access for this key:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFa9k2PqT7wXe0cRuY4mHnB1sLdJ8vZoGxN6iQpM3tE5 alex@workstation

# locked-down form - options first, then the same three fields:
restrict,from="203.0.113.40",command="internal-sftp" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ4vN8xW2qYtR0cKe7mPzuA3fLb6dHs9oGiT1jEwQk5r nightly-report@batch01

The key type and key material come straight from the client's .pub file. The comment is free text — the label whose discipline we preached in the generation article, and your best friend during audits. The options are the subject of this article's core section. First, though: getting the line into the file without breaking anything.

Getting a Key Into the File Safely

Between OpenSSH machines, ssh-copy-id is the clean path — it appends the key and creates the folder and file with correct permissions if they are missing:

$ ssh-copy-id -i ~/.ssh/nightly-report.pub transfer-acme@sftp.example.com

When you are adding a key manually — a partner emailed you their public key, say — the append looks like this on the server, logged in as (or switched to) the target account:

$ mkdir -p ~/.ssh && chmod 700 ~/.ssh
$ cat partner-acme.pub >> ~/.ssh/authorized_keys     # note: >> appends
$ chmod 600 ~/.ssh/authorized_keys
$ ssh-keygen -lf ~/.ssh/authorized_keys              # verify what the file now trusts

Four classic failures happen at exactly this step, and every one of them produces the same maddening symptom — key login fails with no useful error on the client:

  • Overwriting instead of appending. A single > where >> was intended replaces every existing key with the new one. You added the partner and revoked everyone else. Always append, and always list the file's fingerprints afterward.
  • A broken line. A public key is one long line. Email clients and chat tools love to wrap it into several. If the key arrived by message, verify its fingerprint after pasting: run ssh-keygen -lf against the file and compare with the fingerprint the sender quotes. A mangled key simply will not match.
  • The wrong account's file. The file consulted is the one in the home directory of the account being logged into — not root's, not yours. Adding a partner's key to your own authorized_keys grants them your access and leaves their login broken.
  • Loose permissions. By default (StrictModes yes), the server ignores the file if it, the .ssh folder, or the home directory is writable by other users. The login then falls back to password or fails outright, with the real reason visible only in the server's own log. Keep the folder at 700, the file at 600, owned by the account.

When key logins fail, read the server log, not the client output — the server knows which of these happened and says so. The client is deliberately vague to avoid helping attackers.

Restriction Options: Limiting What a Key May Do

Here is the fact that turns authorized_keys from a doorman into a policy engine: a bare key line grants everything the account can do — interactive shell, any command, file transfer, port forwarding through your server. For a human administrator that may be intended. For an automation key whose private half sits unencrypted on some batch server (the honest arrangement we defended in the generation article), it is far more power than the job needs — and options exist to take the excess back, per key, right in the file.

Options are a comma-separated list at the start of the line, before the key type. No spaces around the commas. The ones that matter for transfer work:

  • from="pattern" — accept this key only from matching client addresses: from="203.0.113.40", a comma-separated set, wildcards, or address blocks in CIDR form (from="203.0.113.0/24"). A stolen key becomes useless from anywhere else on the internet. Combine with the network view in our hardening transfer servers series.
  • command="..." — a forced command: whatever the client asks to run, the server runs this instead. For transfer accounts, command="internal-sftp" is the important value — it forces the session straight into OpenSSH's built-in SFTP service, so the key can move files but never gets a shell, no matter what the client requests.
  • restrict — the modern deny-by-default switch: turns off port forwarding, agent forwarding, X11, PTY allocation (the interactive terminal), and the rest in one word, so anything not explicitly re-enabled stays off. On older servers without restrict, spell it out with the individual options: no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty.
  • expiry-time — newer OpenSSH releases let a key line carry an expiry timestamp after which it stops working. Support varies with server version, so confirm in man sshd (the AUTHORIZED_KEYS section) before an important workflow depends on it — but where available, it is a clean way to make temporary access genuinely temporary.

Put together, the standard line for an unattended transfer key reads:

restrict,from="203.0.113.40",command="internal-sftp" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ4vN8xW2qYtR0cKe7mPzuA3fLb6dHs9oGiT1jEwQk5r nightly-report@batch01

Read it as a sentence: this key, and only from this address, may do exactly one thing — SFTP — with every other SSH capability switched off. If the batch server is ever compromised and the key stolen, the thief holds a credential that works from one IP address and can only speak SFTP to one account's directories. That is a contained incident instead of a foothold. This per-key containment is the payoff for skipping passphrase theater on automation keys — the security moved from a fake control on the client to a real control on the server.

Option What it restricts Typical transfer use
from="..." Which client addresses may use the key Pin an automation key to its batch server; pin a partner key to their announced range
command="internal-sftp" Replaces any requested command with SFTP service File transfer allowed, shell impossible
restrict Everything optional — forwarding, PTY, and future capabilities — off by default Lead every automation and partner key line with it
no-port-forwarding etc. Individual capabilities, one per option Older servers that lack restrict
expiry-time="..." Key stops working after a set moment Contractor or migration keys that must not outlive the project (where supported)

One boundary to keep clear: per-key options govern that key. Account-wide policy — forcing every session on a group of accounts into SFTP and jailing them to their own directory trees with ForceCommand internal-sftp and ChrootDirectory — lives in the server's configuration, not in authorized_keys. The two layers stack well: account-wide jail plus per-key source pinning is a standard partner setup. The server-side half is covered in SFTP server configuration.

Patterns by Account Type

Human accounts. Admins and developers get ordinary key lines — restrictions that block interactive work would defeat the purpose. The discipline here is different: every line carries a comment identifying its human and machine (alex@workstation, alex@laptop), one line per device, and lines leave the file the day the person or device does. A human account's file with six anonymous keys is not a convenience; it is six unanswered questions.

Service accounts. One account per job or workflow, one key per account, every line beginning restrict,from= and — for transfer jobs — command="internal-sftp". The account itself should be unprivileged and own only the directories the job touches. Broader service-account practice (naming, ownership, documentation) is covered in our transfer authentication series.

Partner-facing accounts. Each external partner gets their own account, never a shared "partners" login — per-account files are what make one partner's offboarding a one-account cleanup. The distribution flow runs in reverse of what beginners expect: the partner generates their own pair, keeps their private key, and sends you only the public key. You confirm its fingerprint through a different channel than the key arrived by (they email the key, you compare fingerprints on a call — the point is that no single intercepted channel can substitute a key), then install it with full restrictions: restrict,from="<their announced addresses>",command="internal-sftp". If a partner offers to send "the key" and attaches a private key, stop them and explain — you have just learned their key hygiene needs help.

Remember: you should never need to receive anyone's private key, and never need to send yours. Every correct distribution flow moves public keys only. A private key in an email attachment means the sender's copy is now compromised by definition — generate a fresh pair and start over.

Keeping the File Under Change Control

An authorized_keys edit is an access-control change with the same blast radius as a firewall rule, and it deserves the same ceremony. Informal edits — a key pasted in during an outage, a "temporary" grant for a contractor — are how servers end up trusting keys nobody can explain. Three practices keep the file honest:

  • Route every change through your inventory. Additions and removals happen because a recorded request said so, and the key inventory — fingerprint, owner, purpose, restrictions — is updated in the same motion. The inventory habit is the subject of key rotation and the key inventory.
  • Compare reality against a known-good copy. Keep a reference copy of each account's file (in configuration management, or simply a protected archive) and diff the live file against it on a schedule. Any unexplained line is an incident until explained — the audit mindset detailed in retiring keys and offboarding.
  • Take the file away from users where it matters. By default, any user can append to their own file — meaning anyone who ever gets in once can grant themselves permanent access by adding a second key. On servers where that risk is unacceptable (partner-facing transfer servers, above all), move the file somewhere root-owned with the AuthorizedKeysFile directive in sshd_config, for example AuthorizedKeysFile /etc/ssh/authorized_keys/%u (the %u expands to each username). Now only administrators grant access, and account compromise no longer mints permanent credentials.

Windows Servers and GUI-Managed Keys

The same model wears different clothes off of Linux. On Windows OpenSSH, the per-account file exists in each profile's .ssh folder as usual, with one sharp edge worth knowing: for accounts in the Administrators group, the server instead consults a shared administrators_authorized_keys file in the ProgramData ssh folder — a detail that has cost many admins an afternoon. Dedicated Windows SFTP servers manage the trust list through configuration instead of a dotfile: in Sysax Multi Server, you attach each user's public key to their account in the server's configuration interface, which plays the role authorized_keys plays on OpenSSH — the server stores public halves per account and challenges connecting clients against them.

Whatever the storage mechanism, carry the same discipline across: one account per partner or job, a recorded fingerprint and owner for every stored key, and removals executed the day access should end. The file format was never the point; the control was.

Verifying What Access Currently Exists

Because presence equals access, "who can log in here?" is answered by listing the file — and ssh-keygen -lf conveniently fingerprints every line at once:

$ ssh-keygen -lf ~/.ssh/authorized_keys
256 SHA256:Yq8kT3n0Wb1xR7pVd2mAzcE9fH6uJ4sLgN5oPiQwXk0 nightly-report@batch01 (ED25519)
3072 SHA256:hR2mV8wQx1pZk4tN7yLbJc0aFdX9sEo6gUiK5nPqM3w acme-upload@partner (RSA)

# every account on the server, as root:
# for f in /home/*/.ssh/authorized_keys; do echo "== $f"; ssh-keygen -lf "$f"; done

Each fingerprint should match a row in your inventory; each comment should still describe a live job, device, or partner. Run this check on a schedule and after any personnel or partner change — five minutes that regularly surfaces keys whose reason for existing has quietly ended.

The Gate, Summarized

The compressed version to keep: authorized_keys is per-account, one key per line, and presence equals access. Append, never overwrite; verify with fingerprints; keep 700/600 permissions or the server will silently ignore the file. A bare line grants the account's full power, so automation and partner keys get restrict,from=...,command="internal-sftp" as their default shape — containment written directly into the grant. Treat every edit as an access-control change: recorded, inventoried, and periodically compared against reality.

From here, the series continues with the habits that keep the trust list truthful over time — key rotation and the key inventory — and the day the discipline pays off, retiring keys when people and systems leave. For the client half of authentication choices on transfer servers, see SFTP authentication.

Frequently Asked Questions

If I delete a key line, is access revoked immediately?
For new logins, yes — the file is consulted at each authentication, so the next attempt fails. Sessions already open when you deleted the line keep running until they disconnect. If you need an active session gone too, terminate it on the server after removing the line.
Can one authorized_keys file hold multiple keys?
Yes — one key per line, as many lines as needed, and each line can carry its own options. A human account often lists one key per device; a well-run service account should normally hold exactly one, plus temporarily a second during rotation overlap.
I added a key but the server still asks for a password. Why?
Usually one of four things: the key was appended to the wrong account's file, the long key line got wrapped or truncated in transit, permissions are too loose so the server is ignoring the file entirely, or the client is offering a different key than you installed. The server's own log states the real reason — check it before anything else.
How do I make a key that can transfer files but never get a shell?
Prefix its line with a forced command: restrict,command="internal-sftp" (plus a from= source pin). Whatever the client requests, the server runs only its internal SFTP service, so file operations work and interactive sessions are impossible. For whole accounts that should be SFTP-only, set the equivalent policy server-wide in sshd_config.
What is the difference between authorized_keys and known_hosts?
They are mirror images. authorized_keys lives on the server and lists client public keys the server will accept — it answers "who may log into me?" known_hosts lives on the client and lists server public keys the client recognizes — it answers "am I talking to the real server?" Our host keys article covers the second file.
Can users grant themselves access by editing their own file?
By default, yes — the file sits in their home directory and they can append to it, which also means anyone who compromises the account once can add a permanent key. Where that matters, relocate the file to a root-owned path with the AuthorizedKeysFile directive so only administrators can grant access.

From the Sysax team: we build secure file transfer software for Windows — Sysax Multi Server, an FTP, FTPS, SFTP, and HTTPS server, and Sysax FTP Automation for scheduled, scripted transfers. Free trials are on the download page.