> ## Documentation Index
> Fetch the complete documentation index at: https://hyperspeed.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# TLS & HTTPS

> How HTTPS works in Hyperspeed — automatic Let's Encrypt via Caddy, configuration requirements, and alternatives using Traefik or nginx.

Hyperspeed uses **Caddy** as its public entrypoint on ports 80 and 443. Caddy handles TLS automatically when you provide a real hostname. No certificate management tooling is needed beyond setting two environment variables.

## How it works

The behavior of Caddy depends on the value of `CADDY_PUBLIC_HOST`:

| `CADDY_PUBLIC_HOST` | Result |
| - | - |
| `localhost` (default) | HTTP only on port 80. No TLS. Use for local development. |
| A real FQDN (e.g. `app.example.com`) | Caddy obtains a Let's Encrypt certificate automatically via HTTP-01 challenge and serves HTTPS on port 443. |

Caddy handles certificate renewal automatically once the initial certificate is issued.

## Local development (HTTP only)

With the default settings from `.env.example`, the stack runs on `http://localhost` with no TLS:

```bash theme={null}
CADDY_PUBLIC_HOST=localhost
```

This is intentional — Let's Encrypt does not issue certificates for `localhost`. The app is fully functional over HTTP for development purposes.

<Note>
  `CORS_ORIGIN=http://localhost` is the correct value when running locally with Docker + Caddy. Do not use `https://` for local development unless you configure a self-signed certificate separately.
</Note>

## Production HTTPS

<Steps>
  <Step title="Point DNS at your server">
    Create an **A record** for your hostname that resolves to your server's public IP address. Let's Encrypt uses an **HTTP-01 challenge** — it makes an HTTP request to your domain on port 80 to verify you control it. DNS must be propagated and port 80 must be reachable before you start the stack.

    Verify DNS is ready:

    ```bash theme={null}
    dig app.example.com
    ```
  </Step>

  <Step title="Set CADDY_PUBLIC_HOST and CADDY_EMAIL">
    In your `.env`, set the hostname (no scheme, no trailing slash) and a Let's Encrypt account email:

    ```bash theme={null}
    CADDY_PUBLIC_HOST=app.example.com
    CADDY_EMAIL=you@example.com
    ```

    Caddy registers a Let's Encrypt account using `CADDY_EMAIL` the first time it obtains a certificate. The email receives expiry warnings if automatic renewal ever fails.
  </Step>

  <Step title="Set CORS_ORIGIN to the HTTPS origin">
    Update `CORS_ORIGIN` to match the HTTPS origin users will open:

    ```bash theme={null}
    CORS_ORIGIN=https://app.example.com
    ```

    Without this, the browser will receive CORS errors on all API calls.
  </Step>

  <Step title="Start or restart the stack">
    ```bash theme={null}
    docker compose up --build -d
    ```

    Caddy requests the certificate on first start. Watch for errors:

    ```bash theme={null}
    docker compose logs -f caddy
    ```

    A successful certificate issue looks like:

    ```
    certificate obtained successfully
    ```
  </Step>
</Steps>

<Warning>
  If port 80 is blocked by a firewall or already in use by another process, the HTTP-01 challenge will fail and Caddy will not be able to obtain a certificate. Ensure ports 80 and 443 are both open and free before starting the stack.
</Warning>

## WebSocket and HTTPS

Hyperspeed uses WebSockets for realtime features (collaborative editing, live board updates, terminal). Behind HTTPS, WebSocket connections use `wss://` instead of `ws://`.

Caddy handles the WebSocket upgrade automatically — no extra configuration is needed. The Caddyfile routes `/api/*` to the API container with `reverse_proxy`, which passes `Upgrade` and `Connection` headers through by default.

<Tip>
  To verify WebSocket connectivity: open a space with realtime features and check DevTools Network for a `101 Switching Protocols` response on the `/api/v1/organizations/.../ws` connection.
</Tip>

## Using Traefik or nginx instead of Caddy

If your infrastructure already uses Traefik or nginx as an edge proxy, you can replace Caddy with your preferred reverse proxy. The routing requirements are:

* All traffic to `/api/*` and `/health` must proxy to the API container on port 8080, **with WebSocket upgrade headers forwarded**.
* All other traffic should serve the web container on port 80.
* The API and web UI must share a single origin in the browser — the SPA calls `/api/...` relative to the page origin.
* TLS must terminate at the edge proxy.

An example nginx location block for the API:

```nginx theme={null}
location /api/ {
    proxy_pass http://api:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
}
```

<Note>
  When using an alternative proxy, remove or disable the `caddy` service from `docker-compose.yml` and configure your proxy to handle TLS for your hostname. Set `CADDY_PUBLIC_HOST` and `CADDY_EMAIL` to empty or remove them — they are only read by the Caddy container.
</Note>

## Troubleshooting

| Symptom | Likely cause |
| - | - |
| Browser shows "certificate not trusted" | DNS was not propagated when the stack started; Let's Encrypt challenge failed and Caddy fell back to a self-signed cert. Fix DNS, then restart Caddy. |
| CORS errors in browser DevTools | `CORS_ORIGIN` does not exactly match the origin in the browser address bar. Scheme, host, and port must all match. |
| WebSocket connection fails behind HTTPS | The proxy is not forwarding `Upgrade` and `Connection` headers. Add the WebSocket upgrade headers to your proxy config. |
| Port 80 already in use | Another process is bound to port 80. Stop it or change the Compose port mapping (note: HTTP-01 challenge requires port 80 to be the public port). |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.