# Create a public mTLS reverse proxy for testing mTLS
```mermaid
flowchart LR
subgraph Internet
Client["Client (with client cert)"]
end
subgraph Cloud_proxy
Proxy["Caddy / mTLS Proxy on VM"]
end
subgraph Tunnel_path
ngrok["ngrok public URL"]
LocalApp["Your Fleet server"]
end
Client -->|TLS + Client Cert| Proxy
Proxy -->|HTTPS + forwarded headers| ngrok
ngrok -->|tunnel traffic| LocalApp
```
## Assumptions & prerequisites
* You own a domain, e.g. `example.site`, or some domain.
* You will use a subdomain, e.g. `mtls.example.site` or whatever you choose.
* You have a **client CA certificate** (root or intermediate) that signs all valid client certificates (e.g. `client-ca.crt`).
* You have a backend (ngrok URL) that you want to forward to (i.e. `https://my-fleet-server.ngrok.io`).
* You have access to the DigitalOcean or another cloud provider.
* Replace `example.site` and `my-fleet-server.ngrok.io` in this guide with your own values.
---
## Step 1: Create a Droplet (VM) on DigitalOcean (or other cloud provider)
1. Log in to your DigitalOcean account.
2. In the dashboard, click **Create → Droplet**.
3. Choose an OS image (e.g. Ubuntu 24.04 LTS is a good default).
4. Choose a plan (for testing, the cheapest is fine, e.g. “Basic” with 1 vCPU / 1 GB RAM).
5. Choose a datacenter region (ideally near your users or your dev location).
6. Add SSH keys (recommended) so you can SSH in securely. If you don’t have an SSH key, you can generate one (`ssh-keygen`) and paste the public key.
7. Finalize settings (hostname, backups, etc.), then click “Create Droplet”.
After some moments, you’ll have a Droplet with a public IP address (say `203.0.113.45` as an example).
---
## Step 2: Set up DNS so `mtls.yourdomain` points to the Droplet
You need to make your subdomain resolve to that Droplet’s IP.
1. In DigitalOcean dashboard → **Networking** → **Domains** (or **DNS**)
2. In your DNS records page, add a record:
* Type: **A**
* Hostname: `mtls` (so the full hostname is `mtls.example.site`) (or whatever you choose)
* Value / Points to: your Droplet’s IP (e.g. `203.0.113.45`)
* TTL: the default (or lower if you like)
3. Wait for DNS propagation (may take some minutes). You can test with `dig mtls.example.site` or `ping mtls.example.site` to see it resolve to your Droplet IP.
Once that is active, when clients connect to `mtls.example.site`, they reach your Droplet.
---
## Step 3: SSH into the droplet, install Caddy
1. SSH in:
```bash
ssh root@203.0.113.45
```
2. Update packages:
```bash
apt update
apt upgrade -y
```
3. Install Caddy (the recommended way is via their official install script or package). On Ubuntu, a reliable method:
> **Note for Okta conditional access:** Caddy sends the certificate serial number in **decimal** format (as shown above), while AWS ALB sends it in **hexadecimal** format. If you're using this Caddy setup with Okta conditional access, you must configure Fleet to parse the serial number in decimal format by setting [`conditional_access.cert_serial_format`](https://fleetdm.com/docs/configuration/fleet-server-configuration#conditional-access) to `decimal`.