Files
hermes-brain/methodology/CLOUDFLARE-SETUP.md
2026-05-23 06:18:37 +00:00

226 lines
8.0 KiB
Markdown

---
type: methodology
title: Cloudflare Infrastructure Setup
created: 2026-05-11
tags:
- methodology
- cloudflare
- infrastructure
- networking
---
# Cloudflare Infrastructure Setup
## Architecture Overview
```
Browser ──► Cloudflare (orange cloud) ──► Tunnel ──► cloudflared ──► Traefik:8081
(tunnel entrypoint)
┌──────────┴──────────┐
secureweb tunnel
(direct internet) (via tunnel)
:443 :8081
LetsEncrypt plain HTTP
```
All external traffic goes through Cloudflare Tunnel. Traefik serves as the internal reverse proxy, routing by Host header. Traefik's LetsEncrypt certs exist for LAN/direct access but aren't used externally (Cloudflare edge terminates TLS for visitors).
## Active Stack
| Component | Location | Notes |
|---|---|---|
| Tunnel name | `home` | Created at Cloudflare Zero Trust → Networks → Tunnels |
| Tunnel ID | `c29295c5-946a-4ddf-bdfe-7eafcd74faa3` | Visible in dashboard |
| Token | `.env` `TUNNEL_TOKEN=...` | Used by cloudflared to authenticate |
| cloudflared | production-1 Docker container | Runs with `--restart unless-stopped` |
| Traefik | production-1 Docker container | Ports 80, 443, 8080, 8082 |
| Traefik entrypoints | `web` (:80→redirect), `secureweb` (:443, TLS), `tunnel` (:8081), `metrics` (:8082) | |
| Traefik config | `/docker/appdata/traefik/traefik.yaml` | |
| Compose file | `/docker/compose/docker-compose.yaml` | cloudflared service defined but runs standalone due to interpolation issues |
| Traefik labels | Per-service in docker-compose.yaml | Pattern: `entrypoints=tunnel`, `Host(`subdomain.gharbeia.net`)` |
## 1. Adding a Second Domain
Example: adding `example.com` alongside `gharbeia.net`.
### Step 1: Cloudflare DNS
1. Go to Cloudflare Dashboard → Add site → enter `example.com`
2. Cloudflare scans existing DNS records — verify and continue
3. Change nameservers at your registrar to Cloudflare's
4. Wait for DNS to propagate
### Step 2: Cloudflare Tunnel — add hostname
1. **Zero Trust****Networks****Connectors****Cloudflare Tunnels**
2. Select tunnel **home****Edit**
3. Click **Add a public hostname**
4. Set:
- **Subdomain:** `*`
- **Domain:** `example.com`
- **Service Type:** HTTP
- **URL:** `traefik:8081`
5. Save
Cloudflare DNS will automatically create a wildcard CNAME for `*.example.com` pointing to `c29295c5-946a-4ddf-bdfe-7eafcd74faa3.cfargotunnel.com`.
### Step 3: Traefik — update cert resolver
Cloudflare wildcard certs only cover `*.gharbeia.net`. For `*.example.com`, either:
**Option A: Add to Traefik's ACME config**
In `/docker/appdata/traefik/traefik.yaml`, in `certificatesResolvers.letsencrypt.acme`:
```yaml
dnsChallenge:
provider: cloudflare
resolvers:
- 1.1.1.1:53
- 1.0.0.1:53
propagation:
delayBeforeChecks: 120s # increase for wildcards
```
Then in `entryPoints.secureweb.http.tls.domains`:
```yaml
domains:
- main: gharbeia.net
sans:
- "*.gharbeia.net"
- main: example.com
sans:
- "*.example.com"
```
**Important:** Before ACME DNS challenge can succeed for wildcard domains proxied via Cloudflare, you MUST create a DNS-only placeholder record for `_acme-challenge` to break the wildcard:
Go to Cloudflare DNS → Add record:
- **Type:** A
- **Name:** `_acme-challenge`
- **Content:** `127.0.0.1`
- **Proxy:** DNS-only (gray cloud)
Without this, the proxied wildcard CNAME intercepts `_acme-challenge.*` queries and LetsEncrypt can't verify the TXT challenge records.
**Option B: Skip HTTPS cert for now**
If the second domain doesn't need Traefik's own cert (i.e., Cloudflare edge certs are sufficient), no Traefik changes needed. The tunnel handles everything.
### Step 4: Add Traefik labels
For each service on the new domain, add Traefik labels in docker-compose.yaml:
```yaml
labels:
- traefik.enable=true
- traefik.http.routers.servicename.rule=Host(`subdomain.example.com`)
- traefik.http.routers.servicename.entrypoints=tunnel
- traefik.http.services.servicename.loadbalancer.server.port=3000
```
Then restart the container to pick up labels (compose or docker run depending on env state).
---
## 2. Security Panic — Changing All Tokens and Settings
### Step 1: Cloudflare API Token
1. Go to Cloudflare Dashboard → **My Profile****API Tokens**
2. **Delete** the old token
3. **Create Token****Edit zone DNS** template:
- Permissions: Zone → DNS → Edit
- Zone Resources: Include → Specific zone → `gharbeia.net`
- TTL: No expiration (or set a reasonable one)
4. Copy the new token
### Step 2: Update .env on production-1
```bash
ssh production-1
# Edit the token
sed -i "s|^CLOUDFLARE_DNS_API_TOKEN=.*|CLOUDFLARE_DNS_API_TOKEN=<new-token>|" /docker/compose/.env
```
### Step 3: Restart Traefik
Traefik runs standalone (not under compose). Restart it with the new token:
```bash
docker rm -f traefik
docker run -d --name traefik --restart unless-stopped --network networking \
-p 80:80 -p 443:443 -p 8080:8080 -p 8082:8082 \
-e CF_DNS_API_TOKEN="<new-token>" \
-e TZ="America/New_York" \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
-v /docker/appdata/logs/traefik:/var/log \
-v /docker/appdata/traefik:/etc/traefik \
-v /docker/appdata/traefik/letsencrypt:/letsencrypt \
traefik:latest
```
Traefik will automatically renew its LetsEncrypt cert with the new token.
### Step 4: Cloudflare Tunnel Token
If you also rotate the tunnel token:
1. Go to **Zero Trust****Networks****Tunnels****home**
2. Click the three dots → **Recreate token**
3. Copy the new token
On production-1:
```bash
docker rm -f cloudflared
docker run -d --name cloudflared --restart unless-stopped --network networking \
-v /docker/appdata/cloudflared:/home/nonroot/.cloudflared \
cloudflare/cloudflared:latest tunnel --no-autoupdate run --token "<new-token>"
```
Also update the `.env` file:
```
TUNNEL_TOKEN=<new-token>
```
### Step 5: Verify everything works
```bash
# Check cloudflared is connected
docker logs cloudflared --tail 5 | grep "Registered tunnel connection"
# Check Traefik is running and has routes
docker logs traefik --tail 10 | grep "Register"
# Test end-to-end
curl -sI https://git.gharbeia.net/ | head -5
# Should return HTTP/2 200
```
### Step 6: Rotate other secrets (if needed)
Other credentials in `.env` that may need rotation:
- `AUTHENTIK_SECRET_KEY` — generate new: `openssl rand -base64 60 | tr -d '\n'`
- `POSTGRESQL_PASSWORD` — generate new
- `EMAIL_PASSWORD` — Fastmail app password
- `GITEA_TOKEN` — Gitea settings → Applications
- `OPENROUTER_API_KEY` — OpenRouter dashboard
## Troubleshooting
### Cert renewal fails with "Invalid format for Authorization header"
The Cloudflare API token in `CF_DNS_API_TOKEN` is wrong or expired. Generate a new one and restart Traefik.
### Cert renewal stuck on "Waiting for DNS record propagation"
Likely the proxied wildcard CNAME is intercepting `_acme-challenge` subdomain. Add a DNS-only A record for `_acme-challenge``127.0.0.1` in Cloudflare DNS to break the wildcard.
### Tunnel shows 502 Bad Gateway
- Check cloudflared logs: `docker logs cloudflared --tail 10`
- If `connection refused` → Traefik isn't listening on the expected port
- If `tls: unrecognized name` → SNI mismatch (revert to HTTP mode in tunnel config)
### Adding a new service
1. Add Traefik labels to the service in docker-compose.yaml
2. If the container was started without labels, recreate it with `docker run` including `-l` flags (compose may fail due to .env interpolation issues)
3. The tunnel wildcard already catches `*.gharbeia.net` — no tunnel changes needed