Generate a Self-Signed Certificate with keytool for localhost and IP SAN

Generate a PKCS12 keystore with keytool -genkeypair, add DNS and IP Subject Alternative Names for localhost and 127.0.0.1, verify with keytool -list -v, and see why CN alone fails modern HTTPS hostname checks.

Published

Updated

Read time 7 min read

Reviewed byDeepak Prasad

keytool self-signed certificate with localhost and IP SAN banner

A self-signed certificate from keytool -genkeypair is the fastest way to stand up HTTPS on a local Java app, Tomcat, or Kafka broker. The catch is hostname verification: if you connect to https://127.0.0.1:8443 but the certificate only has CN=localhost and no IP in the Subject Alternative Name (SAN) extension, Java prints No subject alternative names present and the handshake fails.

This guide walks through generating a PKCS12 keystore with DNS and IP SAN entries for localhost, a custom hostname, and 127.0.0.1, verifying the extensions with keytool -list -v, and contrasting that with a cert that omits SAN. I ran every command on Ubuntu with OpenJDK 25 and kept the real terminal output below.

Tested on: Ubuntu 26.04 LTS; OpenJDK 25.0.3; kernel 7.0.0-27-generic.


Prerequisites

You need a working keytool binary, a place to write lab keystores, and optionally OpenSSL to double-check SAN in PEM form.

  • keytool on your PATH. If the shell reports command not found, follow Install keytool on Ubuntu.
  • A writable directory for lab files (this guide uses ~/keytool-lab/server/).
  • Optional: openssl to inspect PEM exports; see the keytool cheat sheet for related commands.

For background on what a keystore holds versus a truststore, see Keystore vs truststore in Java.


Why CN alone is not enough

Historically, TLS clients matched the server hostname against the certificate Common Name (CN). RFC 6125 and modern Java HTTPS check the Subject Alternative Name extension first. When you browse or call https://127.0.0.1, the client looks for an IPAddress SAN entry — not merely CN=localhost.

Connect URL Needs in certificate
https://localhost:8443 DNS:localhost in SAN (CN alone may work on some stacks but is unreliable)
https://127.0.0.1:8443 IP:127.0.0.1 in SAN
https://app.lab.local:8443 DNS:app.lab.local in SAN

keytool adds SAN at creation time with the -ext flag on -genkeypair (or on -certreq when you plan CA signing later).


Generate a self-signed certificate with SAN

The -genkeypair command creates a PKCS12 file that holds both the private key and a one-certificate chain. PKCS12 is the default store type on modern OpenJDK; you do not need -storetype JKS. Add DNS and IP names with -ext on the same line as key generation so they are baked into the self-signed cert.

Create a PKCS12 keystore:

bash
mkdir -p ~/keytool-lab/server
cd ~/keytool-lab/server

keytool -genkeypair \
  -alias localhost \
  -keyalg RSA \
  -keysize 2048 \
  -validity 365 \
  -dname "CN=localhost, OU=Lab, O=GoLinuxCloud, L=Lab, ST=Lab, C=US" \
  -ext "SAN=DNS:localhost,DNS:lab.local,IP:127.0.0.1" \
  -keystore localhost-san.p12 \
  -storetype PKCS12 \
  -storepass changeit \
  -noprompt

After the command completes, the keystore file appears in the current directory. Some JDK builds also print a short “Generating … key pair” message.

Run verbose list to confirm the alias, entry type, and SAN extension before wiring the file into a server:

bash
keytool -list -v -keystore localhost-san.p12 -storetype PKCS12 -storepass changeit

Near the bottom of the certificate section you should see SubjectAlternativeName with both DNS and IP entries:

text
Extensions: 

#1: ObjectId: 2.5.29.17 Criticality=false
SubjectAlternativeName [
  DNSName: localhost
  DNSName: lab.local
  IPAddress: 127.0.0.1
]

The same command also shows Entry type: PrivateKeyEntry and a one-certificate chain (self-signed issuer equals owner). If any expected DNS or IP name is missing from the SAN block, regenerate the keystore with a corrected -ext string before continuing.


Verify SAN with keytool and OpenSSL

keytool -list -v is enough when you stay inside Java tooling. Export the certificate to PEM when you want a second opinion from OpenSSL or need to share the public cert with a teammate.

Export the certificate from the keystore:

bash
keytool -exportcert -alias localhost -keystore localhost-san.p12 \
  -storetype PKCS12 -storepass changeit -rfc -file localhost-san.crt

openssl x509 -in localhost-san.crt -noout -text | grep -A3 "Subject Alternative Name"

Sample OpenSSL output:

text
X509v3 Subject Alternative Name: 
                DNS:localhost, DNS:lab.local, IP Address:127.0.0.1

Both tools should agree on the three SAN values. A mismatch usually means you exported the wrong alias or edited the keystore after generation.


What breaks when SAN is missing

To see the failure in context, generate a second keystore that omits -ext entirely. The cert still has CN=localhost, but modern clients ignore CN when checking IP addresses.

Generate a no-SAN keystore and confirm the extension is absent:

bash
keytool -genkeypair \
  -alias localhost \
  -keyalg RSA \
  -keysize 2048 \
  -validity 365 \
  -dname "CN=localhost, OU=Lab, O=GoLinuxCloud, L=Lab, ST=Lab, C=US" \
  -keystore no-san.p12 \
  -storetype PKCS12 \
  -storepass changeit \
  -noprompt

