diff --git a/docs/cloudflare-wildcard-ssl.md b/docs/cloudflare-wildcard-ssl.md new file mode 100644 index 0000000..13a6e33 --- /dev/null +++ b/docs/cloudflare-wildcard-ssl.md @@ -0,0 +1,261 @@ +# Wildcard SSL with Cloudflare + Coolify/Traefik + +This guide documents how to get automatic wildcard SSL certificates for `*.testbed.mk` using Cloudflare DNS and Traefik's DNS-01 challenge. + +## Why This Is Needed + +Traefik v3 removed the `onDemand` TLS option. `HostRegexp()` routers (used for wildcard subdomain matching) don't trigger automatic per-domain certificate provisioning. Only `Host()` rules do. + +The solution: use a **DNS-01 challenge** with a **wildcard certificate**. This provisions a single `*.testbed.mk` cert that covers all memorial subdomains automatically. + +DNS-01 requires a DNS provider with an API. Cloudflare is free and fully supported by Traefik. + +## Architecture + +``` +Internet + │ + ├── *.testbed.mk ──► Cloudflare DNS ──► VPS IP (62.84.176.135) + │ + └── :443 ──► Traefik (coolify-proxy) + ├── Wildcard cert: *.testbed.mk (via Cloudflare DNS-01) + ├── Host(testbed.mk) → app:3000 (Coolify default router) + └── HostRegexp(*.testbed.mk) → app:3000 (custom wildcard router) +``` + +## Step 1: Migrate DNS from Contabo to Cloudflare + +### 1.1 Create a Cloudflare Account +- Go to https://dash.cloudflare.com +- Sign up (free plan is sufficient) + +### 1.2 Add `testbed.mk` to Cloudflare +- Click **Add a Site** +- Enter `testbed.mk` +- Select the **Free** plan +- Cloudflare will scan existing DNS records + +### 1.3 Verify DNS Records +Ensure these records exist in Cloudflare: +| Type | Name | Content | Proxy | +|------|------|---------|-------| +| A | `@` | `62.84.176.135` | DNS only (gray cloud) | +| A | `*` | `62.84.176.135` | DNS only (gray cloud) | +| CNAME | `accounts` | `accounts.clerk.services` | DNS only | +| CNAME | `clerk` | `frontend-api.clerk.services` | DNS only | +| CNAME | `clk._domainkey` | `dkim1.t15ssxc9pnhh.clerk.services` | DNS only | +| CNAME | `clk2._domainkey` | `dkim2.t15ssxc9pnhh.clerk.services` | DNS only | +| CNAME | `clkmail` | `mail.t15ssxc9pnhh.clerk.services` | DNS only | + +**Important:** All records must be **DNS only** (gray cloud, not proxied). Proxied records will break Traefik's HTTP-01 challenge and cause SSL errors. + +### 1.4 Update Nameservers +Cloudflare will display two nameservers (e.g., `xxx.ns.cloudflare.com`). Update them at your current registrar (MKHOST): + +1. Log into MKHOST (or wherever `testbed.mk` is registered) +2. Find DNS/nameserver settings +3. Replace Contabo nameservers (`ns1.contabo.net`, `ns2.contabo.net`, `ns3.contabo.net`) with Cloudflare's nameservers +4. Save + +DNS propagation may take up to 24-48 hours (usually faster). Check with: +```bash +dig +short NS testbed.mk +# Should show xxx.ns.cloudflare.com +``` + +### 1.5 Create a Cloudflare API Token + +1. Go to Cloudflare → **My Profile** → **API Tokens** +2. Click **Create Token** +3. Use the **Edit zone DNS** template (or create custom): + - Permissions: `Zone` → `DNS` → `Edit` + - Zone Resources: `Include` → `Specific zone` → `testbed.mk` +4. Copy the token — you'll need it for Traefik + +## Step 2: Configure Traefik for DNS-01 Wildcard Cert + +### 2.1 Edit Traefik's docker-compose + +On the VPS: +```bash +nano /data/coolify/proxy/docker-compose.yml +``` + +Replace the `certificatesresolvers` section. Change from HTTP-01 to DNS-01: + +```yaml +# REMOVE these lines: + - '--certificatesresolvers.letsencrypt.acme.httpchallenge=true' + - '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http' + +# ADD these lines: + - '--certificatesresolvers.letsencrypt.acme.dnschallenge=true' + - '--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare' +``` + +Add Cloudflare credentials as environment variables in the `traefik` service: +```yaml + environment: + - CLOUDFLARE_DNS_API_TOKEN=YOUR_CLOUDFLARE_API_TOKEN_HERE +``` + +**Note:** Keep `--certificatesresolvers.letsencrypt.acme.storage=/traefik/acme.json` — this preserves existing certs. + +### 2.2 Add Wildcard Certificate Config + +Create a dynamic config file: +```bash +cat > /data/coolify/proxy/dynamic/wildcard-cert.yaml << 'EOF' +tls: + certificates: + - certResolver: letsencrypt + domains: + - main: "testbed.mk" + sans: + - "*.testbed.mk" +EOF +``` + +This tells Traefik to request a wildcard certificate covering `testbed.mk` and `*.testbed.mk` using the DNS-01 challenge via Cloudflare. + +### 2.3 Restart Traefik + +```bash +cd /data/coolify/proxy && docker compose up -d +``` + +### 2.4 Verify Cert Provisioning + +```bash +# Check Traefik logs for cert provisioning +docker logs coolify-proxy 2>&1 | grep -i "certificate\|acme\|cloudflare" | tail -20 + +# Test the cert +curl -vI https://goce.testbed.mk 2>&1 | grep -E "subject:|issuer:" +# Should show: subject: CN=*.testbed.mk (or similar, issued by Let's Encrypt) +``` + +### 2.5 Update docker-compose.yaml in the Project + +The existing Traefik labels in `docker-compose.yaml` can now use the wildcard cert: + +```yaml +services: + app: + labels: + - traefik.enable=true + - traefik.http.routers.spomeniqr-wildcard.rule=HostRegexp(`^[a-z0-9-]+\.testbed\.mk$$`) + - traefik.http.routers.spomeniqr-wildcard.entryPoints=http,https + - traefik.http.routers.spomeniqr-wildcard.service=spomeniqr-svc + - traefik.http.routers.spomeniqr-wildcard.tls=true + - traefik.http.routers.spomeniqr-wildcard.tls.certresolver=letsencrypt + - traefik.http.services.spomeniqr-svc.loadbalancer.server.port=3000 + - traefik.docker.network=coolify +``` + +The `tls.certresolver=letsencrypt` label will now use the DNS-01 challenge. The wildcard cert covers all subdomains automatically. + +## Step 3: Handle Coolify Updates + +Coolify may overwrite `/data/coolify/proxy/docker-compose.yml` on updates. To persist the DNS-01 configuration: + +### Option A: Re-apply after updates +Keep a backup of the modified file: +```bash +cp /data/coolify/proxy/docker-compose.yml /data/coolify/proxy/docker-compose.yml.dns01-backup +``` + +After a Coolify update, diff and re-apply the DNS-01 changes: +```bash +diff /data/coolify/proxy/docker-compose.yml /data/coolify/proxy/docker-compose.yml.dns01-backup +# Manually re-apply the dnschallenge lines +``` + +### Option B: Use Coolify's proxy customization +Check if Coolify v4.1+ supports custom proxy config injection: +- Coolify → Server → Proxy → Custom Configuration +- Add the DNS-01 challenge settings there + +### Option C: Script-based persistence +Create a script that auto-applies the changes: +```bash +cat > /data/coolify/proxy/apply-dns01.sh << 'SCRIPT' +#!/bin/bash +# Re-apply DNS-01 challenge config after Coolify updates +sed -i 's|--certificatesresolvers.letsencrypt.acme.httpchallenge=true|--certificatesresolvers.letsencrypt.acme.dnschallenge=true|' /data/coolify/proxy/docker-compose.yml +sed -i 's|--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http|--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare|' /data/coolify/proxy/docker-compose.yml + +# Add CLOUDFLARE_DNS_API_TOKEN env if not present +grep -q CLOUDFLARE_DNS_API_TOKEN /data/coolify/proxy/docker-compose.yml || \ + sed -i '/container_name: coolify-proxy/a\ environment:\n - CLOUDFLARE_DNS_API_TOKEN=YOUR_TOKEN' /data/coolify/proxy/docker-compose.yml + +cd /data/coolify/proxy && docker compose up -d +SCRIPT +chmod +x /data/coolify/proxy/apply-dns01.sh +``` + +## Step 4: Migrate Existing HTTP-01 Certs + +Existing domains (like `testbed.mk` itself) already have HTTP-01 certs in `acme.json`. The DNS-01 resolver will coexist — new certs use DNS-01, existing ones continue working until renewal. + +When existing certs renew, they'll switch to DNS-01 automatically (since that's the only resolver now). This is seamless. + +## Step 5: Update Clerk Domains + +After DNS migration to Cloudflare: +1. Go to Clerk Dashboard → **Domains** +2. Verify `testbed.mk` is still listed +3. Re-run DNS verification for the Clerk CNAME records (they should still work — Cloudflare proxies DNS-only CNAMEs) +4. If Clerk verification fails, ensure CNAME records in Cloudflare are **DNS only** (not proxied) + +## Troubleshooting + +### Cert not provisioning +```bash +# Check Traefik logs +docker logs coolify-proxy 2>&1 | grep -i error | tail -20 + +# Common issues: +# - CLOUDFLARE_DNS_API_TOKEN is wrong or expired +# - Token doesn't have Edit DNS permission for testbed.mk zone +# - Nameservers haven't fully propagated to Cloudflare yet +``` + +### Subdomain still shows TRAEFIK DEFAULT CERT +```bash +# Check if the wildcard cert is loaded +docker exec coolify-proxy cat /traefik/acme.json | python3 -c " +import sys, json +data = json.load(sys.stdin) +for resolver, certs in data.items(): + for cert in certs.get('Certificates', []): + domain = cert.get('domain', {}).get('main', 'unknown') + print(f'{resolver}: {domain}') +" + +# Force cert renewal by deleting the acme.json entry +# (CAREFUL - only do this if needed) +``` + +### DNS not resolving after migration +```bash +# Check nameservers +dig +short NS testbed.mk +# Should show Cloudflare nameservers + +# Check A record for wildcard +dig +short test.testbed.mk +# Should show 62.84.176.135 +``` + +## Summary + +| Aspect | Contabo DNS (current) | Cloudflare DNS (recommended) | +|--------|----------------------|-------------------------------| +| API access | No | Yes (free) | +| Wildcard SSL | Manual per-domain | Automatic wildcard cert | +| DNS-01 challenge | Not possible | Supported | +| SSL for memorials | Manual/complex | Fully automatic | +| Cost | Free | Free | +| Migration effort | — | ~30 min | +| Maintenance | Per-memorial cert management | Zero-touch after setup | \ No newline at end of file