Generating and Storing SSH Keys Properly
Generating an SSH key takes about ten seconds and one command. That is precisely the problem. Because the command is so easy, keys get generated in a hurry, with default names, no labels, no passphrase decision actually decided, and then copied to wherever seemed convenient. Six months later nobody can say which key belongs to which job, why there are four of them in one folder, or whether the copy on the shared drive matters. The ten-second command was fine; the missing ten minutes of thought around it is what turns into cleanup work.
This article is those ten minutes, plus the command. You will generate a key pair the deliberate way: choose the right type without chasing fashion, make the passphrase decision honestly (especially for unattended transfer jobs, where the standard advice quietly fails), set the file permissions SSH actually enforces, and put the private key in the one place it belongs — and nowhere else. It is part of our SSH Key Management series; if key pairs themselves are still fuzzy, read SSH keys explained first and come back.
Everything here uses ssh-keygen, the generation tool that ships with OpenSSH on Linux, macOS, and modern Windows. Graphical clients and transfer tools have their own generate buttons, but they are all making the same objects with the same decisions — learn it once at the command line and every GUI version becomes obvious.
Three Decisions Before You Type Anything
A key pair is cheap to make and annoying to unmake, so settle three questions before generating:
- Who is this key for — a person or a job? A human's interactive key and an unattended job's key get different passphrase treatment and different storage. Never let one key serve both roles. And make it one pair per job, not one pair per server-it-connects-to and not one master pair for everything: per-job keys are what let you later revoke or restrict a single workflow without collateral damage.
- What type? There is a durable answer that avoids algorithm fashion, covered below.
- Passphrase or not? For humans, yes. For automation, the honest answer is usually no — with compensating controls that actually work instead of a passphrase that only pretends to. That reasoning gets its own section.
Decide those three, and the rest of this article is mechanics.
The Generation Walkthrough
Here is a full, real session generating a key for a nightly reporting job. The -t flag picks the key type, -f names the output file, and -C sets the comment — the human-readable label that travels with the public key:
$ ssh-keygen -t ed25519 -f ~/.ssh/nightly-report -C "nightly-report@batch01" Generating public/private ed25519 key pair. Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/svc-batch/.ssh/nightly-report Your public key has been saved in /home/svc-batch/.ssh/nightly-report.pub The key fingerprint is: SHA256:Yq8kT3n0Wb1xR7pVd2mAzcE9fH6uJ4sLgN5oPiQwXk0 nightly-report@batch01
Walk through what just happened. The tool asked for a passphrase twice (more on that decision shortly — for this unattended job we pressed Enter to leave it empty). It then wrote exactly two files: nightly-report, the private key, and nightly-report.pub, the public key. It printed the fingerprint — the key's short identity. Copy that fingerprint line into your key inventory right now, while you know exactly what this key is for; that single habit is half of key inventory work done for free. (The command also prints a "randomart" ASCII box — a visual form of the fingerprint you can safely ignore.)
If you omit -f, the tool offers a default path like ~/.ssh/id_ed25519. Defaults are fine for a person's primary key; for job keys, always name the file after the job, because a folder full of id_ed25519, id_ed25519_2, and id_ed25519_old is how inventories die.
Choosing the type without chasing fashion
Key types (ed25519, rsa, and others) differ in underlying math, key length, and how widely old servers support them. Ignore any advice framed as this year's best algorithm — that answer drifts, and transfer infrastructure outlives fashion. The durable rule has two halves:
- Default to a current recommended type. In practice: whatever a current OpenSSH release generates by default (currently the compact, fast
ed25519). Current defaults are chosen by people who track the cryptography so you don't have to. - Keep RSA for compatibility. Some older servers and appliances only accept RSA keys. When you must interoperate with one, generate an RSA key of generous length:
ssh-keygen -t rsa -b 4096 -f ~/.ssh/legacy-vendor -C "acme-upload@batch01". Using RSA for a specific endpoint is a compatibility decision, not a downgrade to apologize for.
When a partner or server rejects your key type, that rejection is loud and immediate — you will not silently end up less secure, you will simply generate the type they support and move on.
Comments and Names That Pay Off Later
The comment (-C) costs nothing at generation time and is priceless during audits, because it is the only part of a public key a human can read. Whoever finds your key on a server two years from now — including future you — will judge it entirely by its comment. Adopt a convention and never deviate: purpose@machine works well (nightly-report@batch01, acme-upload@batch01, alex@workstation). Vague comments (test, key2, or a bare default username) are how servers accumulate mystery keys that nobody dares delete — the exact failure mode our offboarding article exists to clean up.
One caution: the comment is a label, not a lock. Anyone can put any comment on any key, so audits ultimately trust fingerprints, not comments. Write comments for convenience; verify with fingerprints.
The Passphrase Decision, Honestly
A passphrase encrypts the private key file itself. With one set, a stolen key file is useless until the thief also obtains the passphrase; without one, the file is the access. Given that, "always set a passphrase" sounds like obvious advice — and for human keys, it is. Type it at login, or better, use ssh-agent — a small background program that holds your decrypted key in memory after you enter the passphrase once per session — and the cost of a passphrase rounds to zero. Humans should set one, full stop.
Unattended jobs are where honest advice diverges from checkbox advice. A scheduled transfer that runs at 2 a.m. has no human to type a passphrase. The common workaround is to store the passphrase somewhere the job can read it — a config entry, an environment variable, a second file in the same folder. Stop and picture what that achieves: the locked box and the key to the box now sit side by side on the same machine, readable by the same attacker in the same breach. That is passphrase theater — cost without protection, plus a new way for the job to break.
Remember: a passphrase your script can read protects you from almost nothing. For unattended jobs, an unencrypted private key with real compensating controls is more honest — and more secure in practice — than a passphrase stored next to the key it unlocks.
The compensating controls are not optional extras; they are the actual security model for automation keys:
- A dedicated key per job, so exposure of one key exposes one workflow, not your whole estate.
- A dedicated, unprivileged account to own and run the job — not an administrator, not a shared login. (Our transfer authentication series covers service-account hygiene in depth.)
- Strict file permissions, next section, so other users on the machine cannot read the key.
- Server-side restrictions on the key itself — limiting it to one source address and one operation — so even a stolen key can do very little. That is the subject of distributing and controlling authorized_keys.
| Question | Human interactive key | Unattended job key |
|---|---|---|
| Passphrase? | Yes, always — with ssh-agent for comfort | Usually none — stored passphrases are theater |
| What protects the key at rest | Passphrase encryption + file permissions | File permissions + dedicated account + server-side restrictions |
| Scope of the key | One person, possibly several servers | Exactly one job or workflow |
| If the machine is compromised | Key file is useless without the passphrase | Key is exposed — restrictions limit the damage; rotate it |
One honest footnote: an unencrypted automation key means a compromise of that machine is a compromise of that key. Accept that consequence explicitly — it is why the restrictions and the per-job scoping matter, and why a suspected exposure triggers immediate rotation rather than debate.
File Permissions SSH Actually Enforces
SSH is unusually opinionated about file permissions, and it enforces the opinion: if your private key is readable by other users, OpenSSH refuses to use it. The error is famous, so learn its shape now:
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: UNPROTECTED PRIVATE KEY FILE! @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Permissions 0644 for '/home/svc-batch/.ssh/nightly-report' are too open. It is required that your private key files are NOT accessible by others. This private key will be ignored.
"Ignored" is the operative word: the client silently falls back to other methods or fails, which in an unattended job looks like a mysterious authentication failure at 2 a.m. The fix — and the correct starting state — is a home permission set:
$ chmod 700 ~/.ssh # the folder: owner only $ chmod 600 ~/.ssh/nightly-report # private key: owner read/write only $ chmod 644 ~/.ssh/nightly-report.pub # public key: world-readable is fine $ ls -l ~/.ssh drwx------ svc-batch .ssh -rw------- svc-batch nightly-report -rw-r--r-- svc-batch nightly-report.pub
Two details worth internalizing. First, the owner matters as much as the mode: the key must belong to the account that runs the job, not to the admin who happened to create it. Second, on Windows the same rule applies through ACLs rather than mode bits — Windows OpenSSH refuses keys that other accounts can read, and the fix is removing inherited permissions so only the owning account (and SYSTEM) has access. Graphical transfer clients are typically less strict than OpenSSH, but treat 600-equivalent as the standard everywhere; the enforcement is a favor, not an obstacle.
Where Private Keys Should Live — and the List of Places They Should Not
The storage rule follows directly from what a private key is: it should exist in exactly one place — the .ssh folder (or equivalent protected store) of the account that uses it, on the machine that runs the connection. Generate it on that machine, as that account, and it never needs to move at all; only the .pub file travels. Generating on your workstation "and then copying it over" creates a second copy whose deletion you now have to remember and can never verify.
The negative list matters because every entry on it is somewhere real keys are actually found during audits:
- Not on shared drives or file servers. A private key on a departmental share is readable by everyone with share access and backed up to places you have never seen. This is the single most common storage failure.
- Not in source control. Repositories are cloned, forked, mirrored, and backed up promiscuously — and history means a key committed once is recoverable forever, even after deletion. Scripts should reference a key path, never contain a key.
- Not in email, chat, or tickets. These systems retain content indefinitely, search it, and share it with future participants. A key pasted into a ticket has an unknowable audience.
- Not in wikis or runbooks. Documentation should record the key's fingerprint, owner, and location — the metadata — never the key material itself.
- Not "temporarily" anywhere. Downloads folders and desktop copies made during setup outlive the setup. If a copy had to exist for a moment, deleting it is part of the task, not an optional cleanup.
Rule of thumb: if you can answer "where is every copy of this private key?" with one filename on one machine, storage is correct. The moment the honest answer includes "and probably...", treat the key as shared and plan its rotation.
Moving the Public Half Where It Belongs
With the pair generated and locked down, the public key needs to reach the server that will accept it. Between OpenSSH systems, ssh-copy-id appends it to the right file with the right permissions in one step — note it takes the .pub path:
$ ssh-copy-id -i ~/.ssh/nightly-report.pub transfer-acme@sftp.example.com
Where ssh-copy-id is unavailable — partner servers, Windows servers, managed platforms — you send or paste the single line from the .pub file through whatever channel the operator provides, and confirm the fingerprint with them out of band. On a Windows SFTP server such as Sysax Multi Server, the public key is imported and attached to the specific account in the server configuration — same principle as an authorized_keys line, managed through the admin interface. The full server-side story, including the per-key restrictions that make automation keys safe, is the next article: distributing and controlling authorized_keys.
The client side then needs to know which key to present. Command-line tools take -i ~/.ssh/nightly-report, or better, a per-host IdentityFile entry in SSH client configuration — tidy patterns for that live in SSH config for transfers. Windows scheduling tools store it per connection: in Sysax FTP Automation, the connection profile records the private key path once and every scheduled run authenticates identically — which is exactly the consistency an unattended job needs.
Backups, Copies, and the Second-Machine Question
Three questions arrive within a week of anyone taking keys seriously. They share one answer: keys are cheap — prefer another key to another copy.
"Should I back up the private key?" For automation keys, no. If the machine dies, you generate a fresh pair on its replacement and authorize the new public key — five minutes, and your inventory stays truthful. A backup copy, by contrast, is a second location to protect and a permanent asterisk on "where is every copy?". For a human's primary key, an encrypted, offline, deliberately stored backup is defensible; even then, regeneration is usually simpler.
"I work from two machines — copy the key to the second one?" Generate a second pair on the second machine and authorize both public keys. Per-machine keys mean a lost laptop revokes one line on the server, not your identity everywhere. The same logic applies when a job moves between machines: new machine, new key, retire the old line.
"I deleted the .pub file — is the key ruined?" No. The public half is derivable from the private half at any time: ssh-keygen -y -f ~/.ssh/nightly-report > ~/.ssh/nightly-report.pub. The reverse is impossible, which is the entire point of the design.
The Checklist Version
Everything above, compressed into the sequence to follow each time you create a key for a transfer job:
- Log into the machine that will run the connection, as the account that will run it.
- Generate with a purposeful name and comment:
ssh-keygen -t ed25519 -f ~/.ssh/<job> -C "<job>@<machine>"(RSA 4096 only if the far end requires it). - Passphrase: set one for a human key; leave empty for an unattended job and rely on real controls, not stored passphrases.
- Verify permissions:
700on~/.ssh,600on the private key, owned by the job account. - Record the fingerprint, comment, owner, and purpose in your key inventory.
- Deliver only the
.pub— viassh-copy-id, the server's import interface, or the partner's channel — and confirm the fingerprint. - Point the client or job profile at the private key path, run one supervised test, and confirm the login in the server's log.
From here, the natural next steps are the server-side gate in distributing and controlling authorized_keys, and the habits that keep generated keys accounted for in key rotation and the key inventory. If this key exists to serve a scheduled workflow, SFTP automation shows the surrounding job design.
Frequently Asked Questions
Which key type should I actually pick?
Is it really acceptable to have no passphrase on an automation key?
Why does SSH suddenly ignore my key or refuse to use it?
Can I generate the key on my workstation and copy it to the server that needs it?
I lost the .pub file. Do I have to start over?
Do I need a different key for every server I connect to?
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.
