Skip to content

DOMAIN:NETWORKING:DNS_MANAGEMENT

OWNER: stef UPDATED: 2026-03-24 SCOPE: all DNS operations for GE and client domains AGENTS: stef (primary), karel (CDN CNAME coordination), arjan (server IPs), leon (deploy chain) PROVIDER: TransIP (Netherlands-based registrar and DNS hosting)


DNS:OVERVIEW

PURPOSE: manage DNS records for all GE and client domains PROVIDER: TransIP (aequitas/transip Terraform provider) METHOD: Terraform only — NEVER manual TransIP console changes CREDENTIALS: Vault path admin-ui/api-keys/transip (piotr manages) PRIMARY_DOMAIN: growing-europe.com HOSTING_DOMAIN: hosting.growing-europe.com (client subdomains)

RULE: all DNS changes via Terraform (IaC, auditable, version-controlled) RULE: never register a new domain without human (Dirk-Jan) approval RULE: always verify DNS propagation after changes


DNS:RECORD_TYPES

A RECORD

PURPOSE: maps domain to IPv4 address USE_WHEN: pointing domain to a server IP

resource "transip_dns_entry" "app_a" {
  domain  = var.domain
  name    = var.subdomain  # e.g., "app" for app.example.com, "@" for apex
  type    = "A"
  content = var.server_ip  # from arjan's provisioning output
  expire  = 300            # 5 min TTL — quick failover
}

VERIFY:

TOOL: dig
RUN: dig {domain} @ns1.transip.net A +short
EXPECT: {server_ip}

AAAA RECORD

PURPOSE: maps domain to IPv6 address USE_WHEN: server has IPv6 (UpCloud servers typically have both IPv4 and IPv6) TTL: 300s (same as A record)

CNAME RECORD

PURPOSE: maps domain to another domain (alias) USE_WHEN: CDN domains (pointing to BunnyCDN pull zone)

resource "transip_dns_entry" "cdn_cname" {
  domain  = var.domain
  name    = "cdn"          # cdn.example.com
  type    = "CNAME"
  content = "${var.pullzone}.b-cdn.net."  # Karel provides pull zone name
  expire  = 300
}

FLOW: Karel creates pull zone -> tells Stef pull zone hostname -> Stef creates CNAME

RULE: CNAME cannot coexist with other record types on the same name RULE: apex domain (@) cannot use CNAME — use A record or ALIAS (if supported)

MX RECORD

PURPOSE: mail routing USE_WHEN: client domain needs email

resource "transip_dns_entry" "mx" {
  domain  = var.domain
  name    = "@"
  type    = "MX"
  content = "10 mail.example.com."
  expire  = 86400  # 24h TTL — mail records are stable
}

TTL: 86400s (24 hours) — MX records rarely change, high TTL reduces DNS load

TXT RECORD

PURPOSE: domain verification, email authentication (SPF, DKIM, DMARC) USE_WHEN: email setup, domain ownership verification, Let's Encrypt DNS-01 challenge

# SPF record
resource "transip_dns_entry" "spf" {
  domain  = var.domain
  name    = "@"
  type    = "TXT"
  content = "v=spf1 include:_spf.google.com ~all"
  expire  = 86400
}

# DKIM record
resource "transip_dns_entry" "dkim" {
  domain  = var.domain
  name    = "google._domainkey"
  type    = "TXT"
  content = "v=DKIM1; k=rsa; p={public_key}"
  expire  = 86400
}

# DMARC record
resource "transip_dns_entry" "dmarc" {
  domain  = var.domain
  name    = "_dmarc"
  type    = "TXT"
  content = "v=DMARC1; p=quarantine; rua=mailto:dmarc@${var.domain}"
  expire  = 86400
}

SRV RECORD

PURPOSE: service discovery (used by some protocols) USE_WHEN: rarely — only for services that use SRV discovery (e.g., SIP, XMPP) TTL: 3600s

CAA RECORD

PURPOSE: Certificate Authority Authorization — controls which CAs can issue certs for domain USE_WHEN: always for production domains

resource "transip_dns_entry" "caa" {
  domain  = var.domain
  name    = "@"
  type    = "CAA"
  content = "0 issue \"letsencrypt.org\""
  expire  = 86400
}

RULE: CAA records should restrict issuance to Let's Encrypt only (GE standard CA) EXCEPTION: client-provided certs from other CAs — add their CA to CAA


