# How mkcert Creates and Manages Local CA Certificates: A Deep Dive into the Source Code

> Explore mkcert source code to understand how it generates and manages local CA certificates, signing leaf certificates and installing roots into system trust stores.

- Repository: [Filippo Valsorda/mkcert](https://github.com/FiloSottile/mkcert)
- Tags: deep-dive
- Published: 2026-03-05

---

**mkcert creates a local Certificate Authority by generating an RSA-3072 key pair and self-signed X.509 certificate stored in a platform-specific directory (CAROOT), then reuses this CA to sign leaf certificates while providing commands to install or uninstall the root certificate from system trust stores.**

The `FiloSottile/mkcert` tool simplifies local HTTPS development by acting as its own certificate authority. Understanding how mkcert creates and manages local CA certificates reveals why it requires zero configuration while producing browser-trusted certificates. This analysis examines the actual implementation in the Go source code, tracing the lifecycle from initial CA generation to system-wide trust integration.

## The CAROOT Directory: Where mkcert Stores Your Local CA

Before creating any certificates, mkcert determines where to persist the root CA files. This location, known as **CAROOT**, varies by operating system and is resolved by the `getCAROOT()` function in [`main.go`](https://github.com/FiloSottile/mkcert/blob/main/main.go).

### Platform-Specific Default Paths

- **Linux and BSD**: `~/.local/share/mkcert`
- **macOS**: `~/Library/Application Support/mkcert`  
- **Windows**: `%LocalAppData%\mkcert`

You can override this location by setting the `CAROOT` environment variable. The `getCAROOT()` implementation (lines 40-64 in [`main.go`](https://github.com/FiloSottile/mkcert/blob/main/main.go)) handles path expansion and ensures the directory exists before any file operations occur.

## CA Creation and Loading Logic

The `(*mkcert).loadCA()` method in [`cert.go`](https://github.com/FiloSottile/mkcert/blob/main/cert.go) orchestrates whether to load an existing CA or create a new one. This method checks for the existence of `rootCA.pem` in the CAROOT directory using `pathExists(filepath.Join(m.CAROOT, rootName))`.

### Loading an Existing CA

If the root certificate file exists, `loadCA()` (lines 81-108 in [`cert.go`](https://github.com/FiloSottile/mkcert/blob/main/cert.go)) parses the PEM-encoded certificate into `m.caCert` and the private key into `m.caKey`. This ensures that subsequent certificate generations maintain the same trust chain, preventing browser warnings about changing CAs.

### Generating a New CA

When no existing CA is found, `newCA()` (lines 110-148 in [`cert.go`](https://github.com/FiloSottile/mkcert/blob/main/cert.go)) executes the following steps:

1. **Key Generation**: Calls `generateKey(true)` to create an RSA-3072 bit private key
2. **Certificate Template**: Constructs an X.509 template with a 10-year validity period, sets the `IsCA` flag to true, and includes the current username and hostname in the Subject field
3. **Self-Signing**: Uses `x509.CreateCertificate(rand.Reader, tpl, tpl, pub, priv)` to produce the final certificate
4. **File Persistence**: Writes the private key to `rootCA-key.pem` with mode `0400` (read-only for owner) and the certificate to `rootCA.pem` with mode `0644`

The filenames are defined as constants in [`main.go`](https://github.com/FiloSottile/mkcert/blob/main/main.go) (lines 52-54):

```go
rootName    = "rootCA.pem"
rootKeyName = "rootCA-key.pem"

```

## Root Certificate Storage and File Structure

Once generated, the CA persists in two files under CAROOT:

- **`rootCA.pem`**: The public certificate (mode 0644)
- **`rootCA-key.pem`**: The private key material (mode 0400)

mkcert uses `ioutil.WriteFile` to atomically write these files. The deterministic naming convention ensures that every invocation of the tool can locate the same CA, creating a consistent trust anchor across all generated leaf certificates.

## Installing the Local CA into System Trust Stores

The `(*mkcert).install()` method triggers platform-specific installation routines that copy the root certificate into various trust stores. This process enables browsers and system tools to trust certificates signed by your local CA without security warnings.

### Linux and macOS Integration

On Unix-like systems, `installPlatform()` (in [`truststore_linux.go`](https://github.com/FiloSottile/mkcert/blob/main/truststore_linux.go), lines 51-74) executes:

```bash
sudo tee /etc/pki/ca-trust/source/anchors/mkcert_development_CA_<serial>.pem
update-ca-trust extract

```

The `caUniqueName()` function (lines 66-68 in [`cert.go`](https://github.com/FiloSottile/mkcert/blob/main/cert.go)) generates the unique filename from the CA's serial number, preventing collisions if multiple mkcert instances exist.

### Windows, NSS, and Java Support

Separate files handle specific trust stores:

- **[`truststore_darwin.go`](https://github.com/FiloSottile/mkcert/blob/main/truststore_darwin.go)**: macOS Keychain integration using `security` commands
- **[`truststore_windows.go`](https://github.com/FiloSottile/mkcert/blob/main/truststore_windows.go)**: Windows certificate store via CryptoAPI
- **[`truststore_nss.go`](https://github.com/FiloSottile/mkcert/blob/main/truststore_nss.go)**: Firefox and Chrome NSS database modification
- **[`truststore_java.go`](https://github.com/FiloSottile/mkcert/blob/main/truststore_java.go)**: Java `cacerts` keystore updates

Each implementation calls the appropriate system commands to import `rootCA.pem` with the unique name generated by `caUniqueName()`.

## Generating Leaf Certificates Signed by the Local CA

With the CA loaded into memory (`m.caCert` and `m.caKey`), the `makeCert(hosts []string)` function (in [`cert.go`](https://github.com/FiloSottile/mkcert/blob/main/cert.go)) creates end-entity certificates:

1. Generates a new key (RSA-2048 or ECDSA-P256 depending on preferences)
2. Builds a certificate template containing the requested hostnames/IPs as Subject Alternative Names (SANs)
3. Signs the certificate using `x509.CreateCertificate(..., m.caCert, m.caKey)`
4. Outputs PEM files (or PKCS#12 with the `-pkcs12` flag)

For Certificate Signing Request (CSR) workflows, `makeCertFromCSR()` (lines 209-280 in [`cert.go`](https://github.com/FiloSottile/mkcert/blob/main/cert.go)) reads a CSR file, copies its subject and extensions into a new template, and signs it with the local CA.

## Uninstalling and Cleaning Up the CA

The `(*mkcert).uninstall()` method reverses the installation process. It calls `uninstallPlatform()`, `uninstallNSS()`, and `uninstallJava()` to remove the certificate from each trust store. For example, `uninstallPlatform()` in [`truststore_linux.go`](https://github.com/FiloSottile/mkcert/blob/main/truststore_linux.go) (lines 77-99) removes the PEM file from the system anchors directory and refreshes the certificate cache.

This operation removes trust but preserves the CA files in CAROOT, allowing you to reinstall later without regenerating the root certificate.

## Summary

- **mkcert stores your local CA** in a platform-specific CAROOT directory (`~/.local/share/mkcert` on Linux), determined by `getCAROOT()` in [`main.go`](https://github.com/FiloSottile/mkcert/blob/main/main.go)
- **The CA is created once** via `newCA()` in [`cert.go`](https://github.com/FiloSottile/mkcert/blob/main/cert.go) using RSA-3072 keys and a 10-year self-signed certificate, then reused via `loadCA()`
- **Two files comprise the CA**: `rootCA.pem` (public certificate, mode 0644) and `rootCA-key.pem` (private key, mode 0400)
- **System trust integration** happens through platform-specific files like [`truststore_linux.go`](https://github.com/FiloSottile/mkcert/blob/main/truststore_linux.go), which copy the certificate to system directories using unique names derived from the CA serial number
- **Leaf certificates** are generated by `makeCert()` and signed with the loaded CA, ensuring they validate against the installed root

## Frequently Asked Questions

### How does mkcert ensure the local CA is only created once?

mkcert checks for existing files before generation. The `loadCA()` function in [`cert.go`](https://github.com/FiloSottile/mkcert/blob/main/cert.go) verifies `rootCA.pem` exists using `pathExists()`. If found, it loads the existing certificate and key into memory; if not, it triggers `newCA()` to generate fresh credentials. This prevents accidental CA regeneration that would invalidate previously issued certificates.

### What cryptographic standards does mkcert use for the root CA?

According to the source code in [`cert.go`](https://github.com/FiloSottile/mkcert/blob/main/cert.go), mkcert generates an **RSA-3072 bit key** for the root CA and sets the certificate validity to **10 years**. The certificate includes the `IsCA` basic constraint set to true and derives its subject name from the current system user and hostname, creating a unique but identifiable local authority.

### Can I move my mkcert CA to another machine?

Yes. The entire CA consists of the two files in your CAROOT directory (`rootCA.pem` and `rootCA-key.pem`). Copy these files to the equivalent directory on another machine, or set the `CAROOT` environment variable to a shared location. mkcert will load the same CA on the new machine, allowing seamless certificate portability across development environments.

### Does mkcert support removing the CA from system trust stores?

Yes. Running `mkcert -uninstall` triggers `(*mkcert).uninstall()`, which calls platform-specific removal functions like `uninstallPlatform()` in [`truststore_linux.go`](https://github.com/FiloSottile/mkcert/blob/main/truststore_linux.go). These functions delete the CA certificate from system directories, NSS databases, and Java keystores, effectively untrusting the root without deleting the underlying files from CAROOT.