Files
muxplex/docs/TRUSTING_THE_LOCAL_CA.md
T
Brian Krabach 33ea016f7e feat(tls): add --method ca for persistent local CA-signed leaves
Adds a new TLS certificate method ('ca') that generates a persistent local
Certificate Authority and signs leaf certificates against it. This allows
users to install the CA once on client devices and have browser-trusted
HTTPS for plain LAN names (my-host, 192.168.1.5, my-host.local) without
requiring external services like Tailscale.

Key improvements over existing --method selfsigned:
- Persistent CA: the root cert is stored separately and never rotates,
  so leaf certificate renewal doesn't require re-trusting on clients
- Auto-detected SANs: includes LAN IP, tailnet name (if applicable),
  hostname, and localhost variants
- Per-platform install guide: comprehensive docs/TRUSTING_THE_LOCAL_CA.md
  with Windows PowerShell, macOS/Linux, iOS, and Android install steps

Solves PWA installation issues on Windows machines with corporate IT
policy blocking Tailscale: PWAs now persist in standalone mode when the
local CA is trusted in the Windows user cert store.

Changes:
- muxplex/tls.py: added _default_lan_ip(), _default_tailnet_name(),
  generate_local_ca(), generate_leaf_signed_by_ca()
- muxplex/cli.py: added 'ca' to --method choices, wired setup_tls()
  to generate CA + leaf with auto-detected SAN
- docs/TRUSTING_THE_LOCAL_CA.md: comprehensive per-platform install guide
- README.md: added --method ca documentation and cross-links
- CHANGELOG.md: documented v0.5.0 features

Generated with Amplifier

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
2026-05-06 12:37:34 -07:00

12 KiB

Trusting the local CA

This guide walks through getting browser-trusted HTTPS for muxplex on plain LAN names (e.g. https://my-host:8088, https://192.168.1.5:8088) without requiring Tailscale on every client and without buying a public domain.

When this is the right approach

Use muxplex setup-tls --method ca when all of the following apply:

  • You want to access muxplex by its plain LAN hostname (my-host) or IP address.
  • Tailscale isn't an option on every client device (corporate IT policy blocks it; can't install on a phone; the URL must be the bare hostname; etc.).
  • You don't want to buy and configure a public domain just to point it at a LAN IP.
  • You can install a root certificate on each client device. A user-level (CurrentUser) trust install is enough; admin/elevation is not needed on most platforms.

If Tailscale is acceptable on every client, prefer --method tailscale — it gives you a real Let's Encrypt certificate with zero per-client setup beyond the Tailscale app itself.

Why this fixes PWA install + reopen flakiness

Browsers (Chromium, WebKit, Firefox) refuse to keep an installed PWA in standalone mode against an origin that doesn't present a trusted certificate. The behavior the user sees:

  1. First install: click through the cert warning, install the PWA, it works.
  2. Reopen: the OS launches the PWA shell; the browser re-evaluates the cert; the click-through exception isn't re-applied to the standalone launch path; the PWA is kicked back into a regular browser tab to surface the warning. Once you click through there, the standalone shell isn't re-entered.

A self-signed leaf in the trust store can fix this, but you have to re-import on every cert rotation. A persistent CA in the trust store fixes it permanently — leaf rotations don't break trust because clients trust the issuer, not the leaf.

How the ca method works

muxplex setup-tls --method ca

generates and persists a private CA, then signs a leaf TLS certificate with it.

Artifact Path Validity
CA cert ~/.config/muxplex/ca/muxplex-ca.crt 10 years
CA private key ~/.config/muxplex/ca/muxplex-ca.key (mode 0600) 10 years
Leaf cert ~/.config/muxplex/muxplex.crt 397 days
Leaf private key ~/.config/muxplex/muxplex.key (mode 0600) 397 days

The leaf's Subject Alternative Name (SAN) automatically includes:

  • <hostname>socket.gethostname()
  • <hostname>.local — for mDNS / Bonjour resolution
  • localhost
  • The Tailscale MagicDNS name (<host>.<tailnet>.ts.net) — only if tailscale status reports one
  • IPs: 127.0.0.1, ::1, and the primary outbound IPv4 address of the host (auto-detected)

