Build a local certificate authority

Sign a server certificate

Issue a short-lived leaf certificate with server constraints and verify it against the lab root.

Signing is the moment your lab CA acts as an authority. It reads the CSR, decides the requested identity is allowed, and produces a certificate. A real CA makes that decision with domain validation. In our lab you are the policy, so the decision is yours. The mechanics are the same.

Let’s sign the request, and copy its SAN extension into the certificate:

openssl x509 -req -in app.csr -CA lab-ca.crt -CAkey lab-ca.key -CAcreateserial -out app.crt -days 14 -sha256 -copy_extensions copy

Let’s walk through the options. -CA and -CAkey identify the signer, and you’ll be prompted for the CA key passphrase. -CAcreateserial creates a lab-ca.srl file so every issued certificate gets a unique serial number. -days 14 keeps the leaf short-lived.

The one that trips people up is -copy_extensions copy. Without it, OpenSSL drops the requested extensions, and your certificate comes out with no subjectAltName at all. I tried it: sign without that flag and -ext subjectAltName prints No extensions in certificate. A useful leaf needs the right extensions, not just a signed name.

Now verify the result against the lab root:

openssl verify -CAfile lab-ca.crt app.crt
# app.crt: OK

app.crt: OK means the path from this leaf to your root validates. The signature checks out, the dates are current, and the issuer is trusted, because you told verify to trust it with -CAfile.

Then inspect the fields that matter:

openssl x509 -in app.crt -noout -issuer -dates -ext subjectAltName
# issuer=CN=Practical TLS Lab CA
# notBefore=Sep  8 16:45:22 2026 GMT
# notAfter=Sep 22 16:45:22 2026 GMT
# X509v3 Subject Alternative Name:
#     DNS:app.lab.test, DNS:localhost

The issuer is our CA, the window is two weeks, and both names are there.

If the SAN block is missing, you forgot -copy_extensions copy. Re-sign. The CSR is still valid, so the fix is one command. This is the single most common issuance mistake I see. Its symptom shows up much later, as a hostname mismatch that makes no sense until someone decodes the certificate.

Keep lab roots and lab certificates short-lived, and don’t reuse this CA for anything real. A production internal CA needs revocation, an issuance policy someone audits, and protected key storage. Our two files provide none of that, and that’s fine for learning.

Lesson completed