The disk is lava: generate your test certs in memory

You can't out-engineer the speed of light, only avoid the distance. Grace Hopper, the computing pioneer, made that physical: she handed people a length of wire and called it a nanosecond, about thirty centimeters, the distance light travels in a billionth of a second. A microsecond was three hundred meters of it. Generating a certificate is about twenty kilometers of that wire; reading one off disk is sixty; fetching it from network storage is hundreds. Same units, very different trips. A test that reads a cert from disk takes the long one, and committed a private key to your repo to do it. Generate it instead.

You're not testing the standard library

When a test needs a TLS cert it's tempting to treat it as precious and check it in. But you're almost never testing "does P-256 signing work" or "is the handshake correct": you trust crypto/tls and crypto/x509 for that, the same way you trust the compiler. You're testing your logic: that your option builder wires the cert into the transport, that your chain verification accepts a good chain and rejects a broken one, that your load-cert-from-a-path API reads the file.

The cert is a prop. Props don't belong in your repo.

Generate it on the fly

A self-signed cert is a dozen lines of standard library:

func selfSigned(t *testing.T) tls.Certificate {
	t.Helper()
	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	require.NoError(t, err)

	tmpl := &x509.Certificate{
		SerialNumber:          big.NewInt(1),
		Subject:               pkix.Name{CommonName: "test"},
		NotBefore:             time.Now().Add(-time.Hour),
		NotAfter:              time.Now().Add(24 * time.Hour), // minted per run, can't rot
		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		IsCA:                  true,
		BasicConstraintsValid: true,
		DNSNames:              []string{"localhost"},
	}
	der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
	require.NoError(t, err)

	return tls.Certificate{Certificate: [][]byte{der}, PrivateKey: key}
}

Validity is minted relative to time.Now() on every run, so it can't expire. Signing a leaf with a CA is the same call with a different parent; a chain is just Certificate: [][]byte{leaf, intermediate}.

The fastest read is no read

Generating the cert beats reading a precreated one off disk:

Generating an ECDSA P-256 cert took ~63 µs; reading the same one off disk took ~200 µs (means of 10 runs, variance under 5%). The generated path holds the cert bytes and a live key, so there's nothing to parse. Reading from disk, X509KeyPair has to parse the cert and the private key and check they match (~13 µs), and that's on top of ~190 µs spent reading the files. Generation skips both. Pure CPU.

And this is close to best case all round. An M5 Max is about the fastest CPU and NVMe you can buy today, so both sides sit near their floor: P-256 keygen is small and heavily optimized, and the drive is as quick as they come. Even here, the read is the bigger cost. Drop to real CI hardware and both slow down, but storage degrades more: a containerized overlay filesystem, a cold page cache, and often shared or network storage, where a read is measured in milliseconds, not microseconds.

This only holds because ECDSA P-256 keygen is microseconds. Generate RSA-2048 instead and it jumps to ~38 ms, 600× slower, paid on every test. Pick a cheap key; the tests care about chain verification, not key type.

The takeaway: CPU is cheap, IO isn't

The certs are just the example. The rule underneath: CPU time is cheap and disk IO is precious, so when you can spend cycles to skip a syscall, spend them.

In this specific case, skipping the disk throws in a few bonuses beyond speed:

  • Nothing expires: no fixture quietly rotting on a date you forgot, no red build on a branch nobody touched.
  • No secrets in the repo: A committed private key is bait for every secret scanner you run (push protection, gitleaks, trufflehog), a steady drip of alerts to triage. Generated keys never hit disk, except a throwaway t.TempDir() when an API genuinely reads a path.
  • No fixture toolchain: No generate.sh, no openssl, no tribal knowledge about who regenerates the certs and how. The trust relationships live in code, right next to the test that leans on them.