The CA cert has the standard CA extensions (BasicConstraints CA:TRUE, pathlen:0, KeyUsage keyCertSign+cRLSign), so OS trust stores and browsers accept it as a Root.

Re-running muxplex setup-tls --method ca is idempotent for the CA: if ~/.config/muxplex/ca/muxplex-ca.crt and .key already exist, they're reused — only a fresh leaf is generated. Clients that already trust the CA don't need to re-import anything when the leaf rotates.

Per-platform install instructions

Windows (Chrome / Edge)

PowerShell, no admin needed. Replace <path-to-ca.crt> with the path you copied the file to.

Import-Certificate -FilePath <path-to-ca.crt> -CertStoreLocation Cert:\CurrentUser\Root

If you'd rather not type the path, paste the PEM inline:

$pem = @'
-----BEGIN CERTIFICATE-----
... paste PEM contents from ~/.config/muxplex/ca/muxplex-ca.crt ...
-----END CERTIFICATE-----
'@
$path = "$env:TEMP\muxplex-ca.crt"
$pem | Set-Content -Path $path -Encoding ASCII
Import-Certificate -FilePath $path -CertStoreLocation Cert:\CurrentUser\Root

Verify:

Get-ChildItem Cert:\CurrentUser\Root | Where-Object Subject -like "*muxplex Local CA*"

Restart Edge / Chrome (fully quit, not just close the window) for them to re-read the cert store. Firefox uses its own store — see the Firefox section below.

macOS (Safari / Chrome / Edge)

sudo security add-trusted-cert -d -r trustRoot \
    -k /Library/Keychains/System.keychain \
    /path/to/muxplex-ca.crt

This adds the CA to the System keychain and marks it as trusted for SSL. Safari, Chrome, and Edge all use the system keychain. Firefox uses its own store — see below.

Verify:

security find-certificate -c "muxplex Local CA" /Library/Keychains/System.keychain

To remove later:

sudo security delete-certificate -c "muxplex Local CA" /Library/Keychains/System.keychain

Linux (system-wide)

sudo cp /path/to/muxplex-ca.crt /usr/local/share/ca-certificates/muxplex-ca.crt
sudo update-ca-certificates

This covers the system trust store used by curl, git, etc., and Chrome / Chromium / Edge on most distros.

For RPM-based systems (Fedora, RHEL):

sudo cp /path/to/muxplex-ca.crt /etc/pki/ca-trust/source/anchors/muxplex-ca.crt
sudo update-ca-trust

Firefox on Linux uses its own store — see below.

iOS / iPadOS (Safari + Web Apps)

  1. Get the CA cert onto the device — easiest is AirDrop from a Mac, or email the .crt file to yourself.
  2. Open the file in Mail or Files; iOS will offer to download a profile.
  3. Go to Settings → General → VPN & Device Management → Downloaded Profile and tap Install.
  4. Critical second step: go to Settings → General → About → Certificate Trust Settings and toggle Enable Full Trust for Root Certificates for the muxplex CA. Without this, Safari and WKWebView don't honor the cert.

After both steps, Safari accepts the cert and PWAs added to the home screen launch in standalone mode without warnings.

Android (Chrome)

Chrome on Android uses the system trust store, but user-installed CAs are flagged with a persistent "your traffic may be monitored" notice. The basic flow:

  1. Copy the .crt file to the device (USB, Drive, Files, etc.).
  2. Settings → Security & privacy → More security & privacy → Encryption & credentials → Install a certificate → CA certificate.
  3. Acknowledge the warning and select the file.

Result varies by Android version and OEM. Some PWA features (service workers, push) may degrade in this state; basic browser HTTPS works.

For a more reliable Android experience, prefer Tailscale or a public-domain Let's Encrypt path.

Firefox (any platform)

Firefox maintains its own cert store — system trust install does not apply.

  1. Tools → Settings → Privacy & Security → Certificates → View Certificates.
  2. Authorities tab → Import.
  3. Pick muxplex-ca.crt.
  4. Tick Trust this CA to identify websites, click OK.

Verification

After installing, fully restart the browser, then visit https://<host>:<port>/. You should see a green padlock with no warnings.

You can also verify from the command line on any client:

