---
title: "FitaLabs Gateway — Caddy Configuration"
description: "Documentation: FitaLabs Gateway — Caddy Configuration"
type: reference
tags: [infra, caddy, reverse-proxy, tls, routing]
timestamp: 2026-07-06
---

# FitaLabs Gateway — Caddy Configuration

Reverse proxy with automatic TLS (Let's Encrypt), serving 8 subdomains.

## Subdomains

| Domain | Function | Backend |
|---------|--------|---------|
| `unifita.app` | Landing page | `/var/www/index.html` |
| `claude.unifita.app` | 1P SPA bundle | `/var/www/claude/` + CDN fallback |
| `api.unifita.app` | API Gateway | `gateway:4000` |
| `gateway.unifita.app` | Admin UI | `gateway:4000/ui` (redirect) |
| `docs.unifita.app` | Docusaurus | `/var/www/docs/` |
| `get.unifita.app` | DMG downloads | `/var/www/get/` |
| `search.unifita.app` | SearXNG | `searxng:8080` |
| `status.unifita.app` | Uptime Kuma | `uptime-kuma:3001` |

## claude.unifita.app — Bundle 1P

```
claude.unifita.app {
    root * /var/www/claude
    encode gzip

    # API → gateway (with auth injection)
    @api {
        path /api/* /v1/* /mcp /bootstrap /account_profile /edge-api/* /accounts/* /auth/*
    }
    reverse_proxy @api gateway:4000 {
        header_up Authorization "Bearer sk-gateway-local"
        header_up anthropic-version "2023-06-01"
    }

    # SPA fallback: client-side routes → index.html
    # Only for navigation (Accept: text/html), not for JS imports
    @spa {
        not file
        not path /api/* /v1/* /mcp /bootstrap /account_profile /edge-api/* /auth/* /i18n/* /claude_code/*
        header Accept *text/html*
    }
    rewrite @spa /index.html

    file_server

    # CDN fallback: missing chunks → Anthropic CDN
    handle_errors 404 {
        rewrite * /claude-ai/v2/assets/v1{uri}
        reverse_proxy https://assets-proxy.anthropic.com
    }
}
```

### Why auth in Caddy and not Service Worker?
- SW has caching issues (old versions persist)
- SW doesn't work in private/incognito until reload
- Caddy `header_up` is server-side, reliable, first request already authenticated
- Simpler debugging: `docker exec caddy wget` vs DevTools SW

### Why SPA fallback only with Accept: text/html?
- Browser navigation sends `Accept: text/html` → serves index.html ✅
- Dynamic import (`import("/logout")`) does NOT send `Accept: text/html` → 404 ✅
- Without this distinction, JS imports received HTML → `'text/html' is not a valid JavaScript MIME type`

### CDN fallback with handle_errors 404
- Local files: served by `file_server`
- Missing chunks: Caddy returns 404 → `handle_errors` catches → proxy to CDN
- Browser receives chunk as if it were local
- Frequently used chunks end up in browser cache

## gateway.unifita.app — Admin UI

```
gateway.unifita.app {
    # Swagger/OpenAPI docs
    handle_path /swagger* {
        rewrite * /docs{path}
        reverse_proxy gateway:4000
    }
    # Root → admin UI
    redir / /ui
    reverse_proxy gateway:4000
}
```

## api.unifita.app — API Gateway

```
api.unifita.app {
    reverse_proxy gateway:4000
}
```

No auth injection — the client (Claude Desktop/Code) sends its own `x-api-key` or `Authorization`.

## TLS

All subdomains use Let's Encrypt via `tls me@aleffita.dev`.
Caddy renews automatically.

## References
- [[fitalabs-gateway-1p-jornada]] — overview
- [[fitalabs-gateway-bundle-1p]] — bundle details
