DNS Configuration | Docs | TitaniumGuard

DNS Configuration

DNS Configuration Reference

Start without a config file

When launched without an explicit --config, DNS loads config.json from the current directory if it exists. Otherwise it starts with a local profile: 127.0.0.1:5353 for DNS, 127.0.0.1:8081 for health, recursive resolution restricted to loopback clients, built-in allow-all query policy, memory cache, and audit logging disabled. Query it with dig -p 5353 @127.0.0.1 example.com. A missing explicitly supplied config path or an invalid config causes startup to fail. Partial explicit configs continue to use the JSON defaults documented below.

{
  // REQUIRED: no
  // TYPE: string (host:port)
  // DEFAULT: "0.0.0.0:8080"
  // DNS UDP+TCP listener bind address.
  "listen_addr": "0.0.0.0:8080",

  // REQUIRED: no
  // TYPE: string | DEFAULT: null
  // Optional policy JSON path for DNS policy engine.
  "policy_file_path": "/etc/titaniumguard/dns-policy.json",

  // REQUIRED: no
  // TYPE: RuleEngineConfig
  // DEFAULT: { "max_trace_facts": 64, "enable_explain_logs": true }
  "rule_engine": {
    // REQUIRED: no | TYPE: u32 | DEFAULT: 64
    "max_trace_facts": 64,

    // REQUIRED: no | TYPE: bool | DEFAULT: true
    "enable_explain_logs": true
  },

  // REQUIRED: no
  // TYPE: string[] (IP addresses)
  // DEFAULT: []
  // If empty, built-in root hints are used.
  "resolvers": ["1.1.1.1", "8.8.8.8"],

  // REQUIRED: no
  // TYPE: QueryLimitsConfig
  // Bounds the complete query lifecycle across every transport.
  "query_limits": {
    // REQUIRED: no | TYPE: u64 | DEFAULT: 10000
    "timeout_ms": 10000,
    // REQUIRED: no | TYPE: u32 | DEFAULT: 1024
    "max_concurrent": 1024
  },

  // REQUIRED: no
  // TYPE: RecursionConfig
  "recursion": {
    "enabled": true,
    "allowed_client_cidrs": ["10.0.0.0/8"],
    // REQUIRED: no | TYPE: u64 | DEFAULT: 2000
    "upstream_timeout_ms": 2000,
    // REQUIRED: no | TYPE: u32 | DEFAULT: 24
    "max_referral_depth": 24,
    // REQUIRED: no | TYPE: u32 | DEFAULT: 64
    "max_upstream_exchanges": 64,
    // REQUIRED: no | TYPE: u32 | DEFAULT: 2
    "max_parallel_upstreams": 2,
    // REQUIRED: no | TYPE: u64 | DEFAULT: 50
    "hedge_delay_ms": 50
  },

  // REQUIRED: no
  // TYPE: ZoneConfig[]
  // DEFAULT: []
  "zones": [
    {
      // REQUIRED: yes
      // TYPE: string
      // Zone apex, usually FQDN with trailing dot.
      "name": "corp.internal.",

      // REQUIRED: yes
      // TYPE: ZoneSoaConfig
      "soa": {
        // REQUIRED: yes | TYPE: string | ALIAS: MNAME
        "mname": "ns1.corp.internal.",

        // REQUIRED: yes | TYPE: string | ALIAS: RNAME
        "rname": "dns-admin.corp.internal.",

        // REQUIRED: yes | TYPE: u32 | ALIAS: SERIAL
        "serial": 2026022201,

        // REQUIRED: yes | TYPE: u32 | ALIAS: REFRESH
        "refresh": 3600,

        // REQUIRED: yes | TYPE: u32 | ALIAS: RETRY
        "retry": 600,

        // REQUIRED: yes | TYPE: u32 | ALIAS: EXPIRE
        "expire": 1209600,

        // REQUIRED: yes | TYPE: u32 | ALIAS: MINIMUM
        "minimum": 300,

        // REQUIRED: no | TYPE: u32 | DEFAULT: 3600
        "ttl": 3600
      },

      // REQUIRED: no
      // TYPE: { owner_name: { record_type: ZoneRecordSetConfig } }
      // DEFAULT: {}
      "records": {
        "@": {
          // REQUIRED: no
          // TYPE: ZoneRecordSetConfig
          "A": {
            // REQUIRED: no | TYPE: u32 | DEFAULT: 300
            "ttl": 300,

            // REQUIRED: yes | TYPE: string[]
            "values": ["10.10.0.53"]
          },
          "NS": {
            "ttl": 3600,
            "values": ["ns1.corp.internal."]
          }
        }
      }
    }
  ],

  // REQUIRED: no
  // TYPE: CachingConfig
  // DEFAULT: { "type": "memory", "max_entries": 100000 }
  "caching": {
    // REQUIRED: yes
    // TYPE: enum
    // ALLOWED: "memory", "redis"
    "type": "memory",

    // REQUIRED: no (memory only) | TYPE: u64 | DEFAULT: 100000
    "max_entries": 100000,

    // REQUIRED: yes (redis only) | TYPE: string
    // "url": "redis://127.0.0.1/",

    // REQUIRED: no (redis only) | TYPE: string | DEFAULT: "dns:cache:"
    // "key_prefix": "dns:cache:"

    // REQUIRED: no (redis only) | TYPE: bool | DEFAULT: false
    // "required": false,

    // REQUIRED: no (redis only) | TYPE: u64 | DEFAULT: 250
    // "timeout_ms": 250,

    // REQUIRED: no (redis only) | TYPE: u32 | DEFAULT: 3
    // "failure_threshold": 3
  },

  // REQUIRED: no
  // TYPE: TransportConfig
  // DEFAULT: {}
  "transports": {
    // REQUIRED: no | TYPE: TlsTransportConfig | DEFAULT: null
    "dot": {
      // REQUIRED: yes | TYPE: string (host:port)
      "listen_addr": "0.0.0.0:853",
      // REQUIRED: yes | TYPE: string
      "cert_path": "/etc/titaniumguard/tls.crt",
      // REQUIRED: yes | TYPE: string
      "key_path": "/etc/titaniumguard/tls.key"
    },

    // REQUIRED: no | TYPE: HttpsTransportConfig | DEFAULT: null
    "doh": {
      // REQUIRED: yes | TYPE: string (host:port)
      "listen_addr": "0.0.0.0:8443",
      // REQUIRED: yes | TYPE: string
      "cert_path": "/etc/titaniumguard/tls.crt",
      // REQUIRED: yes | TYPE: string
      "key_path": "/etc/titaniumguard/tls.key",
      // REQUIRED: no | TYPE: string | DEFAULT: null
      "dns_hostname": "dns.example.com",
      // REQUIRED: no | TYPE: string | DEFAULT: "/dns-query"
      "endpoint": "/dns-query",
      // REQUIRED: no | TYPE: u32 | DEFAULT: 1024
      "max_doh_h2_connections": 1024,
      // REQUIRED: no | TYPE: u32 | DEFAULT: 100
      "max_doh_h2_streams_per_connection": 100,
      // REQUIRED: no | TYPE: usize | DEFAULT: 4096
      "max_doh_body_bytes": 4096,
      // REQUIRED: no | TYPE: u64 | DEFAULT: 5000
      "read_header_timeout_ms": 5000,
      // REQUIRED: no | TYPE: u64 | DEFAULT: 10000
      "read_timeout_ms": 10000,
      // REQUIRED: no | TYPE: u64 | DEFAULT: 10000
      "write_timeout_ms": 10000,
      // REQUIRED: no | TYPE: u64 | DEFAULT: 30000
      "idle_timeout_ms": 30000,

      // REQUIRED: no | TYPE: ODoHConfig | DEFAULT: null
      // HPKE identifiers from RFC 9180.
      "odoh": {
        // REQUIRED: yes (when odoh is set) | TYPE: string
        // ALLOWED (RFC 9180 Table 2):
        // "DHKEM(P-256, HKDF-SHA256)"
        // "DHKEM(P-384, HKDF-SHA384)"
        // "DHKEM(P-521, HKDF-SHA512)"
        // "DHKEM(X25519, HKDF-SHA256)"
        // "DHKEM(X448, HKDF-SHA512)"
        // Also accepts numeric values such as "0x0020".
        "kem_id": "DHKEM(X25519, HKDF-SHA256)",

        // REQUIRED: yes (when odoh is set) | TYPE: string
        // ALLOWED (RFC 9180 Table 3):
        // "HKDF-SHA256"
        // "HKDF-SHA384"
        // "HKDF-SHA512"
        // Also accepts numeric values such as "0x0001".
        "kdf_id": "HKDF-SHA256",

        // REQUIRED: yes (when odoh is set) | TYPE: string
        // ALLOWED (RFC 9180 Table 5):
        // "AES-128-GCM"
        // "AES-256-GCM"
        // "CHACHA20POLY1305"
        // "EXPORT-ONLY"
        // Also accepts numeric values such as "0x0001".
        "aead_id": "AES-128-GCM",

        // REQUIRED: yes (when odoh is set) | TYPE: string (base64)
        // Public key bytes for the selected KEM.
        "public_key_base64": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
      }
    },

    // REQUIRED: no | TYPE: QuicTransportConfig | DEFAULT: null
    "doq": {
      "listen_addr": "0.0.0.0:8853",
      "cert_path": "/etc/titaniumguard/tls.crt",
      "key_path": "/etc/titaniumguard/tls.key",
      // REQUIRED: no | TYPE: string | DEFAULT: null
      "dns_hostname": "dns.example.com",
      // REQUIRED: no | TYPE: u32 | DEFAULT: 512
      "max_concurrent_queries": 512,
      // REQUIRED: no | TYPE: u32 | DEFAULT: 100
      "max_streams_per_connection": 100,
      // REQUIRED: no | TYPE: u64 | DEFAULT: 30000
      "idle_timeout_ms": 30000,
      // REQUIRED: no | TYPE: u32 | DEFAULT: 65535
      "max_message_bytes": 65535
    },

    // REQUIRED: no | TYPE: QuicTransportConfig | DEFAULT: null
    "doh3": {
      "listen_addr": "0.0.0.0:8444",
      "cert_path": "/etc/titaniumguard/tls.crt",
      "key_path": "/etc/titaniumguard/tls.key",
      "dns_hostname": "dns.example.com",
      "max_concurrent_queries": 512,
      "max_streams_per_connection": 100,
      "idle_timeout_ms": 30000,
      // DoH3 uses the same default body limit as DoH.
      "max_message_bytes": 4096
    }
  },

  // REQUIRED: no
  // TYPE: LoggingConfig
  // DEFAULT: values below
  "logging": {
    // REQUIRED: no | TYPE: bool | DEFAULT: true
    "enabled": true,

    // REQUIRED: no | TYPE: enum | DEFAULT: "strict"
    // ALLOWED: "strict", "standard", "enterprise_forensics"
    "default_mode": "strict",

    // REQUIRED: no | TYPE: string | DEFAULT: "var/log/dns"
    "log_dir": "var/log/dns",

    // REQUIRED: no | TYPE: u64 | DEFAULT: 60
    "key_rotation_minutes": 60,

    // REQUIRED: yes in production when logging.enabled=true
    // TYPE: string | DEFAULT: ""
    "hmac_secret": "replace-with-strong-secret",

    // REQUIRED: no | TYPE: u16 | DEFAULT: 30
    "default_retention_days": 30,

    // REQUIRED: no | TYPE: u64 | DEFAULT: 300
    "purge_interval_seconds": 300,

    // REQUIRED: no | TYPE: u32 | DEFAULT: 4096
    "queue_capacity": 4096,

    // REQUIRED: no | TYPE: u32 | DEFAULT: 64
    "batch_size": 64,

    // REQUIRED: no | TYPE: u64 | DEFAULT: 10
    "flush_interval_ms": 10,

    // REQUIRED: no | TYPE: TenantLoggingRule[] | DEFAULT: []
    "tenants": [
      {
        // REQUIRED: yes | TYPE: string
        "tenant_id": "tenant-a",

        // REQUIRED: yes | TYPE: enum
        // ALLOWED: "strict", "standard", "enterprise_forensics"
        "mode": "enterprise_forensics",

        // REQUIRED: no | TYPE: u16 | DEFAULT: null
        "retention_days": 90,

        // REQUIRED: no | TYPE: string[] | DEFAULT: []
        "client_cidrs": ["10.0.0.0/8", "192.168.0.0/16"]
      }
    ]
  }
}

ODoH discovery endpoint

When transports.doh.odoh is configured, the DoH listener also serves:

  • GET /.well-known/odohconfigs

The response body is binary ObliviousDoHConfigs data (RFC 9230 format).

The DNS server no longer publishes placeholder ODoH keys. transports.doh.odoh.public_key_base64 must be configured with real key material matching kem_id.