Skip to content

DNS and certificates

Direct TLS does not require Advanced Certificate Manager. Client Traefik obtains and renews Let's Encrypt certificates; Cloudflare provides authoritative DNS. Configure an ACME contact for the installation.

Routing

Purpose Name DNS and TLS
Control host headscale.example.com A → 192.0.2.10, DNS-only, direct TLS
Client production <company>.oduflow.sh A → client VM, DNS-only, Traefik HTTPS
Client Oduflow oduflow.<company>.oduflow.sh Client wildcard A, Traefik HTTPS
Client Paseo paseo.<company>.oduflow.sh Client wildcard A, Traefik HTTPS
Branch/service route <name>.<company>.oduflow.sh Client wildcard A, Traefik HTTPS

Create <company>.oduflow.sh and *.<company>.oduflow.sh with proxied=false. Publish AAAA only when IPv6 actually works. TCP 80 supports HTTP-01 issuance and HTTPS redirects; TCP 443 serves Traefik. Administrative Salt ports remain inside the VPN. Reserve the Oduflow/Paseo names against branch use.

DNS-only is deliberate: browsers see Traefik's certificate directly. With Cloudflare proxy enabled, Cloudflare presents its own edge certificate. Universal SSL on a full zone normally covers the zone apex and one subdomain level, not arbitrary deeper client hosts. An origin Let's Encrypt certificate does not extend Cloudflare edge coverage. Universal SSL limits.

Cloudflare Tunnel is not the default client publication path: it introduces a separate browser-to-Cloudflare TLS connection. Total TLS does not issue certificates for Tunnel hostnames. Total TLS, Tunnel routing.

Current Let's Encrypt configuration

The implemented client uses HTTP-01 for concrete hostnames, including the production apex and both panels. Wildcard DNS routing does not mean the TLS certificate must itself be a wildcard. No broad Cloudflare API token is delivered to the client. Oduflow 1.75 manages Traefik and its persistent ACME volume.

A conceptual static resolver configuration is:

entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"
certificatesResolvers:
  letsencrypt:
    acme:
      email: admin@example.com
      storage: /var/lib/traefik/acme.json
      httpChallenge:
        entryPoint: web

The actual paths and container routing are generated by client Oduflow. ACME state must persist across restarts, remain private (0600) and never be copied into a golden image with client certificates or keys. Readiness verifies certificate trust/hostname and application authentication, not merely a running Traefik process. See production publication for the temporary host ingress guard and post-hardening ACME retry. Traefik ACME.

Optional future wildcard certificates

A certificate for <company>.oduflow.sh and *.<company>.oduflow.sh would require DNS-01. Wildcards cover one additional label; *.*.oduflow.sh is invalid. Traefik/Lego can create _acme-challenge TXT records using its cloudflare provider. Such a design is not enabled by the current client role.

A dedicated DNS-01 token would need Zone / Zone / Read and Zone / DNS / Edit scoped to the intended zone, supplied through a private file such as CF_DNS_API_TOKEN_FILE. A separate CF_ZONE_API_TOKEN can supply zone lookup. Zone scope does not isolate one client's subdomain, so distributing the control plane's zone-wide token is inappropriate. A delegated challenge zone/CNAME or central certificate issuer would need a separate design. Lego Cloudflare provider, Traefik DNS challenge.

Odoo contract and migration

New instances use oduflow.sh, naming_version=nested_v2 and ingress_mode=direct_tls. Production uses <slug>.<domain>, Oduflow uses oduflow.<slug>.<domain> and Paseo uses paseo.<slug>.<domain>. The client owns *.<slug>.<domain>; its Oduflow chooses names within that namespace. The control plane does not reserve individual devN/svcN slots.

Preparation freezes naming version, ingress, ACME contact, client domain and panel addresses. Pillar remains schema=1; nested_v2 environment/service lists are empty and wildcard identifies the owned subtree. Numeric quotas do not assign DNS names. Direct TLS needs neither a tunnel token nor cloudflared.

Migration 19.0.1.2.0 assigns legacy rows flat_v1 and tunnel before model initialization. Existing <slug>-devN.<domain> / <slug>-paseo.<domain> names and base_domain remain unchanged. Prepared snapshots receive their historical DNS set; new defaults must not rewrite old addresses.

Wildcard namespace reservations cover the whole client subtree, including its apex, even though a TLS wildcard covers only one label. Conflict checks include exact names and nested subtrees across companies. Every Prepare transaction updates one private oduflow.dns.namespace.guard row before checking conflicts. Under REPEATABLE READ, PostgreSQL rejects a stale concurrent snapshot and Odoo retries. An advisory lock alone would not make that snapshot fresh. Reservations survive infrastructure deletion; an infrastructure hostname cannot be reused as a client slug.

Migration 19.0.3.1.0 removes separate fourth-level reservations only when the same instance owns the apex and wildcard. Legacy flat_v1 records remain. The client form no longer exposes a separate Reserved Hostnames page.