Caddy Web Server¶
Overview¶
Caddy is a powerful, enterprise-ready web server with automatic HTTPS. In Quill Medical, Caddy serves as our reverse proxy, handling incoming HTTP/HTTPS requests and routing them to the appropriate backend services (FastAPI, FHIR, OpenEHR, frontend).
Why Caddy?¶
Automatic HTTPS¶
- Zero Configuration: Automatically obtains and renews TLS certificates from Let's Encrypt
- Always Secure: HTTPS is the default, not an afterthought
- No Manual Renewal: Certificates are renewed automatically before expiration
- Modern Security: Uses secure defaults and modern TLS configurations
Simple Configuration¶
- Caddyfile: Human-readable configuration format
- No Complex Syntax: Much simpler than nginx or Apache configurations
- Sensible Defaults: Works out of the box with minimal configuration
- Hot Reloading: Configuration changes applied without downtime
Modern Architecture¶
- HTTP/2 & HTTP/3: Native support for modern protocols
- WebSockets: Built-in WebSocket proxying
- Reverse Proxy: Powerful reverse proxy capabilities
- Load Balancing: Built-in load balancing and health checks
Developer Experience¶
- Written in Go: Fast, single binary, no dependencies
- Easy Deployment: Single binary makes deployment trivial
- Excellent Documentation: Clear, comprehensive documentation
- Active Development: Regular updates and improvements
Our Implementation¶
Configuration Structure¶
Our Caddy configuration is in caddy/dev/Caddyfile:
:80 {
encode zstd gzip
# API requests → backend
@api path /api/*
handle @api {
reverse_proxy backend:8000
}
# EHRbase requests → ehrbase
@ehrbase path /ehrbase/*
handle @ehrbase {
reverse_proxy ehrbase:8080
}
# PWA manifest with correct MIME type
@webmanifest path /*.webmanifest
header @webmanifest Content-Type "application/manifest+json"
header @webmanifest Cache-Control "no-cache"
# SPA served at /
handle {
reverse_proxy frontend:5173
}
}
Architecture Role¶
Caddy sits at the edge of our application stack:
Client Browser
↓
Caddy (Port 80)
↓
├→ FastAPI Backend (Port 8000) - /api/*
├→ EHRbase Server (Port 8080) - /ehrbase/*
└→ Frontend SPA (Port 5173) - /* (Vite dev server)
Key Features in Use¶
Reverse Proxy¶
Caddy forwards requests to backend services based on path matching, as shown in the Caddyfile above. In production, the GCP Global HTTPS Load Balancer handles path-based routing (/api/* → backend, /* → frontend), so Caddy only serves static files and a health check endpoint.
Note: The config snippets below are illustrative Caddy examples showing capabilities we intend to implement. The actual dev and production Caddyfiles are simpler — see Our Implementation above for the real config. Updating these sections is tracked in the project to-do list.
Static File Serving¶
In production, Caddy serves the built React SPA from /srv/app:
root * /srv/app
file_server
try_files {path} /index.html
CORS Handling¶
CORS is handled by the FastAPI backend middleware, not by Caddy. The backend uses CORSMiddleware with configurable CORS_ORIGINS.
Request Logging¶
Currently not configured in either Caddyfile. To be added when needed.
Development vs Production¶
Development¶
In development (caddy/dev/Caddyfile), Caddy listens on port 80 and reverse-proxies to local Docker services (backend on 8000, frontend Vite dev server on 5173, EHRbase on 8080).
Production¶
In production (caddy/prod/Caddyfile), the GCP Global HTTPS Load Balancer handles TLS termination, path routing, and Cloud Armor WAF. Caddy runs inside the frontend container on port 80 and serves:
- Security headers (HSTS, CSP, X-Frame-Options, etc.)
- A
/healthzhealth check endpoint for Cloud Run probes - The built React SPA with
try_filesfallback toindex.html
Routing Strategy¶
Development (Caddy handles routing)¶
| Path pattern | Destination | Purpose |
|---|---|---|
/api/* |
Backend (port 8000) | FastAPI application |
/ehrbase/* |
EHRbase (port 8080) | OpenEHR server |
/* |
Frontend (port 5173) | Vite dev server |
Production (Load Balancer handles routing)¶
In production, the GCP Global HTTPS Load Balancer performs path-based routing before traffic reaches Caddy:
| Path pattern | Destination | Purpose |
|---|---|---|
/api/* |
Backend Cloud Run | FastAPI application |
/* |
Frontend Cloud Run | Caddy serves static SPA files |
FHIR and EHRbase are not publicly exposed — they run on a private VPC and are accessed only by the backend.
Security Features¶
In production, Caddy enforces security headers on all responses. See the cybersecurity documentation for the full header list.
Key headers: HSTS (2 years, preload), CSP, X-Frame-Options DENY, X-Content-Type-Options nosniff, Referrer-Policy, Permissions-Policy, and Server header removal.