Skip to content

Lab HTTPS — Gateway Fabric + Let's Encrypt

Expose MeshFlows lab hostnames over HTTPS using nginx Gateway Fabric (Kubernetes Gateway API) and cert-manager with Let's Encrypt HTTP-01 challenges.

Internal reference: internal/lab-https-setup.md (operator notes).

Hostnames

Subdomain Service
lab-gateway.meshflows.org Gateway API
lab-dashboard.meshflows.org Dashboard
lab-wiki.meshflows.org Wiki
lab-identity.meshflows.org Identity (OAuth, login, API keys)

Other environments follow the same pattern (dev-*, tst-*, …).

Architecture

Internet (*.meshflows.org)
   │  :80 / :443
   ▼
Fritzbox → node:30080 (HTTP) / :30443 (HTTPS)
   ▼
edge-router (meshflows-edge) — Host/SNI routing
   ├─ HTTP :80  → 301 https://$host$request_uri (ACME /.well-known proxied to env :80)
   ├─ HTTPS :443 SNI lab-*  → meshflows-nginx.meshflows-lab:443
   ├─ HTTPS :443 SNI dev-*  → meshflows-nginx.meshflows-dev:443
   └─ code / webmail        → meshflows-nginx.meshflows-lab:443
   ▼
Per-env nginx Gateway Fabric (internal HTTPS NodePorts 31101 / 31201 / …)
   └─ HTTPRoutes → dashboard, gateway, identity, wiki services

Unknown hostnames return HTTP 404 from nginx Gateway Fabric (or from edge-router when no Host map matches).

Public clients should use either http:// or https:// hostnames; plain HTTP is redirected to HTTPS at the edge. HTTPS→HTTP is not used (TLS is the canonical scheme).

Relevant manifests:

  • engine/deploy/overlays/linux-cluster/infra/ — Gateway + HTTPRoute hostname patches
  • wiki/deploy/k8s/ — Wiki Gateway + HTTPRoute
  • .github/workflows/infra.yml — cluster infrastructure install

Step 1 — DNS

Create A records at your registrar pointing to your public IP:

lab-gateway.meshflows.org   A  <public IP>
lab-dashboard.meshflows.org A  <public IP>
lab-wiki.meshflows.org      A  <public IP>
lab-identity.meshflows.org  A  <public IP>

Step 2 — Fritzbox Port Forwards

Protocol External Internal (server)
TCP 80 30080
TCP 443 30443

Port 80 is required for ACME HTTP-01. Public HTTP traffic is 301-redirected to HTTPS by edge-router (engine/scripts/reconcile-edge-router.sh); ACME challenge paths are proxied to the env gateway without redirect. Each env Gateway also has an http-to-https HTTPRoute as a fallback.

Step 3 — Roll Out Infrastructure

Forgejo Actions → Deploy Cluster Infrastructure (infra.yml):

components : all
acme_email : you@email.example
environment: lab

This installs (in order):

  1. Gateway API CRDs (v1.5.0)
  2. nginx Gateway Fabric (NodePort 30080/30443)
  3. GatewayClass nginx
  4. cert-manager (enableGatewayAPI=true)
  5. cert-manager solver Gateway
  6. Let's Encrypt ClusterIssuers (letsencrypt-prod + letsencrypt-staging)

See Gateway API Migration for migration from ingress-nginx.

Step 4 — Deploy Application Manifests

Via CI (recommended):

deploy.yml → environment=lab, release_id=<from build>

Or manually:

kubectl apply -k engine/deploy/overlays/linux-cluster/
kubectl apply -k wiki/deploy/k8s/

cert-manager requests certificates via Gateway TLS listeners (HTTP-01).

TLS issuer defaults in deploy: all environments (incl. dev)letsencrypt-prod. Use tls_issuer=letsencrypt-staging only as an explicit override. Deploy preserves an existing production meshflows-tls secret; cert-manager renews automatically before expiry. If an environment still has a staging certificate (e.g. after switching defaults), deploy removes it once and re-issues via letsencrypt-prod (see engine/scripts/reconcile-tls-certificate.sh).

Verification

kubectl get gateway -A
kubectl get httproute -A
kubectl get certificate -n meshflows-lab
kubectl describe certificate meshflows-tls -n meshflows-lab

curl -H "Host: lab-gateway.meshflows.org" http://<server-LAN-IP>:30080/
# → 301 redirect to https://

curl -k https://lab-gateway.meshflows.org/

Known Pitfalls

Issue Fix
NodePorts not 30080/30443 Re-run infra.yml
cert-manager misses listeners Verify cert-manager.io/cluster-issuer on Gateway and enableGatewayAPI=true
HTTP-01 fails Check Fritzbox port 80 → 30080; ISP blocking
DNS not propagated Wait; cert-manager retries
Let's Encrypt rate limit Prefer leaving existing meshflows-tls in place; cert-manager renews before expiry. Staging issuer only for deliberate tests (max ~5 prod certs/domain/week).