add cloudflare infrastructure setup guide for tunnels, domains, and token rotation
This commit is contained in:
214
methodology/CLOUDFLARE-SETUP.md
Normal file
214
methodology/CLOUDFLARE-SETUP.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user