I generate keys often enough that I no longer trust my memory for the exact OpenSSL syntax. The commands are close, but not close enough, and the difference between a good key file and a bad production day is usually one flag, one wrong format, or one decision made too quickly. This guide is the workflow I actually use when I need RSA or EC private keys for web servers, internal PKI, client certificates, or a certificate authority.
If you already know how to create a CSR, the next question is usually what kind of key you should create in the first place. I covered request creation separately in how to generate a CSR the right way, but before you ever get to the CSR, you need to choose the key algorithm, the size or curve, and the storage format. Those decisions matter more than most tutorials admit.
RSA vs EC: what I choose and why
RSA is still the compatibility king. If I need something that will work almost everywhere, especially with older appliances, middleboxes, load balancers, or legacy Java stacks, RSA is still my safe default. It is familiar, widely supported, and easy to validate with older tooling.
EC, usually ECDSA in the TLS world, gives better security per bit and much smaller keys. A rough mental map that is good enough for planning is:
- RSA 2048 is roughly comparable to EC 224-bit security
- RSA 3072 is roughly in the neighborhood of P-256
- RSA 4096 is roughly comparable to EC 384-bit security
That does not mean the algorithms behave the same way in practice. RSA keys are bigger, signatures are larger, and operations can be slower, especially on constrained systems. EC keys are tiny, certificate chains can be leaner, and handshakes tend to be more efficient. On busy edge systems, that difference is real.
When I still pick RSA
I still use RSA in these situations:
- I need maximum client compatibility
- I am dealing with old TLS stacks or embedded devices
- I am generating a long-lived offline CA key and want conservative interoperability
- A vendor or compliance document explicitly requires RSA
For most server certificates, RSA 2048 is still acceptable. I treat 2048 as the minimum, not the preferred forever choice. For long-lived CA material, I often use RSA 4096 because the extra CPU cost is mostly irrelevant when the key is used rarely and stored offline.
When I prefer EC
I prefer EC when:
- I control both sides enough to know modern TLS support is present
- I want smaller keys and cleaner deployments
- I am issuing modern public-facing server certificates and the platform supports ECDSA properly
- I care more about efficiency than ancient compatibility
If I had to name one default EC curve for public web use, it would be P-256, which OpenSSL usually calls prime256v1. It is the compatibility curve. P-384 is a reasonable choice when I want a higher margin and know my stack supports it cleanly.
For broader TLS background and chain handling, OpenSSL certificate chain debugging and the OpenSSL commands cheatsheet are good companion references.
The first rule: protect the key file before you do anything else
I create the key with tight permissions from the start. On Linux, private keys should generally be readable only by the account that needs them.
umask 077
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out server.key
chmod 600 server.key
ls -l server.key
umask 077 makes newly created files default to owner-only permissions. I still run chmod 600 afterwards because I want the end state to be explicit.
A common mistake is generating the key as root, then copying it around until ownership and permissions become a mess. If Nginx or Apache will read the key as root during startup and then drop privileges, that may be fine. But if another process needs direct access, set ownership deliberately:
chown root:root server.key
chmod 600 server.key
Or for a service user:
chown appuser:appuser server.key
chmod 600 server.key
RSA key generation: legacy commands and current commands
You will still see genrsa everywhere. It works. It is widely used. But genpkey is the more general and current interface.
Generating RSA with genrsa (legacy but still common)
openssl genrsa -out rsa-2048.key 2048
For a larger key:
openssl genrsa -out rsa-4096.key 4096
This older form is readable and short, which is why it remains in so many runbooks. I still see it in production more than genpkey.
Generating RSA with genpkey (preferred current method)
openssl genpkey -algorithm RSA \
-pkeyopt rsa_keygen_bits:2048 \
-out rsa-2048.key
And for 4096 bits:
openssl genpkey -algorithm RSA \
-pkeyopt rsa_keygen_bits:4096 \
-out rsa-4096.key
This is the form I use for new documentation because it aligns with modern OpenSSL usage and keeps me in one command family when switching algorithms.
Which RSA size I actually use
My practical rule looks like this:
- RSA 2048: minimum for normal server certificates
- RSA 3072: reasonable middle ground if you want a bit more margin
- RSA 4096: good for CA keys or rare-use signing keys, not something I pick casually for every server
The trade-off is simple. Larger RSA keys cost more CPU and produce bigger signatures. On a lightly loaded internal server, you may never notice. On large-scale or latency-sensitive frontends, you might.
EC key generation: the two commands worth knowing
OpenSSL gives you two normal paths for EC key generation. One is older and curve-centric, the other fits the newer genpkey style.
Generating an EC key with ecparam
For P-256:
openssl ecparam -name prime256v1 -genkey -noout -out ec-p256.key
For P-384:
openssl ecparam -name secp384r1 -genkey -noout -out ec-p384.key
This syntax is still perfectly valid and very common. The -noout flag suppresses printing the parameter block.
Generating an EC key with genpkey
For P-256:
openssl genpkey -algorithm EC \
-pkeyopt ec_paramgen_curve:prime256v1 \
-out ec-p256.key
For P-384:
openssl genpkey -algorithm EC \
-pkeyopt ec_paramgen_curve:secp384r1 \
-out ec-p384.key
If I am writing a repeatable procedure that uses genpkey for RSA already, I prefer this form for consistency.
Picking the curve without overthinking it
Curve choice turns into a religious debate online much faster than it needs to.
P-256 / prime256v1
This is the default curve I choose most often. It is well supported, efficient, and broadly compatible. If the goal is “modern and boring,” P-256 is usually it.
openssl genpkey -algorithm EC \
-pkeyopt ec_paramgen_curve:prime256v1 \
-out tls-ec.key
P-384 / secp384r1
I use P-384 when I want a stronger security margin and I know the platform is current. It is also a sensible option for internal PKI where I control the clients.
openssl genpkey -algorithm EC \
-pkeyopt ec_paramgen_curve:secp384r1 \
-out tls-ec-p384.key
Curves I avoid unless I have a specific reason
I do not pick exotic curves casually. If I am dealing with public TLS and broad compatibility matters, I stay with P-256 or P-384. There are more options, but more options do not automatically mean better operations.
To list available curves on a system:
openssl ecparam -list_curves
That output is useful, but do not mistake “available” for “good production choice.”
What about Ed25519?
Ed25519 is excellent in the SSH world. I use it for SSH keys routinely because it is small, fast, and easy to handle. But that does not make it a drop-in answer for TLS private keys everywhere.
You can generate an Ed25519 private key with OpenSSL like this:
openssl genpkey -algorithm ED25519 -out ed25519.key
For SSH, that is usually not how I generate keys because ssh-keygen is the more natural tool:
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519
The caution here is support. Ed25519 is not universally deployed across all TLS tooling and older stacks the way RSA and the common ECDSA curves are. For SSH, I recommend it freely. For TLS, I verify application support before I commit to it. If you are working on Linux host access as well, SSH key authentication on Linux pairs well with this topic.
Should the private key have a passphrase?
This is where theory and operations collide.
I use passphrases for CA keys and offline keys
If the key is a CA key, a signing key, or anything that should not be used automatically by a booting service, I protect it with a passphrase.
Generate an encrypted RSA key:
openssl genpkey -aes-256-cbc -algorithm RSA \
-pkeyopt rsa_keygen_bits:4096 \
-out ca-encrypted.key
Generate an encrypted EC key:
openssl genpkey -aes-256-cbc -algorithm EC \
-pkeyopt ec_paramgen_curve:secp384r1 \
-out ca-ec-encrypted.key
That is reasonable for offline CA storage because the key should be used deliberately, not automatically at every restart.
I usually avoid passphrases on unattended web server keys
For a production web server that must restart automatically, a passphrase can be a liability. If nobody is present to unlock the key, the service does not come back cleanly after a reboot or crash.
This is one of those trade-offs that security people sometimes hate hearing, but operations wins here. On a locked-down host with correct file permissions, a non-interactive server key is often the practical answer.
Removing a passphrase when automation requires it
If you already have an encrypted private key and need a copy without a passphrase for service startup:
openssl pkey -in encrypted.key -out unencrypted.key
chmod 600 unencrypted.key
For older RSA-focused examples, you may also see:
openssl rsa -in encrypted-rsa.key -out unencrypted-rsa.key
openssl pkey is the more general current interface.
Be careful with shell history and file sprawl here. I have seen teams remove a passphrase, leave three extra copies of the key in home directories, and make the security situation worse than before.
PKCS#8 vs traditional private key format
This is one of the most confusing parts for people who only touch keys occasionally.
PKCS#8
This is the modern, algorithm-agnostic private key format. Files often begin with:
-----BEGIN PRIVATE KEY-----
Or for encrypted keys:
-----BEGIN ENCRYPTED PRIVATE KEY-----
When I use openssl genpkey, I typically end up with PKCS#8 output. I prefer it because it is the current general-purpose format.
Traditional or legacy format
Older RSA keys often look like this:
-----BEGIN RSA PRIVATE KEY-----
Older EC keys often look like this:
-----BEGIN EC PRIVATE KEY-----
These are still encountered everywhere. Compatibility is usually fine, but when I am standardizing procedures, I lean toward PKCS#8.
Converting between formats
Convert a private key to PKCS#8 without encryption:
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt \
-in legacy.key -out pkcs8.key
Convert a traditional RSA key using the generic interface:
openssl pkey -in rsa-legacy.key -out rsa-pkcs8.key
Format conversions matter even more when you are packaging material into PKCS#12 bundles. If that is part of your workflow, see the PKCS#12 / PFX troubleshooting guide and certificate format conversion guide.
Verifying the key is not corrupted
I always validate a newly generated or newly copied key before I use it.
The command I reach for first is:
openssl pkey -in server.key -check -noout
If the key is valid, OpenSSL reports success. For EC keys specifically, the older command also works:
openssl ec -in ec-p256.key -check -noout
For RSA:
openssl rsa -in rsa-2048.key -check -noout
I prefer openssl pkey unless I have a reason to use the algorithm-specific form.
Common validation failures I actually see
The most common issues are not cryptographic failures. They are operational ones:
- The file is truncated because somebody copied it badly
- The wrong file was pasted into place
- The service account cannot read it
- The file is encrypted and the automation path cannot unlock it
- DOS line endings or broken copy/paste mangled the PEM file
A quick sanity check for PEM structure:
grep -E 'BEGIN|END' server.key
That does not prove the key is good, but it catches obvious damage fast.
Viewing key details safely
To inspect a private key in human-readable form:
openssl pkey -in server.key -text -noout
For a public key derived from the private key:
openssl pkey -in server.key -pubout -out public.pem
Then inspect it:
openssl pkey -pubin -in public.pem -text -noout
I only run -text on secure systems because it dumps sensitive structural details. That is usually fine on an admin box, but I would not paste that output into tickets or chat blindly.
Confirming a certificate matches the private key
This check has saved me from bad deployments more than once.
For any key type, compare the public key material:
openssl x509 -in server.crt -pubkey -noout > cert.pub
openssl pkey -in server.key -pubout > key.pub
diff -u cert.pub key.pub
If diff shows no output, the certificate and key match.
You can also hash the public key material for a quick comparison:
openssl x509 -in server.crt -pubkey -noout | sha256sum
openssl pkey -in server.key -pubout | sha256sum
That approach works better across RSA and EC than relying on the old RSA modulus trick.
Backing up private keys without creating new risk
Key backups are one of those things everyone agrees are important until they have to decide where the backup lives.
My practical rules are simple:
- Store private key backups encrypted at rest
- Keep CA keys offline when possible
- Limit who can restore the backup
- Document restoration steps before the emergency
- Do not email keys or leave them in chat systems
- Do not keep unnecessary plaintext copies on jump hosts
A simple encrypted tar archive workflow can look like this:
tar -czf tls-keys-2026-07-22.tar.gz server.key server.crt chain.pem
openssl enc -aes-256-cbc -pbkdf2 -salt \
-in tls-keys-2026-07-22.tar.gz \
-out tls-keys-2026-07-22.tar.gz.enc
shred -u tls-keys-2026-07-22.tar.gz
openssl enc is fine for simple file-at-rest protection when used carefully with -pbkdf2, but I prefer a proper secrets management or backup platform if one exists. The important point is not to leave the raw tarball behind.
Mistakes I see repeatedly in production
Generating a key on the wrong host
I have seen people generate a private key on a laptop, move it through Slack or email, then deploy it to production. That is a bad chain of custody. Generate sensitive keys on the host that will store them, or on a controlled admin system.
Using RSA 4096 everywhere without a reason
Bigger is not always better. If your only reason is “it feels stronger,” stop and think about compatibility, handshake cost, and actual requirements.
Encrypting a web server key, then forgetting who will unlock it
A passphrase that nobody can provide during reboot is not security. It is an outage.
Leaving key files world-readable
I still occasionally find this after rushed deployments:
ls -l /etc/ssl/private
If you see group or world read permissions on real private keys, fix them.
Confusing certificate conversion with key generation
People sometimes try to “convert” a certificate and expect a private key to appear. If the private key was never saved, it cannot be recovered from the certificate. Public certificates do not contain the private key.
My recommended defaults
If you want the short version of my current preferences:
For a general-purpose web server key
umask 077
openssl genpkey -algorithm EC \
-pkeyopt ec_paramgen_curve:prime256v1 \
-out server.key
chmod 600 server.key
openssl pkey -in server.key -check -noout
For maximum compatibility
umask 077
openssl genpkey -algorithm RSA \
-pkeyopt rsa_keygen_bits:2048 \
-out server.key
chmod 600 server.key
openssl pkey -in server.key -check -noout
For an offline CA key
umask 077
openssl genpkey -aes-256-cbc -algorithm RSA \
-pkeyopt rsa_keygen_bits:4096 \
-out ca.key
chmod 600 ca.key
openssl pkey -in ca.key -check -noout
Those are not the only acceptable choices, but they are conservative and operationally sane.
Conclusion
Key generation is easy to do quickly and surprisingly easy to do badly. The command itself is only part of the job. The real work is choosing the right algorithm for the use case, handling the file securely, validating it immediately, and making sure your future self can still operate the system without drama.
If you remember nothing else, remember this: use RSA when compatibility dominates, use P-256 EC when modern efficiency makes sense, use 4096-bit RSA mostly for long-lived CA scenarios, treat passphrases as an operational trade-off, validate every key with openssl pkey -check, and lock down permissions before the key ever leaves your terminal. That is the difference between a clean deployment and the kind of TLS incident that wastes an afternoon.