Friend Access with mTLS
4got uses mutual TLS (mTLS) client certificates to control who can access your instance. This is a deliberate choice over OAuth, SSO, or any third-party identity provider — your friends prove their identity with a cryptographic certificate that you issued, and no external service is involved in the handshake.
Why mTLS instead of OAuth?
OAuth solves “let strangers log in with their existing accounts.” You’re solving “let my friends in and everyone else out.” Different problem, different tool.
With mTLS:
- No third party knows who uses your search engine. There’s no Google/GitHub/Discord OAuth callback leaking the fact that your friend searched for something. The authentication is a TLS handshake between the client and your server — no one else is in the loop.
- No tokens to steal. A stolen OAuth token gives an attacker your session. A stolen client cert without the private key is useless. The private key never leaves your friend’s device.
- No session hijacking. The TLS handshake happens before HTTP. There are no cookies, headers, or query parameters that a man-in-the-middle or XSS attack could extract.
- No infrastructure dependency. Your auth doesn’t break when GitHub is down, when Google changes their OAuth scopes, or when Discord decides to deprecate an API version.
- Revocation is simple. Remove a friend’s certificate serial number from the trust list. Done. No “invalidate all sessions” dance.
The tradeoff is that cert distribution requires a one-time manual step per friend. For a friends-and-family instance, this is a feature, not a bug — it means access can’t be self-service, which is exactly what you want.
How it works
Friend's browser Your server
| |
|------- TLS ClientHello -------------->|
|<------ TLS ServerHello + ServerCert --|
|<------ CertificateRequest ------------|
|------- ClientCert (signed by your CA) |
|------- CertificateVerify ------------>|
| |
| (server checks: is this cert signed |
| by my CA? is it not revoked?) |
| |
|<------ TLS Finished ------------------|
|------- HTTP request ----------------->|
|<------ Search results ----------------|
If the client doesn’t present a valid certificate, the TLS handshake fails. The connection is refused before any HTTP traffic is exchanged. Unauthorized users never reach 4got — they get a TLS error, not a login page.
Setup
1. Create your Certificate Authority
You are the CA. This is a one-time operation.
mkdir -p /etc/4got/certs && cd /etc/4got/certs
# Generate CA private key
openssl genrsa -out ca.key 4096
# Generate CA certificate (valid 10 years)
openssl req -new -x509 -key ca.key -out ca.crt -days 3650 \
-subj "/CN=4got Friend CA/O=4got"
Guard ca.key with your life. Anyone with this file can issue client certs that your server will trust. Store it offline when not issuing new certs.
2. Issue a client certificate for a friend
Run this for each friend. Replace alice with their name.
# Generate friend's private key
openssl genrsa -out alice.key 2048
# Generate a certificate signing request
openssl req -new -key alice.key -out alice.csr \
-subj "/CN=alice/O=4got Friends"
# Sign with your CA (valid 2 years)
openssl x509 -req -in alice.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out alice.crt -days 730
# Package into a .p12 file (this is what your friend installs)
openssl pkcs12 -export -out alice.p12 \
-inkey alice.key -in alice.crt -certfile ca.crt \
-name "4got (alice)"
You’ll be prompted for an export password. Pick something simple — your friend needs to type it once during import. You can use an empty password if you trust the delivery channel.
Clean up the intermediate files:
rm alice.key alice.csr alice.crt
# Keep alice.p12 — this is what you give to your friend
3. Give the .p12 to your friend
Preferably via sneakernet — hand them a USB drive, AirDrop it across the table, or NFC beam it from your phone. Physical proximity is the strongest delivery channel because there’s no server, no log, no metadata trail, and no possibility of interception.
If physical handoff isn’t possible, send the .p12 over an end-to-end encrypted channel (Signal, Matrix with verification, encrypted email). Communicate the export password through a different channel than the file itself (e.g., file over Signal, password spoken over a phone call).
Never send client certificates over unencrypted email, Discord DMs, Slack, or any platform that stores messages server-side. The .p12 contains your friend’s private key — if it leaks, you need to revoke and reissue.
4. Configure Caddy
Caddy handles the public-facing TLS (automatic Let’s Encrypt) and verifies client certificates against your CA.
search.yourdomain.com {
tls {
client_auth {
mode require_and_verify
trusted_ca_cert_file /etc/4got/certs/ca.crt
}
}
reverse_proxy localhost:8888
}
That’s the entire Caddyfile. Caddy:
- Obtains and renews a Let’s Encrypt certificate for
search.yourdomain.comautomatically - Requires every connecting client to present a certificate signed by your CA
- Rejects unauthorized connections at the TLS layer (before HTTP)
- Forwards authenticated requests to 4got on localhost
5. Your friend installs the certificate
Android
Tap the .p12 file (from Files, a download, or a file manager). Android prompts to install it as a user certificate. Enter the export password. Chrome and other browsers will present it automatically when connecting to your instance.
iOS / iPadOS
Tap the .p12 file to open it. Go to Settings → General → VPN & Device Management and install the profile. Enter the export password. Safari and all apps using the system keychain will present it automatically.
macOS
Double-click the .p12 file. Keychain Access opens and imports it. Enter the export password. Safari and Chrome use it automatically. Firefox users: Preferences → Privacy & Security → Certificates → View Certificates → Import.
Windows
Double-click the .p12 file. The Certificate Import Wizard opens. Follow the prompts, enter the export password, and import into the “Personal” store. Edge and Chrome use it automatically. Firefox users: Settings → Privacy & Security → Certificates → View Certificates → Import.
Linux
Firefox: Preferences → Privacy & Security → Certificates → View Certificates → Your Certificates → Import. Select the .p12 file and enter the export password. Chromium-based browsers: use the system NSS certificate store or import via chrome://settings/certificates.
None of this touches VPN settings, proxy configuration, or network setup. The certificate is a TLS identity credential — it lives in the browser/OS certificate store alongside everything else and is presented during the TLS handshake when the server requests it.
Revoking a friend’s access
Quick method: reissue the CA
If you only have a handful of friends and need to revoke one, the simplest approach is to regenerate the CA and reissue certificates to everyone except the revoked friend. This is practical when you have fewer than ~20 friends.
Proper method: Certificate Revocation List (CRL)
For larger deployments or when you don’t want to bother everyone:
# Create a CRL (first time)
openssl ca -gencrl -out /etc/4got/certs/crl.pem \
-keyfile /etc/4got/certs/ca.key \
-cert /etc/4got/certs/ca.crt
# Revoke a specific certificate
openssl ca -revoke alice.crt \
-keyfile /etc/4got/certs/ca.key \
-cert /etc/4got/certs/ca.crt
# Regenerate the CRL
openssl ca -gencrl -out /etc/4got/certs/crl.pem \
-keyfile /etc/4got/certs/ca.key \
-cert /etc/4got/certs/ca.crt
Note: CRL-based revocation with Caddy requires checking the tls directive documentation for your Caddy version, as CRL support has varied across releases. An alternative is to maintain a list of allowed certificate fingerprints and check them in a Caddy handler.
FAQ
Why not Tailscale / WireGuard? Those are great for private access but they require your friends to install a VPN client and join your network. mTLS works in any browser on any device with zero additional software. It’s also invisible to the user after the one-time cert install — they just open the URL and search.
Why not Cloudflare Access? Cloudflare terminates TLS at their edge and inspects all HTTP traffic in plaintext. There is no Cloudflare configuration where they cannot read your search queries. If you’re building a privacy-first search engine, routing all traffic through a surveillance company defeats the purpose.
Why not HTTP basic auth? Passwords get shared, written down, reused, and phished. Client certificates are cryptographic keys bound to a device. Basic auth is also transmitted on every request (Base64 encoded, not encrypted beyond TLS), while client cert auth happens once during the TLS handshake.
Why not OAuth2 Proxy / Authelia / Authentik? These are good tools for multi-tenant SaaS applications. For a friends-only search engine, they add a database, a login page, a session store, password reset flows, and a dependency on an external identity provider — all to solve a problem that mTLS solves with zero moving parts.
Can my ISP see that my friends are connecting? Your ISP can see that an IP address connected to your server on port 443. They cannot see the client certificate, the search queries, or any HTTP content — that’s all inside the TLS tunnel. The mTLS handshake itself is encrypted.
What if a friend loses their device? Revoke their certificate and issue a new one. The old cert becomes useless immediately (once the CRL is updated or the CA is reissued).