# Linux/macOS
curl -v https://<host>:<port>/ 2>&1 | grep -i "verify\|certificate verify"

Look for SSL certificate verify ok. — if you see SSL certificate verify failed (and you're sure you imported the CA into the right store and restarted the browser), see the Troubleshooting section.

Cert rotation

The leaf cert is valid for 397 days, which keeps it under the 398-day public-trust ceiling that some browsers enforce even for privately-installed CAs. To rotate:

muxplex setup-tls --method ca
muxplex service restart

The CA is reused (clients keep trusting it), the leaf is regenerated, and the server picks up the new leaf on restart. No client-side re-trust is required — that's the whole point of using a CA structure instead of a bare self-signed cert.

The CA itself is valid for 10 years; you only need to re-deploy a new CA + re-trust on clients once per decade.

Troubleshooting

"Browser still shows the warning after I imported the CA."

  • Did you fully quit the browser and reopen it? (Closing the window isn't enough on Chrome/Edge.)
  • Are you visiting a URL that's actually in the cert's SAN? Check muxplex setup-tls --status to see the SAN list. Common gotcha: the cert covers my-host but you're visiting https://my-host.lan.lan isn't in the SAN.
  • Did you import into the right store?
    • Windows: CurrentUser\Root (not My/Personal). Verify with Get-ChildItem Cert:\CurrentUser\Root | Where-Object Subject -like "*muxplex*".
    • macOS: System keychain. Verify with security find-certificate -c "muxplex Local CA".
    • Linux: /usr/local/share/ca-certificates/ then update-ca-certificates.
    • Firefox: imported under Authorities, not Your Certificates.

"curl says verify ok but the browser still warns."

The browser uses a different trust store than the system CLI in some configurations. Most often this is a Firefox issue (separate store) or a stale browser session that hasn't reloaded the cert store.

"My LAN IP changed and now the cert doesn't cover the new IP."

The leaf SAN is fixed at generation time. Re-run muxplex setup-tls --method ca to regenerate the leaf with the new auto-detected LAN IP, then muxplex service restart. The CA stays the same, so clients don't need to re-trust.

"muxplex setup-tls --method ca fails with cryptography import errors."

Make sure you're running muxplex >= 0.5.0 (which introduced the ca method). Older versions accept --method ca only if it's been backported.

Security notes

  • The CA's private key (~/.config/muxplex/ca/muxplex-ca.key) is the secret that lets you mint TLS certs trusted by every device that's installed your CA. Treat it like an SSH private key. It's written with mode 0600. Don't copy it off the host. Don't commit it to a repo.
  • Anyone with that key can sign certs for any hostname that targets a client trusting your CA. The blast radius is "every device on which you installed this CA." That's much smaller than a public CA's blast radius, but worth understanding.
  • The CA cert (muxplex-ca.crt) is public information — it goes onto every client device. Sharing it doesn't reduce your security. Sharing the .key does.
  • The CA's pathlen is set to 0, meaning it can sign leaf certificates but cannot issue intermediate sub-CAs. This limits its ability to be misused if the key is ever compromised.
  • This CA is local to one host. If you run muxplex on multiple hosts and want browser-trusted certs on all of them, each host has its own CA — install all the CAs on each client. There's no harm in trusting multiple muxplex CAs; they're independent.

Comparison with other methods

Method What it generates Per-client setup Browser-trusted?
tailscale Real Let's Encrypt cert for <host>.<tailnet>.ts.net Install + sign in to Tailscale everywhere
mkcert Cert signed by mkcert's local CA, installed into the host's trust store automatically Manually install mkcert's CA into each other client on the host; manual elsewhere
ca (this) Cert signed by a persistent muxplex CA Manually install muxplex's CA on each client (one-time) after install
selfsigned Self-issued leaf, no CA structure None — but the cert must be re-imported on every rotation unless re-imported

The ca method's key advantage over mkcert for this use case is that mkcert's -install step only configures the host it runs on. To get clients to trust mkcert certs, you'd still need to copy mkcert's root CA to each client and import it — exactly the same step as --method ca, but with mkcert as a dependency. --method ca cuts out the mkcert installation requirement and uses the same cryptography library that's already a muxplex dependency.

See also