DNS:TTL_STRATEGY

Record Type TTL Rationale
A / AAAA 300s (5 min) Quick failover during incidents or migrations
CNAME 300s (5 min) CDN changes need fast propagation
MX 86400s (24h) Mail servers rarely change
TXT (SPF/DKIM/DMARC) 86400s (24h) Email auth records are stable
TXT (verification) 300s (5 min) Temporary records, remove after verification
CAA 86400s (24h) CA policy rarely changes
NS 86400s (24h) Nameserver delegation is stable

PRE_MIGRATION_TTL: - Before planned migration, reduce A/CNAME TTL to 60s (24 hours in advance) - After migration complete and verified, restore to standard TTL - REASON: low TTL ensures clients resolve to new IP quickly during cutover

TOOL: terraform
TIMELINE:
  T-24h: reduce TTL to 60s via Terraform apply
  T-0:   perform migration, update A record to new IP
  T+1h:  verify all traffic on new IP
  T+24h: restore TTL to 300s

DNS:PROPAGATION

CHECKING_PROPAGATION

TOOL: dig
RUN: dig {domain} @ns1.transip.net    # authoritative nameserver
RUN: dig {domain} @8.8.8.8            # Google Public DNS
RUN: dig {domain} @1.1.1.1            # Cloudflare DNS
RUN: dig {domain} @9.9.9.9            # Quad9 DNS

IF_STALE_CACHE: - TTL determines how long resolvers cache records - After updating, wait at least 1x TTL for most resolvers to refresh - Some ISP resolvers ignore TTL (violation of RFC) — nothing GE can do about that - For critical cutover, monitor propagation across multiple resolvers

PROPAGATION_TIMELINE

TTL ~50% propagated ~95% propagated
60s ~1 minute ~5 minutes
300s ~5 minutes ~15 minutes
3600s ~1 hour ~4 hours
86400s ~12 hours ~48 hours

DNS:MULTI_DOMAIN_MANAGEMENT

DOMAIN_ORGANIZATION

growing-europe.com                    # GE corporate domain
  office.growing-europe.com           # admin-ui
  wiki.growing-europe.com             # wiki brain
  hosting.growing-europe.com          # client hosting base domain
    {client}.hosting.growing-europe.com  # per-client subdomain

{client-domain}.com                   # client's own domain (managed by stef)
  www.{client-domain}.com             # client website
  app.{client-domain}.com             # client application
  api.{client-domain}.com             # client API
  cdn.{client-domain}.com             # CDN (CNAME to BunnyCDN)

PER_CLIENT_DNS_SETUP

ON_NEW_CLIENT_DOMAIN:
1. Client provides domain name
2. Stef verifies domain ownership (TXT record challenge)
3. Client points nameservers to TransIP (if GE manages DNS)
   OR client creates delegation records (if client keeps DNS control)
4. Stef creates standard record set:
   → A record: server IP (from arjan/rutger)
   → CNAME: cdn.{domain} -> {pullzone}.b-cdn.net (from karel)
   → TXT: SPF, DKIM, DMARC (if email needed)
   → CAA: restrict to letsencrypt.org
5. Stef triggers cert-manager for TLS certificate (DNS-01 challenge)
6. Verify all records resolving correctly

DNS:LET_S_ENCRYPT_VALIDATION

DNS_01_CHALLENGE

PURPOSE: prove domain ownership for TLS certificate issuance METHOD: cert-manager creates _acme-challenge TXT record via TransIP API ADVANTAGE: works for wildcard certificates, does not require inbound HTTP

FLOW:
1. cert-manager requests certificate from Let's Encrypt
2. Let's Encrypt returns challenge token
3. cert-manager webhook creates TXT record:
   _acme-challenge.{domain} TXT "{token}"
4. Let's Encrypt verifies TXT record exists
5. Certificate issued
6. cert-manager removes challenge TXT record

TRANSIP_WEBHOOK: cert-manager uses TransIP webhook for DNS-01 challenge automation CREDENTIAL: TransIP API key in Vault (piotr manages)

IF_CHALLENGE_FAILS: 1. CHECK: is TransIP API key valid? (Vault path admin-ui/api-keys/transip) 2. CHECK: does the domain use TransIP nameservers? 3. CHECK: is there a CAA record blocking Let's Encrypt? 4. CHECK: cert-manager logs for specific error