keytool -list -v -keystore no-san.p12 -storetype PKCS12 -storepass changeit | grep -i "Subject Alternative"

On the no-SAN keystore, grep returns nothing — there is no SubjectAlternativeName extension block. That is the root cause when clients connect by IP.

Start a minimal Java HTTPS server bound to 127.0.0.1 using no-san.p12 (see Keystore vs truststore for a HttpsServer example), then connect with a Java HttpClient to https://127.0.0.1:8443/. The client fails during hostname verification with one of these messages depending on the JDK and URL:

text
javax.net.ssl.SSLHandshakeException: No subject alternative names present
text
javax.net.ssl.SSLHandshakeException: No subject alternative names matching IP address 127.0.0.1 found

Connecting to https://localhost:8443 may appear to work on some JDK builds because the CN matches, but relying on CN is fragile — always add explicit SAN entries for every hostname and IP clients use.

SAN fixes hostname verification. It does not make a self-signed certificate trusted. For Java clients, import the exported certificate into a truststore or use a local CA — see Fix Java PKIX path building failed.

IMPORTANT
Add every name and IP your users type in the browser or config file. Kafka, Elasticsearch, and Tomcat connectors often bind to 127.0.0.1 while docs say localhost; list both in SAN.

Use the keystore with Java HTTPS

Once SAN is confirmed, point your server at localhost-san.p12. For plain Java JSSE configuration, the JVM needs the keystore path, password, and store type. Frameworks such as Spring Boot or Tomcat can also let you choose a specific alias.

For a plain Java process, pass system properties on the command line:

bash
java -Djavax.net.ssl.keyStore=localhost-san.p12 \
     -Djavax.net.ssl.keyStorePassword=changeit \
     -Djavax.net.ssl.keyStoreType=PKCS12 \
     YourServerMain

Spring Boot reads the same values from application.properties or environment variables:

properties
server.ssl.key-store=file:./localhost-san.p12
server.ssl.key-store-password=changeit
server.ssl.key-store-type=PKCS12
server.ssl.key-alias=localhost

Use classpath:localhost-san.p12 only after copying the keystore into src/main/resources. For a file beside the running JAR, use file:./localhost-san.p12.

After restart, the server presents the cert with SAN when clients connect to localhost or 127.0.0.1. Clients still need to trust a self-signed cert — import the public certificate into a truststore or use -Djavax.net.ssl.trustStore with a PKCS12 file that contains a trustedCertEntry for your cert. Production systems should replace the self-signed cert with a CA-signed chain; see Create a CSR with keytool and import a CA-signed certificate.


Troubleshooting

Use this table when SAN or keystore wiring still fails after regeneration:

Symptom Likely cause Fix
No subject alternative names present or No subject alternative names matching IP address ... No IP/DNS SAN for the URL you use Regenerate with -ext "SAN=DNS:...,IP:..."
alias already exists Reused alias in same keystore Pick a new alias or delete the old entry with keytool -delete
java.io.IOException: keystore password was incorrect Wrong -storepass Match the password used at -genkeypair time
SAN missing after CA signing CSR signed without SAN extensions Add -ext SAN=... on -certreq or ensure the CA copies extensions
Browser warns “not secure” Self-signed, not in OS trust store Expected for dev; import CA or use a public CA for production

References


Summary

Use keytool -genkeypair with -ext "SAN=DNS:localhost,DNS:lab.local,IP:127.0.0.1" so Java HTTPS accepts connections to both localhost and 127.0.0.1. Confirm SAN with keytool -list -v before wiring the PKCS12 file into Tomcat or Spring Boot. A certificate with only CN=localhost leaves out the IP SAN and triggers No subject alternative names present when clients target the numeric loopback address.


Frequently Asked Questions

1. How do I add localhost and 127.0.0.1 to a keytool certificate?

Run keytool -genkeypair with -ext "SAN=DNS:localhost,DNS:your.host,IP:127.0.0.1" on the same command as -genkeypair. Verify with keytool -list -v -keystore store.p12 and look for SubjectAlternativeName with DNSName and IPAddress entries.

2. Why does Java HTTPS fail with No subject alternative names present?

Modern TLS clients match the hostname or IP you connect to against the certificate SAN extension, not only the CN. A cert with CN=localhost but no IP SAN fails when you open https://127.0.0.1 because 127.0.0.1 is not listed as an IPAddress SAN.

3. Can keytool add SAN to an existing certificate?

keytool cannot edit SAN on an issued certificate. Regenerate the key pair with -genkeypair and the -ext SAN=... flag, or create a CSR with -certreq -ext SAN=... and have your CA sign a new certificate.

4. What key size and algorithm should I use with keytool for local HTTPS?

RSA 2048 with -keyalg RSA -keysize 2048 is widely compatible for dev and lab use. OpenJDK 25 also supports EC with -keyalg EC -groupname secp256r1 if your server stack allows it.

5. Is a keytool self-signed certificate enough for production?

No. Browsers and Java clients do not trust self-signed certs unless you import them into a truststore. Use a self-signed cert for local Tomcat, Spring Boot, or Kafka dev only, or replace it with a CA-signed chain for production.

6. What is the difference between CN and SAN in a keytool certificate?

CN (Common Name) in -dname is the legacy subject name. SAN (Subject Alternative Name) in -ext lists every DNS hostname and IP address the cert is valid for. Modern TLS clients prefer SAN for hostname verification, and CN fallback is deprecated and unreliable. Always include every DNS name and IP address clients will use in the SAN extension.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …