Build a local certificate authority

Create a certificate signing request

Generate a server key and CSR containing the identity and public key to be signed.

A server never sends its private key to a certificate authority. It sends a certificate signing request (CSR) instead. The CSR contains the public key, the identity the server wants, and a signature made with the private key. That signature proves the requester holds the key, without revealing it.

The CA reads the request, decides whether that identity is allowed, and returns a signed certificate. This split is what makes public CAs possible. Let’s Encrypt signs millions of certificates and never sees a single private key.

Let’s create a key and a CSR for app.lab.test in one command:

openssl req -new -newkey rsa:2048 -nodes -keyout app.key -out app.csr -subj '/CN=app.lab.test' -addext 'subjectAltName=DNS:app.lab.test,DNS:localhost'

A few options to understand. -newkey rsa:2048 generates a fresh 2048-bit RSA key next to the request. -nodes leaves that key unencrypted, so the lab server can start without a passphrase prompt. That’s fine here, because the file stays on your machine with tight permissions. -addext adds both hostnames to the subjectAltName request, which is where clients will look for names.

Before handing anything to a CA, inspect it:

openssl req -in app.csr -noout -text -verify

-verify checks the CSR’s own signature and prints a confirmation line first:

Certificate request self-signature verify OK
Certificate Request:
    Data:
        Subject: CN=app.lab.test
        ...
        Requested Extensions:
            X509v3 Subject Alternative Name:
                DNS:app.lab.test, DNS:localhost

Confirm two things in that output. Subject shows CN=app.lab.test, and Requested Extensions lists both DNS names. If either is wrong, fix the CSR now. It costs one command here and a confusing afternoon later.

The common mistake is skipping -addext and requesting only a common name. Some signing setups won’t add a SAN for you. A certificate with no SAN entries fails name verification in every modern client, even though every field looks reasonable. Catch it at the CSR stage.

Remember which file is which. app.csr contains only public information, so you can send it to a signer or paste it in a ticket. app.key is the secret. If you ever paste a CSR somewhere, double-check you didn’t grab the key by mistake. The two files sit next to each other, and the first line of each tells you what it is.

Lesson completed