The short answer
Four commands carry a certificate from nothing to serving: -genkeypair creates the key, -certreq writes the CSR, -importcert installs the CA's reply, and -list -v proves it worked. Two details break more Java installs than everything else combined. The SAN extension does not travel from -genkeypair to -certreq, so -ext SAN=… has to be repeated on the request. And the signed certificate has to be imported into the same alias that generated the request, because importing it under a new alias returns success and stores something no server can use. Since JDK 9 the default keystore type is PKCS12, and JKS now warns on every command.
On this page
- What keytool actually manages
- JKS or PKCS12: which format in 2026?
- Creating a keystore and a key pair
- Generating a CSR, and the SAN trap
- Importing the certificate the CA sends back
- Listing and inspecting what is in a keystore
- Converting, exporting, and getting the key out
- Trust: cacerts and custom truststores
- Renewal now that certificates last 200 days
- keytool errors and what they mean
- Quick reference
- FAQ
Every command below was run against OpenJDK 21 in August 2026, and the messages quoted are the ones keytool actually printed rather than the ones the documentation implies. Where behaviour differs between Java versions, the difference is called out.
What keytool actually manages
keytool manages entries inside keystore files, and there are only two kinds of entry: a PrivateKeyEntry, which is your own key plus the certificate chain that vouches for it, and a trustedCertEntry, which is someone else's certificate you have decided to accept. A keystore and a truststore are the same file format doing opposite jobs, separated only by which entries they hold and which JVM property points at them.
That distinction is worth holding onto because it splits Java TLS problems in half before you start debugging. A Java service that cannot connect to something has a truststore question. A Java service presenting the wrong certificate to its own clients has a keystore question. The commands are similar enough that people reach for the wrong one under pressure.
An alias is a name, not a label. Every entry in a keystore lives under an alias, and keytool uses it as the primary key for almost every operation. Aliases are case-insensitive and permanent for practical purposes: renaming one means -changealias, and application config files usually name the alias explicitly, so a rename is a config change too.
JKS or PKCS12: which format in 2026?
PKCS12, without reservation. It has been the default keystore type since JDK 9 under JEP 229, it is a published standard (RFC 7292) rather than a Java-specific container, and it is the format certificate authorities actually deliver. On Java 21, opening a JKS keystore prints a warning on every command recommending you migrate. Existing JKS files keep working, so this is a housekeeping task rather than an outage waiting to happen.
The warning text is specific enough to copy, and it names the command that fixes it:
Warning:
The JKS keystore uses a proprietary format. It is recommended to migrate to
PKCS12 which is an industry standard format using "keytool -importkeystore
-srckeystore legacy.jks -destkeystore legacy.jks -deststoretype pkcs12".Running that migration in place converts the file and keeps the original next to it:
keytool -importkeystore \
-srckeystore app.jks \
-destkeystore app.jks \
-deststoretype pkcs12
# Entry for alias legacy successfully imported.
# Import command completed: 1 entries successfully imported, 0 entries failed or cancelled
# Warning:
# Migrated "app.jks" to PKCS12. The JKS keystore is backed up as "app.jks.old".One practical difference worth knowing before you migrate: JKS supports a separate password on each key, and PKCS12 does not treat them independently in the same way. If your application config carries a distinct key password, expect to set it equal to the store password after the conversion. There is also a small comfort in the .old backup — the operation is reversible if a downstream tool turns out to need the original.
Creating a keystore and a key pair
One command creates both: keytool generates the key pair, wraps it in a self-signed placeholder certificate, and writes the keystore file if it does not exist yet. The placeholder is temporary scaffolding that gets replaced when the CA's reply comes back. Pick the alias deliberately, because you will need it again at every later step.
keytool -genkeypair \
-alias server \
-keyalg RSA -keysize 2048 \
-sigalg SHA256withRSA \
-storetype PKCS12 \
-keystore server.p12 \
-validity 365 \
-dname "CN=www.example.com,O=Example GmbH,L=Berlin,C=DE" \
-ext "SAN=dns:www.example.com,dns:example.com"-storetype PKCS12 is redundant on Java 9 and later and harmless everywhere, so it is worth leaving in for scripts that might run on an older JVM. -dname supplied inline keeps the command non-interactive, which matters the moment this ends up in a pipeline. Leave -keysize in as well: JDK 21 defaults RSA to 3072 bits when you omit it, which is fine cryptographically but slower on every handshake and larger than anything a public CA requires.
ECDSA instead of RSA. Swap in -keyalg EC -groupname secp256r1 -sigalg SHA256withECDSA. The keys are far smaller and the handshake is cheaper, and every current browser and Java runtime handles P-256. Some older middleware in front of your Java app may not, which is the real reason RSA is still the safe default in an enterprise estate. The trade-off is laid out in ECC compared with RSA for TLS certificates.
A word on -genkey: it still works and does the same thing, because it is the legacy spelling of -genkeypair. Older documentation is full of it. Writing -genkeypair in anything new costs nothing and says what it does.
Generating a CSR, and the SAN trap
keytool -certreq builds the request from the entry's subject name, and it does not copy the extensions you set when you generated the key. Requesting a certificate for more than one host name means repeating the whole -ext SAN=… argument here. Since public certificate authorities validate against the SAN extension and browsers ignore the common name entirely, a request without SAN either comes back covering one name or gets rejected.
keytool -certreq \
-alias server \
-keystore server.p12 \
-file server.csr \
-ext "SAN=dns:www.example.com,dns:example.com"It is worth seeing what the omission looks like, because keytool gives you no hint. Generating a key pair with two SAN names and then running -certreq without -ext on JDK 21 produces a request whose only extension is a Subject Key Identifier:
$ openssl req -in server.csr -noout -text
# Without -ext on the -certreq command:
Attributes:
Requested Extensions:
X509v3 Subject Key Identifier:
C3:1D:41:5A:85:F9:D0:6D:69:EA:DE:09:DA:B8:48:A9:7E:10:46:C1
# With -ext "SAN=dns:www.example.com,dns:example.com":
Attributes:
Requested Extensions:
X509v3 Subject Key Identifier:
C3:1D:41:5A:85:F9:D0:6D:69:EA:DE:09:DA:B8:48:A9:7E:10:46:C1
X509v3 Subject Alternative Name:
DNS:www.example.com, DNS:example.comMake that check part of the routine. One openssl req -noout -text before submitting costs a few seconds and catches the mistake while it is still free, rather than after validation has run and a reissue is the only way back. If OpenSSL is not installed on the machine, the free CSR and certificate decoders in the My-SSL tool set read the same fields in a browser. For what each field in a request means, our explanation of CSR contents covers the whole structure.
Submit the resulting file to your certificate authority. A domain-validated certificate usually comes back within minutes; an organization-validated one waits on the CA confirming the company exists, which is normally one to three working days. My-SSL issues DV, OV and EV certificates through Certum, and every one of them accepts a keytool-generated request as long as the SAN names are in it.
Importing the certificate the CA sends back
Import the reply into the same alias that generated the request, with -trustcacerts, and only once the issuing chain is reachable from the keystore. Get the alias right and keytool replaces the self-signed placeholder with the real certificate. Get it wrong and keytool stores the certificate as a separate trusted entry, prints a success message, and exits zero.
The order matters. If the CA sent a P7B or a full-chain bundle, one import does everything. If it sent separate PEM files, the root and intermediates go in first, each under its own alias, then the leaf goes into the original alias:
# 1 — the chain, root first, each under its own alias
keytool -importcert -alias caroot -trustcacerts -file root.crt -keystore server.p12
keytool -importcert -alias caint -trustcacerts -file inter.crt -keystore server.p12
# 2 — the reply, into the SAME alias that generated the CSR
keytool -importcert -alias server -trustcacerts -file server.crt -keystore server.p12
# -> Certificate reply was installed in keystore
# 3 — prove it before you restart anything
keytool -list -v -alias server -keystore server.p12 | grep -E "Entry type|chain length"
# -> Entry type: PrivateKeyEntry
# -> Certificate chain length: 2"Failed to establish chain from reply" is the good outcome. It means you used the correct alias and the only thing missing is the chain, so keytool refused rather than storing something broken. Import the intermediate, run the same command again, and it completes. The failure mode with no error message is the one that costs an afternoon. Missing intermediates cause the same class of problem on every platform, which the guide to certificate chains goes through in detail.
When the certificate was issued somewhere else entirely and you have a PFX from the CA rather than a reply to your own request, skip -importcert altogether and import the whole keystore:
keytool -importkeystore \
-srckeystore certificate.pfx -srcstoretype PKCS12 \
-destkeystore server.p12 -deststoretype PKCS12 \
-srcalias 1 -destalias serverThe -srcalias value is whatever the PFX happens to use, often a GUID or the number 1. Run keytool -list -keystore certificate.pfx -storetype PKCS12 first to read it, and rename it on the way in with -destalias so your application config stays stable across renewals.
Listing and inspecting what is in a keystore
-list answers the two questions that matter after any change: what entries exist, and what type is each one. Adding -v expands a single entry into the full certificate, including the chain length and the validity dates. Adding -rfc prints the certificate as PEM instead, which is handy when you want to paste it into something else.
| Command | What it answers |
|---|---|
keytool -list -keystore server.p12 | Which aliases exist, and is each one a PrivateKeyEntry or a trustedCertEntry |
keytool -list -v -alias server -keystore server.p12 | Subject, issuer, SAN names, validity dates and chain length for one entry |
keytool -list -rfc -alias server -keystore server.p12 | The certificate as PEM text, ready to paste elsewhere |
keytool -printcert -file server.crt | What is in a certificate file, without touching a keystore |
keytool -printcertreq -file server.csr | What is in a CSR, including whether SAN made it in |
keytool -printcert -sslserver www.example.com:443 | What a live server is actually presenting right now |
That last one earns its place in a troubleshooting session. Comparing -printcert -sslserver against -list -v on the keystore tells you immediately whether the application picked up the file you edited, which is a different question from whether the file is correct. Applications cache keystores at startup more often than people expect.
Converting, exporting, and getting the key out
keytool exports certificates and refuses to export private keys. There is no flag for it and no plan to add one. When you need the key itself — moving a certificate from a Java app to Nginx, say — the supported route is to convert the keystore to PKCS12 with -importkeystore, then read the key out with OpenSSL.
# Certificate only — keytool can do this
keytool -exportcert -alias server -keystore server.p12 -rfc -file server.crt
# Private key — keytool cannot, so hand it to OpenSSL
keytool -importkeystore \
-srckeystore server.jks -srcstoretype JKS \
-destkeystore server.p12 -deststoretype PKCS12
openssl pkcs12 -in server.p12 -nocerts -nodes -out server.key
openssl pkcs12 -in server.p12 -clcerts -nokeys -out server.crt
openssl pkcs12 -in server.p12 -cacerts -nokeys -out chain.crtGoing the other way, from PEM files into a keystore, has no single-step keytool command either. Build the PKCS12 with OpenSSL first and import it, which is also how you get a certificate bought for a web server into a Java application:
openssl pkcs12 -export \
-in server.crt -inkey server.key -certfile chain.crt \
-name server -out server.p12
keytool -importkeystore \
-srckeystore server.p12 -srcstoretype PKCS12 \
-destkeystore server.p12 -deststoretype PKCS12Two housekeeping notes. Files written by openssl pkcs12 -nodes hold an unencrypted private key, so they belong in a directory with restricted permissions and nowhere near version control. And on OpenSSL 3.x, reading an old PKCS12 file produced by legacy tooling sometimes needs the -legacy flag, because the RC2 and 40-bit RC4 algorithms those files use moved to the legacy provider. The wider set of format conversions lives in our OpenSSL commands cheat sheet, and the guide to PEM, PFX, DER and P7B explains which file you are looking at in the first place.
Trust: cacerts and custom truststores
Java ships its own truststore, cacerts, holding the public CA roots the JVM trusts by default. On Java 9 and later it lives at $JAVA_HOME/lib/security/cacerts; on Java 8 it sat one level deeper under jre/. The default password is changeit, and modern keytool gives you a -cacerts flag so you never have to type the path.
# What does this JVM trust?
keytool -list -cacerts -storepass changeit
# Is one specific root in there?
keytool -list -cacerts -storepass changeit | grep -i certum
# Add a private or internal root (needs write access to the JDK)
keytool -importcert -cacerts -storepass changeit \
-alias internal-root -file internal-root.crt
# Better: a project truststore you own, passed to the app at startup
keytool -importcert -keystore truststore.p12 -storetype PKCS12 \
-alias internal-root -file internal-root.crt
java -Djavax.net.ssl.trustStore=/opt/app/truststore.p12 \
-Djavax.net.ssl.trustStorePassword=... -jar app.jarPrefer the project truststore to editing cacerts. A JDK upgrade replaces the file and takes your additions with it, and on Debian and Ubuntu cacerts is usually a symlink into the distribution's certificate store, so a routine package update can do the same thing. A truststore that lives with the application survives both, and it is visible to whoever inherits the service.
One caveat on custom truststores. Setting javax.net.ssl.trustStore replaces cacerts rather than adding to it. A truststore containing only your internal root will make the application reject every public certificate on the internet, which usually surfaces as an unrelated outbound API breaking an hour later. If the app talks to both, start from a copy of cacerts and add your root to that copy.
Renewal now that certificates last 200 days
Renewal is the same four commands, and the shortcut is to keep the existing key pair and alias: generate a fresh CSR from the entry you already have, then import the new reply over the old certificate in place. That keeps your application config untouched, which is the whole point when the job now recurs twice a year instead of annually.
# Renewal on the existing alias — no new key, no config change
keytool -certreq -alias server -keystore server.p12 -file renew.csr \
-ext "SAN=dns:www.example.com,dns:example.com"
# ... submit renew.csr, wait for the CA ...
keytool -importcert -alias server -trustcacerts -file renewed.p7b -keystore server.p12
keytool -list -v -alias server -keystore server.p12 | grep -E "Valid from|chain length"The dates behind that cadence come from CA/Browser Forum ballot SC-081v3. As of 15 March 2026 the maximum lifetime of a publicly trusted TLS certificate is 200 days; it drops to 100 days on 15 March 2027 and to 47 days on 15 March 2029. Certificate authorities issue slightly under each ceiling, so a certificate bought today expires in roughly six months. Reusing the same key pair every time is convenient, though rotating it at least annually is the healthier habit — and once the 47-day step lands, a manual keytool sequence eight times a year stops being viable and the question becomes automation instead. What the shorter lifetimes change in practice covers the schedule and its consequences.
keytool errors and what they mean
keytool's messages are terse and mostly accurate once you know what they are pointing at. These are the ones that come up repeatedly, with the cause rather than a restatement of the text.
| Message | What is actually wrong |
|---|---|
Failed to establish chain from reply | The alias is right, but the issuing chain is not in the keystore. Import the intermediate and root first, or import the CA's full P7B bundle instead of the bare leaf. |
Certificate was added to keystore | Not an error, and usually not what you wanted. You imported into an alias with no private key, so it was stored as a trusted certificate. Re-import into the original alias. |
Alias <server> does not exist | Wrong keystore file, or the alias differs in a way you did not expect. Run -list and read the aliases before guessing. |
Keystore was tampered with, or password was incorrect | Nearly always the password. It also appears when the file is not a keystore at all, such as a PEM certificate renamed to .p12. |
The JKS keystore uses a proprietary format | Informational. The file still works; run the in-place -importkeystore migration when convenient and the warning stops. |
unable to find valid certification path to requested target | A runtime error rather than a keytool one, and it is a truststore problem: the JVM does not trust the certificate the other side presented. Add the issuing root to the truststore the app is actually using. |
No certificate matches the SAN / name mismatch at runtime | The CSR went out without -ext SAN=…, so the issued certificate covers fewer names than you assumed. This needs a reissue, not a keystore edit. |
Quick reference
Every command on this page in one block, in the order you would run them. Substitute your own alias, file names and host names; everything else is copy-ready.
# ── CREATE ────────────────────────────────────────────────────────────────
keytool -genkeypair -alias server -keyalg RSA -keysize 2048 \
-storetype PKCS12 -keystore server.p12 -validity 365 \
-dname "CN=www.example.com,O=Example GmbH,L=Berlin,C=DE" \
-ext "SAN=dns:www.example.com,dns:example.com"
# ── REQUEST (repeat -ext, it does not carry over) ─────────────────────────
keytool -certreq -alias server -keystore server.p12 -file server.csr \
-ext "SAN=dns:www.example.com,dns:example.com"
openssl req -in server.csr -noout -text # confirm SAN is present
# ── IMPORT (chain first, reply into the ORIGINAL alias) ───────────────────
keytool -importcert -alias caroot -trustcacerts -file root.crt -keystore server.p12
keytool -importcert -alias caint -trustcacerts -file inter.crt -keystore server.p12
keytool -importcert -alias server -trustcacerts -file server.p7b -keystore server.p12
# ── VERIFY ────────────────────────────────────────────────────────────────
keytool -list -keystore server.p12
keytool -list -v -alias server -keystore server.p12
keytool -printcert -sslserver www.example.com:443
# ── CONVERT ───────────────────────────────────────────────────────────────
keytool -importkeystore -srckeystore app.jks \
-destkeystore app.jks -deststoretype pkcs12 # JKS -> PKCS12, in place
keytool -importkeystore -srckeystore certificate.pfx -srcstoretype PKCS12 \
-destkeystore server.p12 -deststoretype PKCS12 -srcalias 1 -destalias server
keytool -exportcert -alias server -keystore server.p12 -rfc -file server.crt
openssl pkcs12 -in server.p12 -nocerts -nodes -out server.key # key: OpenSSL only
# ── TRUST ─────────────────────────────────────────────────────────────────
keytool -list -cacerts -storepass changeit
keytool -importcert -keystore truststore.p12 -storetype PKCS12 \
-alias internal-root -file internal-root.crt
# ── HOUSEKEEPING ──────────────────────────────────────────────────────────
keytool -delete -alias old-server -keystore server.p12
keytool -changealias -alias server -destalias server-2026 -keystore server.p12
keytool -storepasswd -keystore server.p12
keytool -keypasswd -alias server -keystore server.p12Java-specific signing work uses a different tool with the same keystore: jarsigner reads the PrivateKeyEntry you built here, which is covered in signing a JAR with jarsigner. For putting the finished keystore in front of traffic, the Tomcat installation guide and the GlassFish guide pick up where this one stops.
Getting the certificate itself
keytool builds the request and installs the answer; the certificate in between comes from a certificate authority. SSL certificates from My-SSL are issued by Certum, whose roots have been in the Java cacerts store and the major browser trust stores for years, and a keytool-generated CSR is accepted as-is. If your deployment needs several host names on one keystore entry, order a multi-domain certificate and list every name in the -ext SAN= argument before you submit.
Related reading
- OpenSSL commands cheat sheet — the other half of this toolkit, for everything keytool refuses to do.
- Installing a certificate on Apache Tomcat — where the keystore you just built gets wired into a connector.
- PEM, PFX, DER and P7B explained — for working out what the CA actually sent you.