TOOL: kubectl
RUN: kubectl logs -l app=cert-manager -n cert-manager --tail=50
RUN: kubectl get challenges -A
RUN: kubectl describe challenge {name} -n {namespace}

DNS:DNSSEC

STATUS

CURRENT: TransIP supports DNSSEC — can be enabled per domain GE_POLICY: enable DNSSEC for all production client domains

WHAT_DNSSEC_DOES

  • Signs DNS responses with cryptographic keys
  • Prevents DNS spoofing and cache poisoning
  • Chain of trust from root DNS servers to domain
  • Does NOT encrypt queries (that is DNS-over-HTTPS/TLS)

ENABLING_DNSSEC

METHOD: TransIP manages DNSSEC keys (KSK + ZSK)
ENABLE: via TransIP API or Terraform
VERIFY:
TOOL: dig
RUN: dig {domain} +dnssec
EXPECT: RRSIG records in response, AD (Authenticated Data) flag set

PITFALL: enabling DNSSEC with incorrect key configuration can make domain unreachable FIX: always test with dig +dnssec before and after enabling RULE: enable DNSSEC during initial setup, not mid-production


DNS:ORPHAN_RECORD_CHECK

WEEKLY_AUDIT (Stef, Monday 7am)

PURPOSE: detect DNS records pointing to decommissioned servers (dangling DNS)

CHECK:
1. List all A records from TransIP
2. For each A record, verify server is responsive:
   TOOL: curl
   RUN: curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 https://{domain}
3. For each CNAME, verify target exists:
   TOOL: dig
   RUN: dig {cname_target} +short
4. FLAG: any record pointing to unreachable IP or non-existent CNAME
5. ALERT: orphan records are a security risk (subdomain takeover)

SECURITY_RISK: dangling CNAME to deprovisioned CDN/cloud service = subdomain takeover vulnerability VICTORIA: notified of any dangling records for security assessment


DNS:DEPLOY_CHAIN_POSITION

STEF'S ROLE IN DEPLOY CHAIN

... → Rutger (prod k8s applied) →
  STEF:
  1. Create/update DNS records for new deployment
  2. Verify TLS certificate valid and terminating
  3. Apply/verify production firewall rules
  4. Confirm network ready to Leon
→ Karel (CDN purge + edge config) →
→ Leon (cutover confirmed)

DNS:AGENT_WORKFLOW

FOR_STEF

ON_DNS_TASK: 1. READ this page for DNS standards 2. DETERMINE record type needed 3. WRITE Terraform config (never manual console) 4. RUN terraform plan — verify TTL, record value 5. RUN terraform apply 6. VERIFY: dig {domain} @ns1.transip.net 7. VERIFY: dig {domain} @8.8.8.8 (propagation) 8. HAND OFF to leon (deploy chain) or confirmation to requesting agent

ON_WEEKLY_AUDIT (Monday 7am):

  1. List all DNS records
  2. Check for orphan records
  3. Report findings
  4. Remove orphans (with approval)

DNS:ANTI_PATTERNS

BEFORE_EVERY_DNS_ACTION: 1. Am I making manual TransIP console changes? (NEVER — Terraform only) 2. Am I registering a new domain without human approval? (NEVER) 3. Am I creating wildcard DNS? (check with victoria first) 4. Am I setting TTL too high for records that may need quick changes? (A/CNAME: 300s max) 5. Am I setting TTL too low for stable records? (MX/TXT: 86400s appropriate) 6. Am I leaving dangling CNAME records? (security risk — subdomain takeover) 7. Am I skipping propagation verification? (NEVER — always verify with dig) 8. Am I touching CDN configuration? (Karel's domain, Stef only manages origin DNS)


DNS:CROSS_REFERENCES

TLS_CERTIFICATES: domains/networking/tls-certificates.md — DNS-01 challenge for cert issuance CDN_EDGE: domains/networking/cdn-edge.md — CNAME records for BunnyCDN pull zones NETWORK_SECURITY: domains/networking/network-security.md — firewall and network configuration TERRAFORM_PATTERNS: domains/infrastructure/terraform-patterns.md — TransIP provider config DEPLOYMENT_STRATEGIES: domains/infrastructure/deployment-strategies.md — DNS changes during deploy