{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/0xERR0R/blocky/config/config",
  "properties": {
    "upstreams": {
      "properties": {
        "init": {
          "properties": {
            "strategy": {
              "type": "string",
              "enum": [
                "blocking",
                "failOnError",
                "fast"
              ],
              "description": "Startup strategy controlling how initialization failures are handled.\n\n- `blocking`: Initialization runs before DNS resolution starts; errors are logged but Blocky keeps running if possible.\n- `failOnError`: Like blocking but Blocky exits with an error if initialization fails.\n- `fast`: Blocky serves DNS immediately and runs initialization in the background.",
              "default": "blocking"
            }
          },
          "additionalProperties": false,
          "type": "object",
          "description": "Initialization strategy controlling when upstream resolvers are tested on startup."
        },
        "timeout": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "integer"
            }
          ],
          "description": "Timeout for upstream DNS connections; a value \u003c= 0 is reset to the default.",
          "default": "2s",
          "examples": [
            "30s",
            "1h"
          ]
        },
        "groups": {
          "additionalProperties": {
            "items": {
              "type": "string",
              "description": "Upstream DNS server: [net]:host[:port][/path][#commonName] or sdns://...",
              "examples": [
                "tcp+udp:1.1.1.1",
                "https://dns.google/dns-query",
                "tcp-tls:1.1.1.1:853"
              ]
            },
            "type": "array"
          },
          "type": "object",
          "description": "Named groups of upstream DNS resolvers; the \"default\" group is required."
        },
        "strategy": {
          "type": "string",
          "enum": [
            "parallel_best",
            "strict",
            "random"
          ],
          "description": "Strategy for selecting which upstream(s) to use per query (parallel_best, random, strict).\n\n- `parallel_best`: Picks 2 random weighted resolvers per query and returns the fastest answer (default).\n- `strict`: Queries upstreams in strict order; the next is tried only if the previous fails.\n- `random`: Picks one random weighted resolver per query; another is tried on failure.",
          "default": "parallel_best"
        },
        "userAgent": {
          "type": "string",
          "description": "HTTP User-Agent header sent when connecting to DoH upstream servers."
        },
        "quic": {
          "properties": {
            "maxIdleTimeout": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ],
              "description": "Maximum idle duration before the QUIC connection is closed.",
              "default": "30s",
              "examples": [
                "30s",
                "1h"
              ]
            },
            "keepAlivePeriod": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ],
              "description": "Interval at which keep-alive packets are sent to maintain the QUIC connection.",
              "default": "15s",
              "examples": [
                "30s",
                "1h"
              ]
            }
          },
          "additionalProperties": false,
          "type": "object",
          "description": "QUIC-specific connection settings used when DoQ upstreams are configured."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "description": "Upstream DNS servers and strategy configuration."
    },
    "connectIPVersion": {
      "type": "string",
      "enum": [
        "dual",
        "v4",
        "v6"
      ],
      "description": "IP version used for outgoing connections (dual, v4, v6).\n\n- `dual`: Use both IPv4 and IPv6.\n- `v4`: Use IPv4 only.\n- `v6`: Use IPv6 only."
    },
    "customDNS": {
      "properties": {
        "rewrite": {
          "additionalProperties": {
            "type": "string"
          },
          "type": "object",
          "description": "Domain rewrite rules applied before resolution; keys are rewritten to their values."
        },
        "fallbackUpstream": {
          "type": "boolean",
          "description": "If true, the original query is sent upstream when the mapped resolver returns an empty answer.",
          "default": false
        },
        "customTTL": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "integer"
            }
          ],
          "description": "TTL for DNS records defined in the mapping section (does not apply to zone file records).",
          "default": "1h",
          "examples": [
            "30s",
            "1h"
          ]
        },
        "mapping": {
          "additionalProperties": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "items": {
                  "type": "string"
                },
                "type": "array"
              }
            ]
          },
          "type": "object",
          "description": "Simple domain-to-IP mappings; multiple IPs per domain separated by commas."
        },
        "zone": {
          "type": "string",
          "description": "DNS zone file content for more complex record definitions (A, AAAA, CNAME, TXT, SRV).",
          "default": ""
        },
        "filterUnmappedTypes": {
          "type": "boolean",
          "description": "If true, queries for types not defined for a domain return empty; if false, they are forwarded upstream.",
          "default": true
        }
      },
      "additionalProperties": false,
      "type": "object",
      "description": "Custom static DNS mappings and zone definitions."
    },
    "conditional": {
      "properties": {
        "rewrite": {
          "additionalProperties": {
            "type": "string"
          },
          "type": "object",
          "description": "Domain rewrite rules applied before resolution; keys are rewritten to their values."
        },
        "fallbackUpstream": {
          "type": "boolean",
          "description": "If true, the original query is sent upstream when the mapped resolver returns an empty answer.",
          "default": false
        },
        "mapping": {
          "additionalProperties": {
            "type": "string"
          },
          "type": "object",
          "description": "Domain-to-upstream mappings; queries for each domain are forwarded to its configured resolver(s)."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "description": "Conditional upstream resolvers for specific domains."
    },
    "blocking": {
      "properties": {
        "denylists": {
          "additionalProperties": {
            "items": {
              "type": "string",
              "description": "Source: an http(s) URL, a local file path, or an inline YAML block.",
              "examples": [
                "https://example.com/list.txt",
                "/etc/blocky/list.txt"
              ]
            },
            "type": "array"
          },
          "type": "object",
          "description": "Named groups of block-list sources (URLs, file paths, or inline content)."
        },
        "allowlists": {
          "additionalProperties": {
            "items": {
              "type": "string",
              "description": "Source: an http(s) URL, a local file path, or an inline YAML block.",
              "examples": [
                "https://example.com/list.txt",
                "/etc/blocky/list.txt"
              ]
            },
            "type": "array"
          },
          "type": "object",
          "description": "Named groups of allow-list sources; entries here take precedence over denylists in the same group."
        },
        "schedules": {
          "additionalProperties": {
            "properties": {
              "start": {
                "type": "string",
                "description": "Start time in HH:MM format; omit together with end for an all-day schedule."
              },
              "end": {
                "type": "string",
                "description": "End time in HH:MM format; if before start, the window is overnight (e.g. 22:00 to 06:00)."
              },
              "weekdays": {
                "items": {
                  "type": "string",
                  "description": "Day of week: mon, tue, wed, thu, fri, sat, sun.",
                  "examples": [
                    "mon",
                    "sat"
                  ]
                },
                "type": "array",
                "description": "Days of the week this schedule is active (mon, tue, wed, thu, fri, sat, sun)."
              }
            },
            "additionalProperties": false,
            "type": "object",
            "description": "Schedule defines a time-based schedule for blocking rules."
          },
          "type": "object",
          "description": "Named time-based schedules that can gate when list groups are active."
        },
        "listSchedules": {
          "additionalProperties": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "type": "object",
          "description": "Maps each list group name to the schedule(s) that control when it is active."
        },
        "clientGroupsBlock": {
          "additionalProperties": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "type": "object",
          "description": "Maps client identifiers (name, IP, CIDR) to the list groups that apply to them."
        },
        "blockType": {
          "type": "string",
          "description": "Response for blocked A/AAAA queries: zeroIP (0.0.0.0/::), nxDomain, or custom IPs; other types get NXDOMAIN.",
          "default": "ZEROIP"
        },
        "blockTTL": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "integer"
            }
          ],
          "description": "TTL of blocked responses; how long clients cache the block before querying the domain again.",
          "default": "6h",
          "examples": [
            "30s",
            "1h"
          ]
        },
        "loading": {
          "properties": {
            "strategy": {
              "type": "string",
              "enum": [
                "blocking",
                "failOnError",
                "fast"
              ],
              "description": "Startup strategy controlling how initialization failures are handled.\n\n- `blocking`: Initialization runs before DNS resolution starts; errors are logged but Blocky keeps running if possible.\n- `failOnError`: Like blocking but Blocky exits with an error if initialization fails.\n- `fast`: Blocky serves DNS immediately and runs initialization in the background.",
              "default": "blocking"
            },
            "concurrency": {
              "type": "integer",
              "description": "Maximum number of sources downloaded and processed concurrently (default: 4).",
              "default": 4
            },
            "maxErrorsPerSource": {
              "type": "integer",
              "description": "Maximum parse errors per source before the source is considered invalid; -1 disables the limit.",
              "default": 5
            },
            "refreshPeriod": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ],
              "description": "How often sources are reloaded; a value of 0 or less disables periodic refresh (default: 4h).",
              "default": "4h",
              "examples": [
                "30s",
                "1h"
              ]
            },
            "downloads": {
              "properties": {
                "timeout": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "integer"
                    }
                  ],
                  "description": "Timeout per download attempt (default: 5s).",
                  "default": "5s",
                  "examples": [
                    "30s",
                    "1h"
                  ]
                },
                "readTimeout": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "integer"
                    }
                  ],
                  "description": "Timeout for reading the download response body (default: 20s).",
                  "default": "20s",
                  "examples": [
                    "30s",
                    "1h"
                  ]
                },
                "readHeaderTimeout": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "integer"
                    }
                  ],
                  "description": "Timeout for reading the download response headers (default: 20s).",
                  "default": "20s",
                  "examples": [
                    "30s",
                    "1h"
                  ]
                },
                "writeTimeout": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "integer"
                    }
                  ],
                  "description": "Timeout for writing the downloaded file (default: 20s).",
                  "default": "20s",
                  "examples": [
                    "30s",
                    "1h"
                  ]
                },
                "attempts": {
                  "type": "integer",
                  "description": "Number of download attempts before giving up (default: 3).",
                  "default": 3
                },
                "cooldown": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "integer"
                    }
                  ],
                  "description": "Pause between consecutive download attempts (default: 500ms).",
                  "default": "500ms",
                  "examples": [
                    "30s",
                    "1h"
                  ]
                }
              },
              "additionalProperties": false,
              "type": "object",
              "description": "HTTP(S) download settings for remote sources."
            }
          },
          "additionalProperties": false,
          "type": "object",
          "description": "Controls how block/allow lists are loaded and periodically refreshed."
        },
        "blackLists": {
          "additionalProperties": {
            "items": {
              "type": "string",
              "description": "Source: an http(s) URL, a local file path, or an inline YAML block.",
              "examples": [
                "https://example.com/list.txt",
                "/etc/blocky/list.txt"
              ]
            },
            "type": "array"
          },
          "type": "object",
          "deprecated": true
        },
        "whiteLists": {
          "additionalProperties": {
            "items": {
              "type": "string",
              "description": "Source: an http(s) URL, a local file path, or an inline YAML block.",
              "examples": [
                "https://example.com/list.txt",
                "/etc/blocky/list.txt"
              ]
            },
            "type": "array"
          },
          "type": "object",
          "deprecated": true
        },
        "downloadTimeout": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "integer"
            }
          ],
          "deprecated": true,
          "examples": [
            "30s",
            "1h"
          ]
        },
        "downloadAttempts": {
          "type": "integer",
          "deprecated": true
        },
        "downloadCooldown": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "integer"
            }
          ],
          "deprecated": true,
          "examples": [
            "30s",
            "1h"
          ]
        },
        "refreshPeriod": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "integer"
            }
          ],
          "deprecated": true,
          "examples": [
            "30s",
            "1h"
          ]
        },
        "failStartOnListError": {
          "type": "boolean",
          "deprecated": true
        },
        "processingConcurrency": {
          "type": "integer",
          "deprecated": true
        },
        "startStrategy": {
          "type": "string",
          "enum": [
            "blocking",
            "failOnError",
            "fast"
          ],
          "deprecated": true
        },
        "maxErrorsPerFile": {
          "type": "integer",
          "deprecated": true
        }
      },
      "additionalProperties": false,
      "type": "object",
      "description": "Blocking configuration with allow/denylists and client groups."
    },
    "clientLookup": {
      "properties": {
        "clients": {
          "additionalProperties": {
            "items": {
              "type": "string",
              "format": "ipv4"
            },
            "type": "array"
          },
          "type": "object",
          "description": "Static map of client name to one or more IP addresses for manual client name assignment."
        },
        "upstream": {
          "type": "string",
          "description": "Upstream DNS server used for rDNS client name lookups (typically your router).",
          "examples": [
            "tcp+udp:1.1.1.1",
            "https://dns.google/dns-query",
            "tcp-tls:1.1.1.1:853"
          ]
        },
        "singleNameOrder": {
          "items": {
            "type": "integer"
          },
          "type": "array",
          "description": "Order of preference when a router returns multiple names for a client (1-based index)."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "description": "Client name lookup configuration for resolving client identifiers."
    },
    "caching": {
      "properties": {
        "minTime": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "integer"
            }
          ],
          "description": "Minimum TTL for cached entries; if the response TTL is smaller, this value is used instead.",
          "examples": [
            "30s",
            "1h"
          ]
        },
        "maxTime": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "integer"
            }
          ],
          "description": "Maximum TTL for cached entries. If \u003c0, caching is disabled. If 0, the response TTL is used.",
          "examples": [
            "30s",
            "1h"
          ]
        },
        "cacheTimeNegative": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "integer"
            }
          ],
          "description": "TTL for negative responses (NXDOMAIN / empty). Use -1 to disable caching of negative results.",
          "default": "30m",
          "examples": [
            "30s",
            "1h"
          ]
        },
        "maxItemsCount": {
          "type": "integer",
          "description": "Maximum number of cache entries (soft limit). 0 means unlimited."
        },
        "prefetching": {
          "type": "boolean",
          "description": "If true, blocky preloads DNS results for frequently queried names before they expire."
        },
        "prefetchExpires": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "integer"
            }
          ],
          "description": "Time window used to track query frequency for prefetch eligibility.",
          "default": "2h",
          "examples": [
            "30s",
            "1h"
          ]
        },
        "prefetchThreshold": {
          "type": "integer",
          "description": "Minimum number of queries within prefetchExpires required to trigger prefetching.",
          "default": 5
        },
        "prefetchMaxItemsCount": {
          "type": "integer",
          "description": "Maximum number of domains tracked for prefetching (soft limit). 0 means unlimited."
        },
        "exclude": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "Regex list of domains that are never cached."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "description": "DNS response caching settings."
    },
    "queryLog": {
      "properties": {
        "target": {
          "type": "string",
          "description": "Directory for CSV log files, or database URL for mysql/postgresql/timescale targets."
        },
        "type": {
          "type": "string",
          "enum": [
            "console",
            "none",
            "mysql",
            "postgresql",
            "csv",
            "csv-client",
            "timescale"
          ],
          "description": "Log target type: mysql, postgresql, timescale, csv, csv-client, console, or none.\n\n- `console`: Log to console output (used when no type is set).\n- `none`: Do not log any queries.\n- `mysql`: Log each query to an external MySQL or MariaDB database.\n- `postgresql`: Log each query to an external PostgreSQL database.\n- `csv`: Log to a CSV file (one per day).\n- `csv-client`: Log to a CSV file (one per day and per client).\n- `timescale`: Log each query to an external Timescale database."
        },
        "logRetentionDays": {
          "type": "integer",
          "description": "Delete log entries older than this many days. 0 disables retention cleanup."
        },
        "creationAttempts": {
          "type": "integer",
          "description": "Maximum number of attempts to create the query log writer on startup.",
          "default": 3
        },
        "creationCooldown": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "integer"
            }
          ],
          "description": "Delay between query log writer creation attempts.",
          "default": "2s",
          "examples": [
            "30s",
            "1h"
          ]
        },
        "fields": {
          "items": {
            "type": "string",
            "enum": [
              "clientIP",
              "clientName",
              "responseReason",
              "responseAnswer",
              "question",
              "duration"
            ]
          },
          "type": "array",
          "description": "Which fields to include in log entries; defaults to all available fields."
        },
        "flushInterval": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "integer"
            }
          ],
          "description": "Interval at which buffered log entries are flushed to the external database.",
          "default": "30s",
          "examples": [
            "30s",
            "1h"
          ]
        },
        "ignore": {
          "properties": {
            "sudn": {
              "type": "boolean",
              "description": "If true, queries resolved as Special Use Domain Names (SUDN) are not logged.",
              "default": false
            }
          },
          "additionalProperties": false,
          "type": "object",
          "description": "Rules to suppress certain queries from being logged."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "description": "Query logging configuration."
    },
    "prometheus": {
      "properties": {
        "enable": {
          "type": "boolean",
          "description": "If true, enables the Prometheus metrics endpoint.",
          "default": false
        },
        "path": {
          "type": "string",
          "description": "URL path under which the Prometheus metrics are served.",
          "default": "/metrics"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "description": "Prometheus metrics configuration."
    },
    "redis": {
      "properties": {
        "address": {
          "type": "string",
          "description": "Server address and port, or the sentinel master name when sentinel is used."
        },
        "username": {
          "type": "string",
          "description": "Redis username (if authentication is required).",
          "default": ""
        },
        "password": {
          "type": "string",
          "description": "Redis password (if authentication is required).",
          "default": ""
        },
        "database": {
          "type": "integer",
          "description": "Redis database index to use.",
          "default": 0
        },
        "required": {
          "type": "boolean",
          "description": "If true, blocky will not start when the Redis connection cannot be established.",
          "default": false
        },
        "connectionAttempts": {
          "type": "integer",
          "description": "Maximum number of connection attempts before giving up.",
          "default": 3
        },
        "connectionCooldown": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "integer"
            }
          ],
          "description": "Delay between consecutive connection attempts.",
          "default": "1s",
          "examples": [
            "30s",
            "1h"
          ]
        },
        "sentinelUsername": {
          "type": "string",
          "description": "Sentinel username (if sentinel authentication is required).",
          "default": ""
        },
        "sentinelPassword": {
          "type": "string",
          "description": "Sentinel password (if sentinel authentication is required).",
          "default": ""
        },
        "sentinelAddresses": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "List of Redis Sentinel host:port addresses; enables sentinel mode when non-empty."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "description": "Redis configuration for cache and state synchronization between instances."
    },
    "log": {
      "properties": {
        "level": {
          "type": "string",
          "default": "info",
          "examples": [
            "info",
            "debug"
          ]
        },
        "format": {
          "type": "string",
          "enum": [
            "text",
            "json"
          ],
          "description": "- `text`: Human-readable text.\n- `json`: Structured JSON.",
          "default": "text"
        },
        "privacy": {
          "type": "boolean",
          "default": false
        },
        "timestamp": {
          "type": "boolean",
          "default": true
        }
      },
      "additionalProperties": false,
      "type": "object",
      "description": "Logging configuration."
    },
    "ports": {
      "properties": {
        "dns": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "integer"
            },
            {
              "items": {
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "integer"
                  }
                ]
              },
              "type": "array"
            }
          ],
          "description": "Listen address(es) for DNS over TCP and UDP (default: 53).",
          "default": "53"
        },
        "http": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "integer"
            },
            {
              "items": {
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "integer"
                  }
                ]
              },
              "type": "array"
            }
          ],
          "description": "Listen address(es) for HTTP (metrics, REST API, DoH)."
        },
        "https": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "integer"
            },
            {
              "items": {
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "integer"
                  }
                ]
              },
              "type": "array"
            }
          ],
          "description": "Listen address(es) for HTTPS (metrics, REST API, DoH)."
        },
        "tls": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "integer"
            },
            {
              "items": {
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "integer"
                  }
                ]
              },
              "type": "array"
            }
          ],
          "description": "Listen address(es) for DNS-over-TLS (DoT)."
        },
        "dohPath": {
          "type": "string",
          "description": "URL path for DoH queries.",
          "default": "/dns-query"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "description": "Listen addresses for DNS, HTTP, HTTPS, and TLS."
    },
    "minTlsServeVersion": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "1.0",
            "1.1",
            "1.2",
            "1.3"
          ]
        },
        {
          "type": "number"
        }
      ],
      "description": "Minimum TLS version the DoT and DoH servers use to serve encrypted DNS requests.",
      "default": "1.2"
    },
    "certFile": {
      "type": "string",
      "description": "Path to the TLS certificate file for DoH and DoT; if empty, a self-signed certificate is generated."
    },
    "keyFile": {
      "type": "string",
      "description": "Path to the TLS key file for DoH and DoT; if empty, a self-signed certificate is generated."
    },
    "bootstrapDns": {
      "anyOf": [
        {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "properties": {
                "upstream": {
                  "type": "string"
                },
                "ips": {
                  "items": {
                    "type": "string"
                  },
                  "type": "array"
                },
                "resolvFile": {
                  "type": "string"
                }
              },
              "additionalProperties": false,
              "type": "object"
            }
          ]
        },
        {
          "items": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "properties": {
                  "upstream": {
                    "type": "string"
                  },
                  "ips": {
                    "items": {
                      "type": "string"
                    },
                    "type": "array"
                  },
                  "resolvFile": {
                    "type": "string"
                  }
                },
                "additionalProperties": false,
                "type": "object"
              }
            ]
          },
          "type": "array"
        }
      ],
      "description": "Bootstrap DNS servers used to resolve DoH/DoT upstream hostnames."
    },
    "hostsFile": {
      "properties": {
        "sources": {
          "items": {
            "type": "string",
            "description": "Source: an http(s) URL, a local file path, or an inline YAML block.",
            "examples": [
              "https://example.com/list.txt",
              "/etc/blocky/list.txt"
            ]
          },
          "type": "array",
          "description": "Host files to load (e.g. /etc/hosts); supports local paths, URLs, and inline content."
        },
        "hostsTTL": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "integer"
            }
          ],
          "description": "TTL for DNS records resolved from host files.",
          "default": "1h",
          "examples": [
            "30s",
            "1h"
          ]
        },
        "filterLoopback": {
          "type": "boolean",
          "description": "If true, loopback addresses (127.0.0.0/8 and ::1) from host files are ignored."
        },
        "loading": {
          "properties": {
            "strategy": {
              "type": "string",
              "enum": [
                "blocking",
                "failOnError",
                "fast"
              ],
              "description": "Startup strategy controlling how initialization failures are handled.\n\n- `blocking`: Initialization runs before DNS resolution starts; errors are logged but Blocky keeps running if possible.\n- `failOnError`: Like blocking but Blocky exits with an error if initialization fails.\n- `fast`: Blocky serves DNS immediately and runs initialization in the background.",
              "default": "blocking"
            },
            "concurrency": {
              "type": "integer",
              "description": "Maximum number of sources downloaded and processed concurrently (default: 4).",
              "default": 4
            },
            "maxErrorsPerSource": {
              "type": "integer",
              "description": "Maximum parse errors per source before the source is considered invalid; -1 disables the limit.",
              "default": 5
            },
            "refreshPeriod": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ],
              "description": "How often sources are reloaded; a value of 0 or less disables periodic refresh (default: 4h).",
              "default": "4h",
              "examples": [
                "30s",
                "1h"
              ]
            },
            "downloads": {
              "properties": {
                "timeout": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "integer"
                    }
                  ],
                  "description": "Timeout per download attempt (default: 5s).",
                  "default": "5s",
                  "examples": [
                    "30s",
                    "1h"
                  ]
                },
                "readTimeout": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "integer"
                    }
                  ],
                  "description": "Timeout for reading the download response body (default: 20s).",
                  "default": "20s",
                  "examples": [
                    "30s",
                    "1h"
                  ]
                },
                "readHeaderTimeout": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "integer"
                    }
                  ],
                  "description": "Timeout for reading the download response headers (default: 20s).",
                  "default": "20s",
                  "examples": [
                    "30s",
                    "1h"
                  ]
                },
                "writeTimeout": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "integer"
                    }
                  ],
                  "description": "Timeout for writing the downloaded file (default: 20s).",
                  "default": "20s",
                  "examples": [
                    "30s",
                    "1h"
                  ]
                },
                "attempts": {
                  "type": "integer",
                  "description": "Number of download attempts before giving up (default: 3).",
                  "default": 3
                },
                "cooldown": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "integer"
                    }
                  ],
                  "description": "Pause between consecutive download attempts (default: 500ms).",
                  "default": "500ms",
                  "examples": [
                    "30s",
                    "1h"
                  ]
                }
              },
              "additionalProperties": false,
              "type": "object",
              "description": "HTTP(S) download settings for remote sources."
            }
          },
          "additionalProperties": false,
          "type": "object",
          "description": "Controls how host files are loaded and periodically refreshed."
        },
        "refreshPeriod": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "integer"
            }
          ],
          "deprecated": true,
          "examples": [
            "30s",
            "1h"
          ]
        },
        "filePath": {
          "type": "string",
          "deprecated": true,
          "examples": [
            "https://example.com/list.txt",
            "/etc/blocky/list.txt"
          ]
        }
      },
      "additionalProperties": false,
      "type": "object",
      "description": "Local hosts file resolution settings."
    },
    "fqdnOnly": {
      "properties": {
        "enable": {
          "type": "boolean",
          "default": false
        }
      },
      "additionalProperties": false,
      "type": "object",
      "description": "When enabled, blocky returns NXDOMAIN immediately for non-FQDN queries."
    },
    "filtering": {
      "properties": {
        "queryTypes": {
          "items": {
            "type": "string",
            "enum": [
              "A",
              "AAAA",
              "AFSDB",
              "AMTRELAY",
              "ANY",
              "APL",
              "ATMA",
              "AVC",
              "AXFR",
              "CAA",
              "CDNSKEY",
              "CDS",
              "CERT",
              "CNAME",
              "CSYNC",
              "DHCID",
              "DLV",
              "DNAME",
              "DNSKEY",
              "DS",
              "EID",
              "EUI48",
              "EUI64",
              "GID",
              "GPOS",
              "HINFO",
              "HIP",
              "HTTPS",
              "IPSECKEY",
              "ISDN",
              "IXFR",
              "KEY",
              "KX",
              "L32",
              "L64",
              "LOC",
              "LP",
              "MAILA",
              "MAILB",
              "MB",
              "MD",
              "MF",
              "MG",
              "MINFO",
              "MR",
              "MX",
              "NAPTR",
              "NID",
              "NIMLOC",
              "NINFO",
              "NS",
              "NSAP-PTR",
              "NSEC",
              "NSEC3",
              "NSEC3PARAM",
              "NULL",
              "NXNAME",
              "NXT",
              "None",
              "OPENPGPKEY",
              "OPT",
              "PTR",
              "PX",
              "RESINFO",
              "RKEY",
              "RP",
              "RRSIG",
              "RT",
              "Reserved",
              "SIG",
              "SMIMEA",
              "SOA",
              "SPF",
              "SRV",
              "SSHFP",
              "SVCB",
              "TA",
              "TALINK",
              "TKEY",
              "TLSA",
              "TSIG",
              "TXT",
              "UID",
              "UINFO",
              "UNSPEC",
              "URI",
              "X25",
              "ZONEMD"
            ]
          },
          "type": "array",
          "description": "DNS query types to drop; matching queries receive an empty answer (e.g. AAAA to block IPv6 lookups)."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "description": "DNS query type filtering configuration."
    },
    "ede": {
      "properties": {
        "enable": {
          "type": "boolean",
          "default": false
        }
      },
      "additionalProperties": false,
      "type": "object",
      "description": "Extended DNS Errors (RFC 8914) configuration."
    },
    "ecs": {
      "properties": {
        "useAsClient": {
          "type": "boolean",
          "description": "Use the ECS client subnet as the client IP when a full-prefix ECS option is present.",
          "default": false
        },
        "forward": {
          "type": "boolean",
          "description": "Forward the ECS option from the client request to upstream resolvers.",
          "default": false
        },
        "ipv4Mask": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "string"
            }
          ],
          "description": "Subnet mask for IPv4 EDNS Client Subnet; adds ECS option when greater than zero (max 32).",
          "default": "0"
        },
        "ipv6Mask": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "string"
            }
          ],
          "description": "Subnet mask for IPv6 EDNS Client Subnet; adds ECS option when greater than zero (max 128).",
          "default": "0"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "description": "EDNS Client Subnet options."
    },
    "specialUseDomains": {
      "properties": {
        "rfc6762-appendixG": {
          "type": "boolean",
          "description": "These are \"recommended for private use\" but not mandatory.\nIf a user wishes to use one, it will most likely be via conditional\nupstream or custom DNS, which come before SUDN in the resolver chain.\nThus defaulting to `true` and returning NXDOMAIN here should not conflict.",
          "default": true
        },
        "enable": {
          "type": "boolean",
          "description": "Set to false to completely disable Special Use Domain Name blocking (not recommended for remote upstreams).",
          "default": true
        }
      },
      "additionalProperties": false,
      "type": "object",
      "description": "Special Use Domain Names (SUDN) blocking configuration."
    },
    "dns64": {
      "properties": {
        "enable": {
          "type": "boolean",
          "description": "Enable DNS64 synthesis of AAAA records from A records for IPv6-only clients (RFC 6147).",
          "default": false
        },
        "prefixes": {
          "items": {
            "type": "string",
            "description": "CIDR network prefix.",
            "examples": [
              "64:ff9b::/96"
            ]
          },
          "type": "array",
          "description": "IPv6 prefixes used for synthesis; valid lengths are /32, /40, /48, /56, /64, /96 (default: 64:ff9b::/96)."
        },
        "exclusionSet": {
          "items": {
            "type": "string",
            "description": "CIDR network prefix.",
            "examples": [
              "64:ff9b::/96"
            ]
          },
          "type": "array",
          "description": "IPv6 prefixes excluded from triggering synthesis. Overrides the default exclusion set; advanced use only."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "description": "DNS64 synthesis configuration for IPv6-only clients (RFC 6147)."
    },
    "dnssec": {
      "properties": {
        "validate": {
          "type": "boolean",
          "description": "Enable DNSSEC validation of DNS responses.",
          "default": false
        },
        "trustAnchors": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "Custom trust anchors (DNSKEY or DS records); empty uses built-in IANA root trust anchors."
        },
        "maxChainDepth": {
          "type": "integer",
          "description": "Maximum domain label depth for chain of trust validation (DoS protection).",
          "default": 10
        },
        "cacheExpirationHours": {
          "type": "integer",
          "description": "How long to cache DNSSEC validation results, in hours.",
          "default": 1
        },
        "maxNSEC3Iterations": {
          "type": "integer",
          "description": "RFC 5155 §10.3",
          "default": 150
        },
        "maxUpstreamQueries": {
          "type": "integer",
          "description": "DoS protection: max upstream queries per validation",
          "default": 30
        },
        "clockSkewToleranceSec": {
          "type": "integer",
          "description": "Clock skew tolerance in seconds for signature validation (default: 3600 = 1 hour)\nAllows validation to succeed even if system clock is off by this amount.\nMatches Unbound/BIND defaults for real-world deployments (VMs, containers, embedded systems).\nPer RFC 6781 §4.1.2: Validators should account for clock skew in deployment environments.",
          "default": 3600
        }
      },
      "additionalProperties": false,
      "type": "object",
      "description": "DNSSEC validation configuration."
    },
    "http3": {
      "properties": {
        "enable": {
          "type": "boolean",
          "description": "Enable the HTTP/3 listener on the same addresses as ports.https (requires ports.https to be set).",
          "default": false
        }
      },
      "additionalProperties": false,
      "type": "object",
      "description": "HTTP/3 (DoH3) server configuration."
    },
    "rateLimit": {
      "properties": {
        "enable": {
          "type": "boolean",
          "description": "Enable per-client DNS rate limiting.",
          "default": false
        },
        "rate": {
          "type": "integer",
          "description": "Sustained query rate limit in queries per second per client.",
          "default": 0
        },
        "burst": {
          "type": "integer",
          "description": "Token bucket capacity; queries above this burst are dropped. Defaults to rate × 2.",
          "default": 0
        },
        "ipv4Prefix": {
          "type": "integer",
          "description": "Prefix length used to aggregate IPv4 client addresses into a single bucket.",
          "default": 32
        },
        "ipv6Prefix": {
          "type": "integer",
          "description": "Prefix length used to aggregate IPv6 client addresses into a single bucket.",
          "default": 64
        },
        "allowlist": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "description": "CIDRs or IPs that are never rate-limited."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "description": "Per-client DNS query rate limiting configuration."
    },
    "upstream": {
      "additionalProperties": {
        "items": {
          "type": "string",
          "description": "Upstream DNS server: [net]:host[:port][/path][#commonName] or sdns://...",
          "examples": [
            "tcp+udp:1.1.1.1",
            "https://dns.google/dns-query",
            "tcp-tls:1.1.1.1:853"
          ]
        },
        "type": "array"
      },
      "type": "object",
      "deprecated": true
    },
    "upstreamTimeout": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "integer"
        }
      ],
      "deprecated": true,
      "examples": [
        "30s",
        "1h"
      ]
    },
    "disableIPv6": {
      "type": "boolean",
      "deprecated": true
    },
    "logLevel": {
      "type": "string",
      "deprecated": true,
      "examples": [
        "info",
        "debug"
      ]
    },
    "logFormat": {
      "type": "string",
      "enum": [
        "text",
        "json"
      ],
      "deprecated": true
    },
    "logPrivacy": {
      "type": "boolean",
      "deprecated": true
    },
    "logTimestamp": {
      "type": "boolean",
      "deprecated": true
    },
    "port": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "integer"
        },
        {
          "items": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "integer"
              }
            ]
          },
          "type": "array"
        }
      ],
      "deprecated": true
    },
    "httpPort": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "integer"
        },
        {
          "items": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "integer"
              }
            ]
          },
          "type": "array"
        }
      ],
      "deprecated": true
    },
    "httpsPort": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "integer"
        },
        {
          "items": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "integer"
              }
            ]
          },
          "type": "array"
        }
      ],
      "deprecated": true
    },
    "tlsPort": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "integer"
        },
        {
          "items": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "integer"
              }
            ]
          },
          "type": "array"
        }
      ],
      "deprecated": true
    },
    "startVerifyUpstream": {
      "type": "boolean",
      "deprecated": true
    },
    "dohUserAgent": {
      "type": "string",
      "deprecated": true
    }
  },
  "additionalProperties": false,
  "type": "object",
  "description": "Config main configuration"
}
