Executor Plugins
Executors are the core action layer in OxiDNS. They can read or write requests, set responses, query upstreams, cache results, perform fallback logic, emit logs, or trigger system integrations.
When reading this chapter, keep two questions in mind:
- Does this plugin act only in the forward stage, or can it also rewrite results on the return path?
- Is it part of the main resolution path, or an observability and side-effect plugin?
sequence
Purpose
Orchestrates matchers and executors into a pipeline. This is the most common entry executor.
Example Configuration
- tag: seq_main
type: sequence
args:
# Try cache first
- exec: "$cache_main"
# Stop immediately on cache hit
- matches: "has_resp"
exec: "accept"
# Arrays of matches are AND-ed together
# Examples prefer quick-setup matcher expressions directly
- matches:
- "client_ip $lan_ip_set"
- "qname $local_domains"
exec: "$hosts_main"
# Rules may also execute unconditionally
- exec: "$metrics_main"
# Only forward when no response exists yet
- matches: "!has_resp"
exec: "$forward_main"
# Normalize TTL after a response is available
- matches: "has_resp"
exec: "$ttl_main"
Configuration Details
args
- Type:
array; Required: yes; Default: none - Purpose: Defines the rule chain.
- Runtime impact:
- Rules execute in order.
- Initialization fails when the array is empty.
args[].matches
- Type:
stringorarray - Required: no
- Purpose: Match condition for the current rule.
- Runtime impact:
- Multiple conditions are combined with logical AND.
- Omitted means the rule has no precondition.
args[].exec
- Type:
string; Required: no; Default: none - Purpose: Action to run when the rule matches.
- Supports:
- plugin references
- quick setup expressions
- built-in control flow
Behavior
- Rules run sequentially.
- A rule with multiple
matchesrequires all of them to be true. - Other
sequenceinstances can be called withjumporgoto.
Built-In Control Flow
Besides plugin calls, sequence.args[].exec can also use built-in control flow:
accept
- Ends the current
sequenceimmediately. - This is an explicit early stop, so outer callers do not continue with later rules.
- Does not build a response by itself; it is usually used after an earlier stage has already produced one.
return
- Ends the current
sequenceimmediately and gives control back to the caller. - Does not build a response.
- If the current
sequencewas entered byjump, the caller continues with the next rule.
reject [rcode]
- Builds a response immediately and ends the current
sequence. - The default
rcodeisREFUSED. - Decimal numeric rcodes and case-insensitive English RCODE names are accepted, for example
reject 2,reject SERVFAIL,reject servfail, orreject NXDOMAIN; common mappings and meanings are listed in the DNS Code Reference. - Only base DNS RCODEs
0..15are supported; extended RCODEs require an EDNS OPT and are not generated by this built-in action. reject 0returns a plainNOERRORresponse and does not add an SOA automatically.- Stops later rules from running.
mark ...
- Inserts one or more integer marks, then continues to the next rule in the current
sequence. - Supports
mark 1,mark 1 2 3, andmark 1,2,3.
jump seq_tag
- Calls another
sequence; conceptually this is a subroutine call. - The parameter must be the target
sequencetag without$. - If the target
sequencereaches its tail or executesreturn, the currentsequenceresumes with the next rule. - If the target
sequenceexecutesaccept,reject, or anotherStop, the currentsequencestops too.
goto seq_tag
- Transfers control one-way to another
sequence. - The parameter must be the target
sequencetag without$. - Once
gotoruns, the currentsequencenever resumes at later rules. - If the target
sequenceexecutesreturn, thatreturnis propagated outward.
Typical Uses
- One readable top-level entry.
- Split cache, local answers, forwarding, and integrations into understandable policy layers.
- Build complex branches with marks and matchers.
- Referenced plugins must already exist.
- A
sequenceneeds at least one rule.
forward
Purpose
Sends DNS queries to upstreams.
Example Configuration
- tag: forward_main
type: forward
args:
# Effective fan-out in multi-upstream mode
concurrent: 3
# Concurrent upstream response selection: fastest / balanced / prefer_positive / consensus
response_selection: balanced
upstreams:
# Simplest UDP upstream
- tag: "cf_udp"
addr: "udp://1.1.1.1:53"
timeout: 3s
# Domain-based DoH upstream showing bootstrap, pooling, HTTP/3,
# and Linux socket options
- tag: "doh_main"
addr: "https://resolver.example/dns-query"
bootstrap: "8.8.8.8:53"
bootstrap_version: 4
port: 443
idle_timeout: 30
min_conns: 2
max_conns: 256
timeout: 5s
enable_pipeline: false
enable_http3: true
so_mark: 100
bind_to_device: "eth0"
# DoT upstream showing dial_addr, SOCKS5, TLS verification, and pipelining
- tag: "dot_backup"
addr: "tls://dns.example:853"
dial_addr: "203.0.113.53"
socks5: "user:pass@127.0.0.1:1080"
idle_timeout: 60
min_conns: 1
max_conns: 128
insecure_skip_verify: false
timeout: 4s
enable_pipeline: true
Configuration Details
concurrent
- Type:
integer; Required: no; Default:1 - Runtime range is clamped to
1..=32and never exceeds the upstream count. - Purpose: Number of concurrent upstream fan-out requests.
- Recommendation: use
2..=8for routine racing; higher values are intended for short-term observation or explicit multi-protocol racing.
response_selection
- Type:
string; Required: no; Default:balanced - Values:
fastest,balanced,prefer_positive,consensus - Purpose: Selects the winning response when concurrent upstreams return different results.
- Modes:
fastest: the first successful DNS response wins; transport errors never win.balanced: default mode.NOERRORwith answers wins immediately;NXDOMAINor NODATA waits briefly for a positive answer.prefer_positive: positive answers win immediately; negative answers wait until all started concurrent upstreams finish.consensus: positive answers win immediately; negative answers require two upstream confirmations, falling back toprefer_positivewhen fan-out is too small.
upstreams
- Type:
array; Required: yes; Default: none - Purpose: Defines one or more upstream targets.
- Runtime impact:
- One upstream means normal forwarding.
- More than one enables racing behavior.
short_circuit
- Type:
boolean; Required: no; Default:false - Purpose: Stop the executor chain after a successful upstream response.
- Notes:
- When disabled,
forwardstill populatesresponse, but later executors can continue processing it. - When enabled, a successful upstream result immediately ends the remaining executor chain.
- When disabled,
upstreams[].addr
- Type:
string; Required: yes - Purpose: Upstream address, protocol, and target.
- Supports:
udp://tcp://tcp+pipeline://tls://tls+pipeline://quic:///doq://https:///doh://h3://
- Notes:
- No scheme means UDP.
- DoH addresses should include the full request path.
- Startup and config validation do not resolve domain-based upstreams. Without
bootstrapordial_addr, the hostname is resolved through the OS resolver when the first connection is created. - For domain-based upstreams, choose either
bootstrapordial_addrto avoid runtime bootstrap dependency on the local DNS setup. bootstrapanddial_addrare mutually exclusive at runtime. If both are configured, onlydial_addris effective andbootstrapis ignored.
upstreams[].tag
- Type:
string; Required: no - Purpose: Per-upstream log label.
upstreams[].dial_addr
- Type:
ip; Required: no - Purpose: Actual connection IP while preserving the hostname from
addrfor SNI, Host, and certificate validation. - Notes: Takes precedence over
bootstrapwhen both are configured.
upstreams[].outbound
- Type:
string; Required: no - Purpose: References a profile from
network.outbound.profilesto inject resolver and proxy defaults into this upstream. - Default: When omitted,
network.outbound.defaultis used. Without a default, the upstream keeps system resolution/direct dialing. - Override rules: local
dial_addrtakes precedence over resolver use; localbootstraptakes precedence over the outbound resolver; localsocks5takes precedence over the profile proxy. - Note: Profile proxying applies only to TCP, DoT, and DoH2. UDP, DoQ, and DoH3 upstreams ignore SOCKS5 proxy settings.
upstreams[].port
- Type:
integer; Required: no - Purpose: Override the protocol default port.
upstreams[].bootstrap
- Type:
string; Required: no - Purpose: Bootstrap resolver for domain-based upstreams.
- Notes: Must use an
IP:portaddress. With bootstrap enabled, OxiDNS resolves the upstream hostname through that resolver and caches it according to DNS TTL. Ignored whendial_addris also configured.
upstreams[].bootstrap_version
- Type:
integer; Required: no - Allowed values:
4,6 - Purpose: Force bootstrap resolution toward IPv4 or IPv6.
upstreams[].socks5
- Type:
string; Required: no - Purpose: SOCKS5 proxy for upstream connections.
- Supports:
host:portusername:password@host:port
- Notes: SOCKS5 applies only to TCP, DoT, and DoH2. UDP, DoQ, and DoH3 upstreams ignore this setting.
upstreams[].idle_timeout
- Type:
integer; Required: no; Default:10 - Unit: seconds
- Purpose: Idle pooled connection lifetime.
upstreams[].max_conns
- Type:
integer; Required: no; Default:64 - Purpose: Maximum pooled connections.
- Range:
1..4096
upstreams[].min_conns
- Type:
integer; Required: no; Default:0 - Purpose: Minimum warmed connections kept by the pool.
- Range:
0..4096, and it must not exceed the upstream's effectivemax_conns. - Notes: When omitted, connections remain lazy and are not pre-created when the pool is created.
upstreams[].insecure_skip_verify
- Type:
boolean; Required: no; Default:false - Purpose: Skip TLS certificate validation.
upstreams[].timeout
- Type:
duration; Required: no; Default:5s - Purpose: Per-upstream query timeout.
upstreams[].enable_pipeline
- Type:
boolean; Required: no; Default:false - Purpose: Enable pipelining for TCP or DoT.
- Notes:
tcp+pipeline://andtls+pipeline://force this on. - Recommendation: Before enabling it, run
oxidns probe upstream <addr>against the target upstream. For TCP/DoT, the probe forces concurrent queries through one connection so you can decide whether that upstream is safe for pipeline mode and catch timeouts, connection closes, response ID confusion, or crossed questions first. - Verdict: If the probe reports
supported, enabling pipeline is reasonable. If it reportsunsupported,unstable, orinconclusive, keep pipeline disabled or retest with lower concurrency / a longer timeout.
upstreams[].enable_http3
- Type:
boolean; Required: no; Default:false - Purpose: Use HTTP/3 for DoH.
upstreams[].so_mark
- Type:
integer; Required: no - Purpose: Linux
SO_MARK.
upstreams[].bind_to_device
- Type:
string; Required: no - Purpose: Linux
SO_BINDTODEVICE.
quick setup
- exec: "forward 1.1.1.1"
- exec: "forward 1.1.1.1 8.8.8.8"
- exec: "forward 1.1.1.1 short_circuit=true"
Quick setup supports the trailing flag forms short_circuit, short_circuit=true, and short_circuit=false.
Use the full plugin form for bootstrap, proxy, HTTP/3, pool settings, and other advanced options.
Behavior
- Single-upstream mode queries the configured upstream directly.
- Multi-upstream mode races queries from a randomized starting point and keeps the first successful answer.
- With
short_circuitenabled, a successful upstream response stops the remaining executor chain immediately.
Metrics
Exported through the global GET /api/metrics endpoint:
forward_query_totalforward_success_totalforward_error_totalforward_timeout_totalforward_latency_countforward_latency_sum_ms
Per-upstream series are also exported with an upstream label (the upstream tag, or its resolved address when no tag is configured):
forward_upstream_query_totalforward_upstream_success_totalforward_upstream_error_totalforward_upstream_timeout_totalforward_upstream_latency_countforward_upstream_latency_sum_ms
Typical Uses
- Standard forwarding
- Multi-upstream resilience
- Mixed-protocol upstream groups
- More upstreams are not automatically better. Keep upstream groups semantically clear.
cache
Purpose
Provides TTL-aware response caching with negative cache support and persistence.
Example Configuration
- tag: cache_main
type: cache
args:
# Maximum number of cached entries
size: 8192
# Stop the chain immediately when cache returns a response
short_circuit: true
# Serve stale responses briefly after original TTL expiry and refresh lazily
lazy_cache_ttl: 120
# Cache NXDOMAIN / NODATA responses too
cache_negative: true
# Upper bound for negative-cache TTL
max_negative_ttl: 300
# Fallback TTL when a negative response has no SOA
negative_ttl_without_soa: 60
# Upper bound for positive TTL
max_positive_ttl: 600
# Minimum positive TTL required for cache admission
min_positive_ttl: 4
# Exclude ECS from the cache key for a better hit ratio
ecs_in_key: false
# Persist cache contents to disk
dump_file: "./dns_cache.dump"
# Periodic dump interval in seconds
dump_interval: 600
Configuration Details
size
- Type:
integer; Required: no; Default:1024 - Purpose: Cache capacity.
lazy_cache_ttl
- Type:
duration; Required: no - Purpose: Enable lazy cache for successful positive responses.
- Behavior:
- The original response TTL still defines the fresh-hit window.
lazy_cache_ttldefines the stale reply TTL and keeps entries briefly available after freshness expires.- Stale hits trigger an asynchronous background refresh.
- This setting does not shorten the original fresh TTL.
dump_file
- Type:
string; Required: no - Purpose: Persistence dump file path.
dump_interval
- Type:
duration; Required: no; Default:600s - Purpose: Periodic dump interval.
short_circuit
- Type:
boolean; Required: no; Default:false - Purpose: Stop the chain when the cache produces a response.
- Notes:
- When set to
false, later executors still run even if cache has already populatedresponse. - To skip later
forwardstages on cache hits, handle it explicitly insequence, for example withhas_resporaccept.
- When set to
cache_negative
- Type:
boolean; Required: no; Default:true - Purpose: Cache negative responses.
max_negative_ttl
- Type:
duration; Required: no; Default:300s - Purpose: Cap negative-cache TTL.
negative_ttl_without_soa
- Type:
duration; Required: no; Default:60s - Purpose: Fallback TTL for negative answers without SOA.
max_positive_ttl
- Type:
duration; Required: no - Purpose: Cap positive-cache TTL.
min_positive_ttl
- Type:
duration; Required: no - Purpose: Require a minimum positive TTL before writing a response to cache.
- Notes: Positive responses whose effective cache TTL is lower than this value are not cached. The check runs after
max_positive_ttlcapping.
ecs_in_key
- Type:
boolean; Required: no; Default:false - Purpose: Include ECS information in the cache key.
quick setup
- exec: "cache"
- exec: "cache short_circuit=true"
- With no arguments, quick setup uses the default cache configuration.
- It currently supports the trailing flag forms
short_circuit,short_circuit=true, andshort_circuit=false. - Use the full plugin form for other advanced settings.
Behavior
- Reads from cache on the forward path and writes responses on the return path.
- Respects DNS TTL semantics instead of using a fixed timeout.
- Can persist cache contents through dump and load operations.
Plugin API
GET /plugins/<cache_tag>/entries- Reads cache entries with pagination. Supports
limit,cursor, andqname;qnameis a case-insensitive substring filter over the cache-key domain.
- Reads cache entries with pagination. Supports
GET /plugins/<cache_tag>/flushGET /plugins/<cache_tag>/dumpPOST /plugins/<cache_tag>/load_dump
Metrics
Exported through the global GET /api/metrics endpoint. The cache plugin does not expose a cache-specific stats/metrics endpoint.
cache_lookup_totalcache_hit_total{kind="fresh|stale"}cache_miss_totalcache_expired_totalcache_insert_totalcache_skip_total{reason="truncated|no_ttl|low_positive_ttl"}cache_lazy_refresh_total{result="started|success|failed"}cache_entry_count
Typical Uses
- Lower upstream latency
- Protect upstreams from repeated identical traffic
- Preserve warm cache state across restarts
- Decide carefully whether ECS should be part of the cache key. It improves correctness for ECS-aware policies but reduces hit ratio.
fallback
Purpose
Runs a primary executor first and falls back to a secondary executor when the primary is too slow or fails.
Example Configuration
- tag: fallback_main
type: fallback
args:
# Preferred path
primary: "forward_fast"
# Backup path
secondary: "forward_stable"
# Let the backup take over after 200 ms
threshold: 200
# Keep the backup running in parallel for lower tail latency
always_standby: true
Configuration Details
primary
- Type:
string; Required: yes - Purpose: Primary executor tag.
secondary
- Type:
string; Required: yes - Purpose: Secondary executor tag.
threshold
- Type:
integer; Required: no - Unit: milliseconds
- Purpose: Delay before the secondary is allowed to take over.
always_standby
- Type:
boolean; Required: no; Default:false - Purpose: Keep the secondary in standby for all requests rather than only after the threshold condition.
short_circuit
- Type:
boolean; Required: no; Default:false - Purpose: Stop the executor chain after fallback selects the winning response.
Behavior
- Provides controlled degradation instead of unconditional double-querying.
- Useful when one path is usually faster but another path is more complete or stable.
- With
short_circuitenabled, the winning branch writes its response and then immediately stops the remaining executor chain.
Metrics
Exported through the global GET /api/metrics endpoint:
fallback_primary_totalfallback_primary_error_totalfallback_secondary_total
Typical Uses
- Low-latency primary plus stable backup
- Tail-latency protection
- A too-aggressive threshold can turn the secondary into a routine dependency.
hosts
Purpose
Returns local static answers using host-style entries.
Example Configuration
- tag: hosts_main
type: hosts
args:
entries:
# Unprefixed rules default to full:
- "router.local 192.168.1.1"
# Exact-name rule
- "full:gateway.local 192.168.1.2"
# Suffix rule returning both IPv4 and IPv6
- "domain:svc.local 10.0.0.10 fd00::10"
# Keyword rule
- "keyword:nas 192.168.1.20"
# Regex rule
- "regexp:^api[0-9]+\\.corp\\.local$ 10.10.0.5"
files:
# Merge more hosts rules from files
- "/etc/oxidns/hosts.txt"
short_circuit: true
Configuration Details
entries
- Type:
array; Required: no; Default: empty array - Purpose: Defines inline hosts rules.
- Rule format:
<domain_rule> <ip1> <ip2> ...
files
- Type:
array; Required: no; Default: empty array - Purpose: Specifies the list of external hosts rule files.
short_circuit
- Type:
bool; Required: no; Default:false - Purpose: Stops the remaining executor chain after a local answer is generated.
Rule format:
<domain_rule> <ip1> <ip2> ...
Behavior
- Handles only
INclassA/AAAArequests with exactly one question. - Unprefixed rules default to
full:to match mosdnshosts. - Rule-family priority is fixed as
full -> domain -> regexp -> keyword. domain:uses the longest matching suffix.- Repeated patterns use last-write-wins semantics in load order: inline
entriesfirst, then each configured file line by line. - Positive local answers return same-family addresses with a fixed TTL of
10. - If the domain matches but the requested address family is missing, the plugin returns
NoError + empty answer + fake SOAinstead of passing through. - Non-matching queries pass through to subsequent execution.
- By default it keeps running the remaining chain after a local response; enable
short_circuitto stop immediately for both positive and empty local replies.
Metrics
Exported through the global GET /api/metrics endpoint:
hosts_hit_totalhosts_miss_total
Typical Uses
- Local service discovery
- Small fixed overrides
arbitrary
Purpose
Injects arbitrary DNS records from zone-style rule strings.
Example Configuration
- tag: arbitrary_main
type: arbitrary
args:
rules:
# TXT record
- "example.com. 60 IN TXT \"hello world\""
# MX record
- "mail.example.com. 300 IN MX 10 mx1.example.com."
# A / AAAA / CNAME / PTR records are also supported
- "www.example.com. 120 IN A 192.0.2.10"
- "www.example.com. 120 IN AAAA 2001:db8::10"
- "alias.example.com. 120 IN CNAME www.example.com."
- "10.2.0.192.in-addr.arpa. 300 IN PTR host.example.com."
files:
# Load more static records from files
- "/etc/oxidns/zone.txt"
short_circuit: false
Configuration Details
rules
- Type:
array; Required: no - Purpose: Inline record rules.
- Syntax:
- Each list item is parsed as an independent zone snippet.
- Supports
$ORIGIN,$TTL,$INCLUDE,$GENERATE, owner inheritance, TTL units, comments, quoted strings, and multiline()syntax. - Common record types are parsed directly, including
A,AAAA,CNAME,NS,PTR,DNAME,ANAME,MD,MF,MB,MG,MR,NSAPPTR,MX,RT,AFSDB,RP,MINFO,HINFO,TXT,SPF,AVC,RESINFO,SOA,SRV,NAPTR, andCAA. - Other record types can be loaded through RFC3597 generic syntax:
TYPE#### \# <len> <hex>. - Defaults TTL to
3600when omitted.
files
- Type:
array; Required: no - Purpose: External rule files.
- Syntax: Uses the same zone parser as
rules.
short_circuit
- Type:
bool; Required: no; Default:false - Purpose: Stop the remaining executor chain after setting a synthetic response.
- Notes: By default
arbitraryonly sets the response and lets the chain continue.
Behavior
- Produces fully synthetic answers.
- Matches exactly on
qname + qtype + qclass. - When a request carries multiple questions, all matched records are accumulated into one response.
- By default the executor only sets the response and keeps the remaining chain running.
- When
short_circuitis enabled it returnsStopafter a match. - Quick setup syntax is intentionally not supported.
- Useful when
hostsis too limited.
Typical Uses
- TXT test records
- Local authority-style data
- Keep rule files readable. Arbitrary records become hard to audit faster than
hostsentries. - This is still a static answer generator, not a full authoritative server with transfer or dynamic update support.
- The parser is broader than the zone parser used by mosdns
arbitrary, but matching remains an exact static lookup.
redirect
Purpose
Rewrites matching names toward different target names or answer destinations.
Example Configuration
- tag: redirect_main
type: redirect
args:
rules:
# Exact-name redirect
- "full:old.example.com new.example.net"
# Suffix redirect
- "domain:legacy.example.com modern.example.net"
# Keyword redirect
- "keyword:staging staging-gateway.example.net"
# Regex redirect
- "regexp:^api[0-9]+\\.legacy\\.example\\.com$ api-gateway.example.net"
# Unprefixed rules default to full:
- "old-static.example.com static.example.net"
files:
# Merge more redirect rules from files
- "/etc/oxidns/redirect.txt"
Configuration Details
rules
- Type:
array; Required: no; Default: empty array - Purpose: Defines inline redirect rules.
- Rule format:
<domain_rule> <target_name>
<domain_rule>supports:full:domain:keyword:regexp:- bare domains without a prefix, treated as exact
full:matches
files
- Type:
array; Required: no; Default: empty array - Purpose: Specifies the list of external redirect rule files.
- File format is the same as
rules, one rule per line. Empty lines and#comments are ignored.
Rule format:
full:old.example.com new.example.net
domain:legacy.example.com modern.example.net
keyword:staging staging-gateway.example.net
regexp:^api[0-9]+\.legacy\.example\.com$ api-gateway.example.net
old-static.example.com static.example.net
Behavior
- Only handles
INqueries. If there is no question or no matching rule, the executor passes through to the remaining chain. - If multiple rules match, the earliest loaded matching rule wins. Load order is
inline
rulesfirst, thenfilesin declaration order and line order. redirectdoes not resolve the target name by itself. Use it with a later executor such asforwardin asequenceso that executor can produce the real response for the rewritten target name.- Forward phase:
- Rewrites the request QUESTION NAME.
- Return phase:
- Restores the target name in the response question back to the original name.
- Prepends a
CNAME original -> targetrecord to the answers.
Common sequence usage:
- exec: "$redirect_main"
- exec: "$forward_main"
Typical Uses
- Point a unified entry domain to another set of records.
- Perform alias redirection for specific domains without changing client configuration.
- Put
redirectbeforeforwardin the usual case. If the remaining chain does not produce a response,redirectwill not synthesize target records by itself. - It is better suited for simple queries such as
A/AAAA/TXT. - Full semantic transparency is not guaranteed for complex records and some extension scenarios.
ecs_handler
Purpose
Controls EDNS Client Subnet forwarding or injection.
Example Configuration
- tag: ecs_main
type: ecs_handler
args:
# Strip client-supplied ECS first
forward: false
# Add ECS when the request has none
send: true
# IPv4 ECS prefix length
mask4: 24
# IPv6 ECS prefix length
mask6: 48
- tag: ecs_preset
type: ecs_handler
args:
# Preset ECS source
preset: "203.0.113.10"
# Fixed-source mode usually keeps these switches explicit
forward: false
send: true
mask4: 24
mask6: 48
Configuration Details
forward
- Type:
boolean; Required: no - Purpose: Preserve ECS from the client side.
send
- Type:
boolean; Required: no - Purpose: Send ECS to upstreams.
preset
- Type:
string; Required: no - Purpose: Use a preset ECS source.
mask4
- Type:
integer; Required: no - Purpose: IPv4 ECS mask.
mask6
- Type:
integer; Required: no - Purpose: IPv6 ECS mask.
quick setup
- exec: "ecs_handler 203.0.113.10/24"
Behavior
- Can preserve, synthesize, or normalize ECS before forwarding.
- Interacts with cache correctness if ECS is also part of the cache key.
Typical Uses
- Geo-sensitive upstream policies
- Client-network-aware answers
- Keep ECS handling and cache-key policy aligned.
forward_edns0opt
Purpose
Forwards selected EDNS0 options to upstreams.
Example Configuration
- tag: edns_forward
type: forward_edns0opt
args:
# Preserve only the selected EDNS0 option codes
codes: [10, 12]
Configuration Details
codes
- Type:
array; Required: yes - Purpose: EDNS0 option codes to preserve and forward.
quick setup
- exec: "forward_edns0opt 10,12"
Behavior
- Keeps only selected EDNS0 options instead of blindly forwarding everything.
Typical Uses
- Preserve specific client-side EDNS signaling needed by upstreams.
ttl
Purpose
Rewrites response TTL values.
Example Configuration
Full object form:
- tag: ttl_main
type: ttl
args:
# First force TTL to 300
fix: 300
# Then keep a lower bound
min: 60
# And cap the upper bound
max: 600
Configuration Details
fix
- Type:
duration; Required: no - Purpose: Force all TTLs to one fixed value.
min
- Type:
duration; Required: no - Purpose: Lower bound for TTLs.
max
- Type:
duration; Required: no - Purpose: Upper bound for TTLs.
quick setup
- exec: "ttl 300"
- exec: "ttl 60-600"
Behavior
- Adjusts TTLs on the response path.
- Can fix, clamp, or normalize TTLs.
Typical Uses
- Stabilize answer retention
- Avoid extreme upstream TTL values
ip_selector
ip_selector was first introduced in v1.2.0 and is currently in an experimental stage. Configuration fields, default values, probing strategies, and cache semantics may be adjusted in future releases based on real-world usage feedback. It is recommended to validate the plugin in non-critical environments first and monitor the release notes for related changes.
Purpose
Response IP selector. It only shapes A/AAAA records already present in response, using cached probe scores and bounded active probing to produce a stable order or a smaller answer set.
ip_selector does not race upstream DNS responses, suppress dual-stack families, or carry its own domain-rule system. Use forward / fallback for upstream racing, prefer_ipv4 / prefer_ipv6 for dual-stack policy, and sequence with matchers/providers for domain-specific routing.
Example Configuration
- tag: ip_select
type: ip_selector
args:
selection_mode: first_success
outbound: remote
probe_methods: ["tcp:443", "tcp:80"]
probe_stagger: 200
probe_timeout: 600
max_wait: 1000
top_n: 1
dnssec_policy: reorder_only
max_parallel_probes: 256
cache:
enabled: true
size: 4096
ttl: 3600
failure_ttl: 60
Recommended sequence placement:
- exec: $ip_select
- exec: $cache_main
- matches: !$has_resp
exec: $forward_main
- exec: accept
This lets cache store the original upstream response while ip_selector applies final response shaping on the return path.
Configuration Details
selection_mode
- Type:
string; Required: no; Default:first_success - Values:
first_success,best_within_budget,background - Purpose:
first_success: use the first candidate IP with a successful probe.best_within_budget: choose the lowest-latency IP withinmax_wait.background: return immediately using the original or cached order while refreshing probe scores in the background.
probe_methods
- Type:
array<string> | string; Required: no; Default:["tcp:443", "tcp:80"] - Values:
tcp:<port>,ping,none - Purpose: Probe methods used to score answer IPs.
pingis best-effort and fails open when unavailable.
outbound
- Type:
string; Required: no; Default:network.outbound.default - Purpose: References a
network.outbound.profilesentry so TCP probes can reuse the profile proxy. - Notes:
- Only
tcp:<port>probes use proxy settings;pingalways runs locally. - This field only uses the profile proxy. Target IPs are already known and are not resolved again.
- Only
socks5
- Type:
string; Required: no; Default: none - Purpose: Local SOCKS5 proxy override for TCP probes.
- Notes:
- Uses the same format as
upstreams[].socks5. - When both
outboundandsocks5are configured, localsocks5overrides the profile proxy. - Only
tcp:<port>probes use SOCKS5;pingalways runs locally.
- Uses the same format as
probe_stagger
- Type:
integer; Unit: milliseconds; Required: no; Default:200 - Purpose: Delay between launching different probe methods.
probe_timeout
- Type:
integer; Unit: milliseconds; Required: no; Default:600 - Purpose: Timeout for one probe attempt.
max_wait
- Type:
integer; Unit: milliseconds; Required: no; Default:1000 - Purpose: Maximum per-response wait budget for selection.
top_n
- Type:
integer; Required: no; Default:1 - Purpose: Keep the first N selected addresses. Use
0to reorder only without deleting records.
dnssec_policy
- Type:
string; Required: no; Default:reorder_only - Values:
reorder_only,skip - Purpose: When the request carries DO bit or the response contains RRSIGs covering A/AAAA, the default policy only reorders records.
skipleaves the response unchanged.
max_parallel_probes
- Type:
integer; Required: no; Default:256 - Purpose: Plugin-wide active probe concurrency limit.
cache
- Type:
object; Required: no - Fields:
enabled: enable probe score cache, defaulttrue.size: target cache capacity, default4096.ttl: successful score retention in seconds, default3600.failure_ttl: failed score retention in seconds, default60.
quick setup
- exec: "ip_selector"
- exec: "ip_selector best_within_budget tcp:443,tcp:80,ping"
ip_selector accepts only the OxiDNS-native names listed above and does not provide compatibility aliases.
Metrics
ip_selector_probe_total{method,result}ip_selector_probe_latency_count{method}ip_selector_probe_latency_sum_ms{method}ip_selector_selected_total{source="probe|cache|fallback"}ip_selector_cache_entriesip_selector_dropped_probe_total{reason="parallel_limit|inflight"}
prefer_ipv4 / prefer_ipv6
Purpose
Biases dual-stack results toward one address family.
Example Configuration
- tag: prefer_v4
type: prefer_ipv4
args:
# Cache whether the preferred family exists
cache: true
# Keep the preference cache for one hour
cache_ttl: 3600
Configuration Details
cache
- Type:
boolean; Required: no; Default:true - Purpose: Cache preference decisions.
cache_ttl
- Type:
integer; Required: no; Default:3600 - Unit: seconds
- Purpose: Retention for the preference cache.
quick setup
- exec: "prefer_ipv4"
- exec: "prefer_ipv6"
- Quick setup uses the default configuration:
cache: trueandcache_ttl: 3600. - Use the full plugin configuration when disabling the cache or changing its TTL.
Behavior
- Helps make A and AAAA selection more stable when both families exist.
- Preferred-family queries pass through normally and warm the preference cache when they return preferred-family answers.
- Non-preferred-family queries are blocked immediately when the cache says the preferred family exists; otherwise
prefer_ipv4/prefer_ipv6runs the downstream chain concurrently for the original query and a preferred-family probe.
Typical Uses
- Prefer the family that works better on a given network
- Reduce dual-stack instability
- Preference is not a substitute for fixing broken transport paths.
black_hole
Purpose
Generates local interception responses for matched DNS queries, covering every qtype.
Example Configuration
- tag: sinkhole
type: black_hole
args:
mode: custom
ips:
# Returned for A queries
- "0.0.0.0"
# Returned for AAAA queries
- "::"
short_circuit: true
- tag: block_nxdomain
type: black_hole
args:
mode: nxdomain
short_circuit: true
Configuration Details
mode
- Type:
string; Required: no; Default:nxdomainwhenipsis empty,customwhenipsis configured - Values:
nxdomain: returnsNXDOMAINand adds an SOA authority record for negative caching.nodata: returns an emptyNOERRORresponse and adds an SOA authority record for negative caching.null: returns0.0.0.0forA,::forAAAA, and NODATA for other qtypes.custom: returns matching address-family values fromipsforA/AAAA; missing families and other qtypes return NODATA.refused: returns an emptyREFUSEDresponse.
- Compatibility: legacy
ips+short_circuitconfig does not needmode; it is treated ascustom.
ips
- Type:
array; Required: no; Default: empty array - Purpose: Local synthetic addresses used by
custommode. - Operational impact:
- IPv4 addresses are used only for A responses.
- IPv6 addresses are used only for AAAA responses.
ipsis allowed only for implicit or explicitcustommode.
short_circuit
- Type:
bool; Required: no; Default:false - Purpose: Stops the remaining executor chain after an interception response is generated.
quick setup
- exec: "black_hole"
- exec: "black_hole nxdomain short_circuit=true"
- exec: "black_hole nodata"
- exec: "black_hole null"
- exec: "black_hole custom 0.0.0.0 :: short_circuit=true"
# legacy form, equivalent to custom
- exec: "black_hole 0.0.0.0 ::"
Behavior
- Plain
black_holereturnsNXDOMAIN. nxdomain,nodata, andrefusedcover every qtype.nullandcustomreturn addresses for A/AAAA and NODATA for other qtypes.- Requests without a question pass through unchanged.
- By default it keeps running the remaining chain after a match; enable
short_circuitto stop immediately.
Metrics
Exported through the global GET /api/metrics endpoint:
blackhole_block_total
Typical Uses
- Blocking ads, trackers, and malicious domains
- Returning NXDOMAIN / NODATA / REFUSED by policy
- Returning null or custom placeholder addresses
drop_resp
Purpose
Drops the current response.
Example Configuration
- tag: clear_response
type: drop_resp
# No standalone args; execution simply clears the current response
Configuration Details
No standalone configuration fields.
quick setup
- exec: "drop_resp"
Behavior
- Clears the existing response from context so later rules can continue.
Typical Uses
- Discard unwanted intermediate results
- Force a later branch to rebuild the answer
reverse_lookup
Purpose
Maintains a reverse IP-to-name cache and optionally handles PTR requests.
Example Configuration
- tag: reverse_lookup_main
type: reverse_lookup
args:
# Reverse-cache capacity
size: 65535
# Retention time for IP -> name mappings
ttl: 7200
# Answer PTR directly from the learned cache
handle_ptr: true
Configuration Details
size
- Type:
integer; Required: no - Purpose: Reverse cache capacity.
handle_ptr
- Type:
boolean; Required: no; Default:false - Purpose: Answer PTR requests from the reverse cache.
ttl
- Type:
duration; Required: no - Purpose: Reverse cache retention TTL.
Behavior
- Learns from successful responses.
- Can expose cached domain names for IP lookups and PTR handling.
Plugin API
GET /plugins/<tag>?ip=<ip_addr>
Typical Uses
- Debugging resolved destinations
- Supporting PTR-like introspection for learned answers
- This is an auxiliary index, not a replacement for authoritative PTR data.
learn_domain
learn_domain was first introduced in v1.2.0 and is currently in an experimental stage. Filtering rules, write trigger timing, and the integration interface with dynamic_domain_set may change in future releases. Before deploying in production, please evaluate the impact of learning rates and disk writes on the persistence layer.
Purpose
Observes qnames in the DNS pipeline and writes matching request names into a target dynamic_domain_set. It is a side-effect executor and never modifies the DNS request or response.
Example Configuration
- tag: learned_allow
type: dynamic_domain_set
args:
path: "/etc/oxidns/learned-allow.txt"
- tag: learn_allow
type: learn_domain
args:
provider: learned_allow
phase: after
questions: first
qtypes: ["A", "AAAA"]
success_only: true
answer_required: true
rule_kind: full
async: true
error_mode: continue
timeout: 1s
Configuration Details
provider
- Type:
string; Required: yes - Purpose: Target
dynamic_domain_setprovider tag. - Constraint: must reference
dynamic_domain_set, not a regulardomain_set.
phase
- Type:
string; Required: no; Default:after - Values:
before,after - Purpose:
before: learn from the request questions before downstream executors run.after: run downstream first, then decide whether to learn based on the response.
questions
- Type:
string; Required: no; Default:first - Values:
first,all - Purpose: Learns only the first question or all questions.
qtypes
- Type:
array; Required: no; Default:["A", "AAAA"] - Purpose: DNS query types eligible for learning.
success_only
- Type:
bool; Required: no; Default:true - Purpose: Only applies to
phase: after; requiresNOERROR.
answer_required
- Type:
bool; Required: no; Default:true - Purpose: Only applies to
phase: after; requires at least one answer record.
rule_kind
- Type:
string; Required: no; Default:full - Values:
full,domain - Purpose: Rule kind written into the target
dynamic_domain_set.
async
- Type:
bool; Required: no; Default:true - Purpose: Enqueues learned rules and continues immediately. When
false, waits for provider persistence.
error_mode
- Type:
string; Required: no; Default:continue - Values:
continue,stop,fail - Purpose: Controls execution behavior if learning fails.
timeout
- Type:
duration; Required: no; Default:1s - Purpose: Limits provider write waiting time when
async: false.
Behavior
- Learned domains are normalized to lowercase with trailing dots removed.
- The default output is
full:example.comto avoid broad accidental matches. - With the default
phase: after, only successfulA/AAAAresponses with answers are learned. phase: beforedoes not inspect response conditions.- v1 only learns the request question qname and does not learn CNAME targets.
Typical Uses
- Add successfully resolved domains to a dynamic allow list.
- Persist domains seen on a policy branch into a local file for later
qname $learned_allowor parentdomain_setmatching.
query_summary
Purpose
Records concise query summaries.
Example Configuration
- tag: summary_main
type: query_summary
args:
# Extra title so multiple summary points are easy to distinguish
msg: "main pipeline"
Configuration Details
msg
- Type:
string; Required: no - Purpose: Extra summary label.
quick setup
- exec: "query_summary main"
Behavior
- Emits compact logs or summaries for operator visibility.
Typical Uses
- Light observability on the main path
- Distinguish different branches
query_recorder
Purpose
Persists the entry request, the post-next response, and sequence execution-path events into a recorder-owned SQLite database, then exposes history, aggregate stats, and an SSE stream.
Example Configuration
- tag: query_recorder_main
type: query_recorder
args:
# SQLite path for this recorder. Different recorders should use different paths.
path: "./data/query-recorder-main.sqlite"
# Hot-path enqueue buffer size
queue_size: 8192
# Batch size per SQLite flush
batch_size: 256
# Background flush interval in milliseconds
flush_interval_ms: 200
# Number of recent records kept in memory for SSE tail replay
memory_tail: 1024
# Retention window in days; minimum 1
retention_days: 7
# Cleanup interval in hours; minimum 1
cleanup_interval_hours: 1
# Maximum concurrent SQLite readers; minimum 1
reader_concurrency: 2
Configuration Details
path
- Type:
string; Required: yes - Purpose: SQLite path for this recorder.
queue_size
- Type:
integer; Required: no; Default:8192 - Purpose: Bounded queue size between the request path and the writer thread.
batch_size
- Type:
integer; Required: no; Default:256 - Purpose: Number of records flushed per SQLite batch.
flush_interval_ms
- Type:
integer; Required: no; Default:200 - Purpose: Maximum batch flush interval in milliseconds.
memory_tail
- Type:
integer; Required: no; Default:1024 - Purpose: Size of the in-memory tail used by
stream?tail=n.
retention_days
- Type:
integer; Required: no; Default:7; Minimum:1 - Purpose: Record retention window. Expired rows are deleted by the cleanup task.
cleanup_interval_hours
- Type:
integer; Required: no; Default:1; Minimum:1 - Purpose: Cleanup task cadence.
reader_concurrency
- Type:
integer; Required: no; Default:2; Minimum:1 - Purpose: Limits concurrent SQLite readers used by WebUI / API history and stats queries, so read bursts on large recorder databases do not occupy too many blocking threads or too much memory.
Behavior
- This is a pure executor observer and does not change server finalization logic.
- It captures a structured snapshot of the entry request, enables
DnsContext.execution_path, runsnext, and commits immediately afternextreturns. - Successful runs store the current response. Failed runs store
errorand an empty response shape. - Request and response payloads are not stored as wire blobs. Question, RR, and EDNS fields are extracted into JSON text columns.
- The
client_ipfield comes from the transport source address seen by OxiDNS; if a local forwarding chain such as systemd-resolved, dnsmasq, AdGuardHome, dae, or clash sits in front, rows may show only127.0.0.1. - Each recorder uses versioned tables under the same prefix:
qr_<safe_tag>_<fnv64hex>_v1_recordsqr_<safe_tag>_<fnv64hex>_v1_stepsqr_<safe_tag>_<fnv64hex>_v1_questionsqr_<safe_tag>_<fnv64hex>_v1_meta
recordscontains only the fixed schema fields for structured snapshots.stepsstoressequencepath events for path analysis and hit-rate reporting.questionsis a derived index table fromquestions_jsonfor faster qname/qtype queries.metarecords completed derived-table migrations.- Every recorder owns its own bounded queue, SQLite connection, writer thread, tail buffer, and SSE broadcaster.
- v1 assumes different recorders use different
pathvalues. There is no cross-recorder writer sharing or path coordination.
Data Shape
questions_jsonis always a question array, for example:
[
{ "name": "www.example.com.", "qtype": "A", "qclass": "IN" }
]
answers_json,authorities_json,additionals_json, andsignature_jsonare RR arrays, for example:
[
{
"name": "www.example.com.",
"class": "IN",
"ttl": 300,
"rr_type": "A",
"payload_kind": "A",
"payload_text": "192.0.2.1",
"payload": { "ip": "192.0.2.1" }
}
]
req_edns_jsonandresp_edns_jsonare EDNS objects orNULL.- The
v1suffix in the table name is the schema version. Future upgrades add new versioned tables rather than altering the existing ones in place.
API
GET /plugins/<tag>/records- Returns record rows ordered by
created_at_msdescending. - Query parameters:
cursor=<created_at_ms>:<id>limit=<n>, default100, max500since_ms=<unix_ms>until_ms=<unix_ms>qname=<text>, substring match against request question namesclient_ip=<text>, substring match against the client IP stringqtype=<type>/rcode=<rcode>/status=all|error|has_response|no_response
- Returns record rows ordered by
GET /plugins/<tag>/records/<id>- Returns one full record plus
steps.
- Returns one full record plus
DELETE /plugins/<tag>/records- Clears all history rows and
stepsfor the current recorder, and clears the in-memory tail. - The operation first flushes the background writer queue and returns
cleared_recordsfor the number of deleted main-table rows.
- Clears all history rows and
GET /plugins/<tag>/stats/plugins- Returns hit stats grouped by
matcher / executor / builtin. - Supports
since_ms,until_ms,kind=matcher|executor|builtin|all, and the record filters.
- Returns hit stats grouped by
GET /plugins/<tag>/stats/top_clients/stats/top_qnames- Returns client IP or QNAME rankings.
- Supports
limit=<n>, default20; the backend no longer enforces a200cap. - Supports the same time range and filter parameters as records.
GET /plugins/<tag>/stats/qtype/stats/rcode- Returns QTYPE or RCODE distribution.
- Supports the same time range and filter parameters as records.
GET /plugins/<tag>/stats/latency- Returns latency summary values, histogram buckets, and slow-query rankings.
- Supports
slow_limit=<n>orlimit=<n>, default20; the backend no longer enforces a200cap.
GET /plugins/<tag>/stats/timeseries- Returns query trends aggregated by minute or hour.
- Supports
bucket=minute|hourandbuckets=<n>(default60, maximum720).
GET /plugins/<tag>/stream- Streams newly written records over SSE.
- Supports
tail=<n>to replay the in-memory tail. - Clients should send
Accept: text/event-streamand tolerate heartbeat frames, error events, empty payloads, and brief disconnects.
Typical Uses
- Persistent audit and troubleshooting trails
sequencepath analysis and plugin hit-rate reporting- Real-time query log feeds for dashboards or control planes
- Place the recorder close to the entry point when the full main-path trace is required.
- If an earlier branch short-circuits before the recorder, that request will not be recorded.
- If
nextfails and the server later emits a fallback response, the database still reflects the plugin's point of view:errorplus an empty response. - If the management API is disabled, the recorder still writes SQLite data but does not expose query or SSE routes.
- To preserve real client IPs, point clients directly at OxiDNS or configure a trusted
src_ip_headerfor HTTP/DoH reverse-proxy deployments.
metrics_collector
Purpose
Collects Prometheus metrics for query handling.
Example Configuration
- tag: metrics_main
type: metrics_collector
args:
# Collector label exported through /api/metrics
name: "main"
Configuration Details
name
- Type:
string; Required: no - Purpose: Metrics label namespace.
quick setup
- exec: "metrics_collector main"
Behavior
- Exposes query counters, inflight counts, and latency metrics through the management API.
API
GET /api/metrics- Prometheus text format. This is the single global endpoint, and built-in metrics from other plugins are exported through the same route.
Typical Uses
- Prometheus integration
- Observe multiple policy entry points separately
debug_print
Purpose
Prints a debug message.
Example Configuration
- tag: debug_main
type: debug_print
args:
# Log title; defaults to "debug print" when omitted
msg: "before forward"
Configuration Details
msg
- Type:
string; Required: yes - Purpose: Message content.
quick setup
- exec: "debug_print cache branch"
Typical Uses
- Temporary debugging
- Reading sequence branches during development
sleep
Purpose
Sleeps for a bounded duration inside the chain.
Example Configuration
- tag: sleep_100ms
type: sleep
args:
# Add 100 ms of async delay
duration: 100
Configuration Details
duration
- Type:
duration; Required: yes - Purpose: Sleep duration.
quick setup
- exec: "sleep 100"
Typical Uses
- Testing
- Timing experiments
http_request
Purpose
Sends callback requests to external http/https services. It can trigger before the current DNS flow enters downstream executors or after downstream execution completes, which makes it suitable for webhooks, audit pipelines, alerts, and external integrations.
Example Configuration
- tag: webhook_notify_after
type: http_request
args:
method: POST
url: "https://hooks.example.com/dns"
phase: after
async: true
timeout: 5s
headers:
X-Client-IP: "${client_ip}"
X-Qname: "${qname}"
query_params:
source: "oxidns"
qname: "${qname}"
json:
qname: "${qname}"
client_ip: "${client_ip}"
rcode: "${rcode_name}"
resp_ip: "${resp_ip}"
Config Fields
args.method
- Type:
string; Required: yes - Purpose: Selects the HTTP method such as
GET,POST,PUT,PATCH, orDELETE.
args.url
- Type:
string; Required: yes - Purpose: The target URL.
- Notes: Supports
${key}placeholder interpolation. The rendered URL must use eitherhttporhttps.
args.phase
- Type:
string; Required: no; Default:after - Allowed values:
before,after - Purpose: Controls whether the request is sent before or after downstream executors run.
args.async
- Type:
boolean; Required: no; Default:true - Purpose: Chooses bounded background dispatch or inline synchronous dispatch.
args.timeout
- Type:
string; Required: no; Default:5s - Purpose: Caps the total time budget for one HTTP call.
- Supported units:
ms,s,m,h,d
args.error_mode
- Type:
string; Required: no; Default:continue - Allowed values:
continue: only log the failure and keep runningstop: returnStopon failurefail: return an executor error immediately
args.headers
- Type:
map<string,string>; Required: no; Default: empty - Purpose: Adds HTTP request headers.
- Notes: Header values support
${key}placeholder interpolation.
args.query_params
- Type:
map<string,string>; Required: no; Default: empty - Purpose: Appends additional query parameters to the rendered URL.
- Notes: Values support
${key}placeholder interpolation and are combined with any query already present inargs.url.
args.body
- Type:
string; Required: no - Purpose: Sends a raw string body.
- Notes: Supports
${key}placeholder interpolation and can be paired withargs.content_type.
args.json
- Type:
object | array; Required: no - Purpose: Sends a JSON body.
- Notes: Automatically sets
Content-Type: application/json. Every string leaf supports${key}interpolation while non-string values are preserved as-is.
args.form
- Type:
map<string,string>; Required: no - Purpose: Sends an
application/x-www-form-urlencodedbody. - Notes: Values support
${key}interpolation and the plugin automatically sets the matchingContent-Type.
args.content_type
- Type:
string; Required: no - Purpose: Sets
Content-Typefor rawargs.body. - Notes: This helper can only be used with
args.body, not withargs.jsonorargs.form.
args.outbound
- Type:
string; Required: no - Purpose: References a profile from
network.outbound.profilesto control the resolver and proxy used by this HTTP request. - Notes: When omitted, the request uses
network.outbound.default. Ifargs.socks5is also set,socks5only overrides the proxy while the resolver still comes from the outbound profile.
args.socks5
- Type:
string; Required: no - Purpose: Routes requests through a SOCKS5 proxy.
- Notes: Uses the same format as
upstream[].socks5, includinghost:port,username:password@host:port, and bracketed IPv6.
args.insecure_skip_verify
- Type:
boolean; Required: no; Default:false - Purpose: Skips HTTPS certificate validation.
args.max_redirects
- Type:
integer; Required: no; Default:5 - Purpose: Limits how many redirects are followed.
args.queue_size
- Type:
integer; Required: no; Default:256 - Purpose: Sets the bounded queue capacity used by async mode.
Available Placeholders
- Same as
script:qname,qtype,qtype_name,qclass,qclass_name - Source fields:
client_ip,client_port,server_name,url_path - Runtime fields:
marks,has_resp - Response fields:
rcode,rcode_name,resp_ip - Cron metadata:
cron_plugin_tag,cron_job_name,cron_trigger_kind,cron_scheduled_at_unix_ms
Behavior
- With
phase: before, the HTTP request is dispatched first and the downstream executor chain runs afterward. - With
phase: after, the downstream executor chain runs first and the HTTP request is dispatched against the resulting context. async: trueuses a bounded background queue. Queue insertion failures are handled according toerror_mode.async: falsewaits for the HTTP call on the current request path.- Only terminal
2xxresponses are treated as success.3xxresponses are followed up tomax_redirects. - The plugin drains and discards the HTTP response body so connections remain reusable, but it does not write that body back into
DnsContext. - If
Content-Typeis already set explicitly inargs.headers, the plugin does not overwrite it.
args.body,args.json, andargs.formare mutually exclusive.- This is a side-effect executor. In v1 it cannot rewrite DNS requests, responses, marks, or attrs based on the HTTP result.
- v1 does not support multipart uploads or quick setup syntax.
- Configure two separate
http_requestplugin instances when both trigger moments are required.
script
Purpose
Runs an explicitly configured external command and injects a stable subset of the current DnsContext into command arguments or environment variables.
Example
- tag: script_notify
type: script
args:
command: "bash"
args:
- "/etc/oxidns/notify.sh"
- "${qname}"
- "${client_ip}"
env:
FDNS_QNAME: "${qname}"
FDNS_CLIENT_IP: "${client_ip}"
FDNS_MARKS: "${marks}"
timeout: "5s"
error_mode: continue
max_output_bytes: 4096
Config Fields
args.command
- Type:
string; Required: yes - Purpose: Command path or program name to execute.
- Notes: This field is never templated.
args.args
- Type:
array<string>; Required: no; Default: empty - Purpose: Positional command arguments.
- Notes: Each item supports
${key}interpolation.
args.env
- Type:
map<string,string>; Required: no; Default: empty - Purpose: Extra child-process environment variables.
- Notes: Values support
${key}interpolation and overlay the inherited process environment.
args.cwd
- Type:
string; Required: no; Default: none - Purpose: Working directory for the child process.
args.timeout
- Type:
string; Required: no; Default:5s - Purpose: Maximum execution time for one script run.
- Supported units:
ms,s,m,h,d
args.error_mode
- Type:
string; Required: no; Default:continue - Allowed values:
continue: log failure or timeout, then returnNextstop: log failure or timeout, then returnStopfail: return an executor error immediately
args.max_output_bytes
- Type:
usize; Required: no; Default:4096 - Purpose: Maximum captured stdout/stderr length before truncation.
Available Placeholders
- Request fields:
qname,qtype,qtype_name,qclass,qclass_name - Source fields:
client_ip,client_port,server_name,url_path - Runtime fields:
marks,has_resp - Response fields:
rcode,rcode_name,resp_ip - Cron metadata:
cron_plugin_tag,cron_job_name,cron_trigger_kind,cron_scheduled_at_unix_ms
Behavior
- The plugin does not mutate DNS requests or responses.
- It runs only the explicit configured command and does not wrap it with
sh -c,cmd /c, or similar shell shortcuts. - Arguments and environment variables are rendered from the current
DnsContexton each execution. - On timeout the child process is terminated, then
error_modedecides how the sequence continues.
- v1 does not support quick setup syntax.
commandmust not be empty.- Only the documented built-in placeholders are accepted; unknown placeholders fail plugin initialization.
- This is a side-effect executor. It does not support writing attrs, marks, or DNS responses back through stdout.
ipset
Purpose
Writes response IPs into Linux ipset through the embedded Rust netlink backend, without requiring the runtime ipset command.
Example Configuration
- tag: ipset_main
type: ipset
args:
# ipset used for A answers
set_name4: "oxidns_v4"
# ipset used for AAAA answers
set_name6: "oxidns_v6"
# Aggregate IPv4 writes to /24 prefixes
mask4: 24
# Aggregate IPv6 writes to /64 prefixes
mask6: 64
Configuration Details
set_name4
- Type:
string; Required: no; Default: none - Purpose: Specifies the ipset name used to write IPv4 addresses.
set_name6
- Type:
string; Required: no; Default: none - Purpose: Specifies the ipset name used to write IPv6 addresses.
mask4
- Type:
integer; Required: no; Default:24 - Purpose: Specifies the prefix length used when writing IPv4 addresses into ipset.
mask6
- Type:
integer; Required: no; Default:32 - Purpose: Specifies the prefix length used when writing IPv6 addresses into ipset.
quick setup
- exec: "ipset oxidns_v4,inet,24 oxidns_v6,inet6,64"
Format:
<set_name>,<family>,<mask>
Here, family is inet or inet6.
Behavior
- Extracts unique A/AAAA addresses from the answer section.
- Writes them into the corresponding set according to the address family.
- Delivers them to the background writer through a non-blocking queue.
Typical Uses
- Policy routing
- Firewall integration
- On non-Linux platforms it degrades to a no-op.
- When the queue is full, the side effect is dropped and does not block the DNS hot path.
nftset
Purpose
Writes response IPs into nftables sets through the embedded Rust netlink backend, without requiring the runtime nft command.
Example Configuration
Structured form:
- tag: nftset_main
type: nftset
args:
ipv4:
# IPv4 target uses the ip family
table_family: "ip"
table_name: "mangle"
set_name: "dns_v4"
mask: 24
ipv6:
# IPv6 target uses the ip6 family
table_family: "ip6"
table_name: "mangle"
set_name: "dns_v6"
mask: 64
Compatibility form:
- tag: nftset_legacy
type: nftset
args:
# Compatibility fields, useful when migrating old configs
table_family4: "ip"
table_name4: "mangle"
set_name4: "dns_v4"
mask4: 24
table_family6: "ip6"
table_name6: "mangle"
set_name6: "dns_v6"
mask6: 64
Configuration Details
ipv4
- Type:
object; Required: no; Default: none - Purpose: Defines the target IPv4 nftables set.
- Child fields:
table_familytable_nameset_namemask
ipv6
- Type:
object; Required: no; Default: none - Purpose: Defines the target IPv6 nftables set.
- Child fields:
table_familytable_nameset_namemask
table_family4 / table_family6
- Type:
string; Required: no; Default: none - Purpose: In the compatibility form, defines the nftables table family for IPv4 / IPv6 respectively.
table_name4 / table_name6
- Type:
string; Required: no; Default: none - Purpose: In the compatibility form, defines the nftables table name for IPv4 / IPv6 respectively.
set_name4 / set_name6
- Type:
string; Required: no; Default: none - Purpose: In the compatibility form, defines the set name for IPv4 / IPv6 respectively.
mask4 / mask6
- Type:
integer; Required: no; Default:24formask4,48formask6 - Purpose: In the compatibility form, defines the prefix length for IPv4 / IPv6 respectively.
quick setup
- exec: "nftset ip,mangle,dns_v4,ipv4_addr,24 ip6,mangle,dns_v6,ipv6_addr,64"
Format:
<family>,<table>,<set>,<type>,<mask>
Behavior
- Extracts A/AAAA addresses.
- Writes nftables interval elements according to the prefix.
- Also uses the background writer so that the hot path remains non-blocking.
Typical Uses
- nftables-driven routing or firewall policies
- On non-Linux platforms it degrades to a no-op.
ros_address_list
Purpose
Writes response IPs into MikroTik RouterOS address lists, with dynamic entries, persistent entries, startup-time file loading, and shutdown cleanup.
Example Configuration
- tag: ros_address_list_main
type: ros_address_list
args:
# RouterOS API endpoint
address: "172.16.1.1:8728"
# API username
username: "api-user"
# API password
password: "secret"
# RouterOS API connection timeout, in seconds
connect_timeout: 5
# RouterOS API command send timeout, in seconds
send_timeout: 5
# RouterOS API response receive timeout, in seconds; increase only for unavoidable slow legacy scans
receive_timeout: 30
# Use asynchronous writes to avoid blocking the DNS hot path
async: true
# Address list used for A records
address_list4: "oxidns_ipv4"
# Address list used for AAAA records
address_list6: "oxidns_ipv6"
# Prefix for comments on OxiDNS-managed entries
comment_prefix: "oxidns"
# Lower bound for dynamic-entry TTL
min_ttl: 60
# Upper bound for dynamic-entry TTL
max_ttl: 3600
# Force dynamic entries to 300 seconds; use 0 to omit RouterOS timeout
fixed_ttl: 300
# Remove owned entries when the plugin shuts down
cleanup_on_shutdown: true
persistent:
ips:
# Persistent single IP
- "1.1.1.1"
# Persistent IPv4 CIDR
- "100.64.1.0/24"
# Persistent IPv6 CIDR
- "2001:db8::/64"
files:
# Load more persistent items from files
- "/etc/oxidns/persistent_ips.txt"
Configuration Details
address
- Type:
string; Required: yes - Purpose: RouterOS API endpoint.
username
- Type:
string; Required: yes - Purpose: RouterOS username.
password
- Type:
string; Required: yes - Purpose: RouterOS password.
connect_timeout
- Type:
u64; Required: no; Default:5 - Purpose: Maximum wait time, in seconds, for establishing a RouterOS API connection.
- Notes: Must be greater than
0. Increase it if the management network or RouterOS API occasionally responds slowly.
send_timeout
- Type:
u64; Required: no; Default:5 - Purpose: Maximum wait time, in seconds, for sending one RouterOS API command.
- Notes: Must be greater than
0. The default is usually sufficient.
receive_timeout
- Type:
u64; Required: no; Default:5 - Purpose: Maximum wait time, in seconds, for the next chunk of RouterOS API response data.
- Tuning: Prefer a dedicated, size-controlled
address-listfor OxiDNS. Avoid connecting the plugin to an existing large shared list. Increase this value, for example to30or60, only when slow legacy list queries or a slow RouterOS management plane cannot be avoided.
async
- Type:
bool; Required: no; Default:true - Purpose: Controls whether address writes use asynchronous mode. When enabled, the DNS response path only submits tasks, and a background manager completes the RouterOS interaction.
address_list4
- Type:
string; Required: no - Purpose: IPv4 address-list name.
address_list6
- Type:
string; Required: no - Purpose: IPv6 address-list name.
comment_prefix
- Type:
string; Required: no - Purpose: Prefix for generated RouterOS comments.
persistent
- Type:
object; Required: no; Default: none - Purpose: Defines the static address set that should be kept for the long term. This part does not depend on DNS responses to trigger. After plugin startup it can be synchronized to RouterOS directly and then kept consistent by the reconcile loop.
persistent.ips
- Type:
array<string>; Required: no; Default: empty - Purpose: Declares persistent IPs or CIDR ranges inline.
persistent.files
- Type:
array<string>; Required: no; Default: empty - Purpose: Loads the persistent address set from external files at plugin startup.
- Notes: These files are read once during initialization. To apply later file changes, reload the plugin or the application.
min_ttl
- Type:
u64; Required: no; Default:60 - Purpose: Defines the minimum TTL allowed for dynamic address entries.
max_ttl
- Type:
u64; Required: no; Default:3600 - Purpose: Defines the maximum TTL allowed for dynamic address entries.
fixed_ttl
- Type:
u64; Required: no; Default: none - Purpose: Specifies one fixed TTL for all dynamically written entries. If it is set to
0, dynamic entries will not set a RouterOStimeout.
cleanup_on_shutdown
- Type:
bool; Required: no; Default:true - Purpose: Controls whether entries managed by the plugin are removed when the plugin exits.
Behavior
- The plugin itself does not modify DNS responses.
- Startup does not wait for RouterOS address-list scans to finish. Initial connectivity checks and persistent-entry reconciliation run in the background manager; failures are logged without blocking DNS startup or dynamic observations.
- It only passes through during the forward phase.
- During the return phase:
- Extracts A/AAAA records from
NOERRORresponses. - Deduplicates them and keeps the largest TTL.
- Submits them to the background manager according to async or sync mode.
- Extracts A/AAAA records from
- The manager is responsible for:
- Background initial connectivity verification
- Dynamic entry refresh
- Persistent entry consistency maintenance
- Cleanup on shutdown
Typical Uses
- DNS-driven policy routing on RouterOS
- Maintaining dynamic destination groups from DNS answers
- At least one of
address_list4oraddress_list6is required. - Prefer dedicated OxiDNS
address-listtargets and avoid large shared lists to reduce RouterOS management-plane scan cost. comment_prefixand the plugintagmust not contain;or=.- Synchronous mode does not change the DNS response itself. Even if the RouterOS write fails, the DNS result is still preserved.
upgrade
Purpose
Runs the OxiDNS upgrade flow from the executor pipeline. It is suitable for maintenance tasks triggered by cron, sequence, or another executor.
Example Configuration
- tag: upgrade_auto
type: upgrade
args:
repository: svenshi/oxidns
asset: auto
bundle: auto
github_token: ghp_xxx
cache_dir: ./upgrade/cache
backup_dir: ./upgrade/backups
webui_dir: ./webui
skip_webui: false
no_restart: false
force: false
cleanup: true
timeout: 30s
outbound: remote
socks5: 127.0.0.1:1080
insecure_skip_verify: false
Options
force- Boolean. Default:
false. - Continue downloading, verifying, and replacing even when the selected release is not newer than the current version.
- Boolean. Default:
cleanup- Boolean. Default:
true. - Cleans
cache_dirandbackup_dirafter a successful upgrade.
- Boolean. Default:
repository- GitHub repository. Default:
svenshi/oxidns.
- GitHub repository. Default:
asset- Release asset name.
autoselects the archive for the current platform and build bundle. - An explicit asset has the highest priority and skips
bundleinference.
- Release asset name.
bundle- Type:
auto | full | standard | minimal. - Default:
auto. - Selects the release build bundle when
asset: autois used.fulluses the legacy asset name;standard/minimaluse slim asset names with a bundle prefix.
- Type:
github_token- GitHub personal access token for API requests, used to raise the rate limit or access private repositories.
- The value is sent as a Bearer token on GitHub API requests.
cache_dir/backup_dir- Download cache and pre-replacement backup directories.
webui_dir- Path. Default:
./webui. - Directory where the WebUI static assets are installed during an upgrade; keep it aligned with
api.http.webui.root.
- Path. Default:
skip_webui- Boolean. Default:
false. - When
true, only the binary is replaced and the WebUI directory upgrade is skipped.
- Boolean. Default:
no_restart- Boolean. Default:
false. - When
true, a successful upgrade does not trigger an automatic restart. - The default
falserestarts automatically after a successful upgrade: CLIapplyrestarts the installed service through the system service manager, while the executor requests a graceful in-process restart through the application control channel so the new binary is loaded.
- Boolean. Default:
timeout,outbound,socks5,insecure_skip_verify- Same meaning as the CLI
upgradeflags. outboundreferences a profile fromnetwork.outbound.profiles. The legacysocks5field remains supported and overrides the profile proxy.
- Same meaning as the CLI
Behavior
- The executor always returns
ExecStep::Next. - The plugin only runs the
applyaction. It does not providecheckordownloadmodes. - By default it updates only when a newer version is available.
force: trueforces the update. - By default it cleans cache and backup files after a successful upgrade. Set
cleanup: falseto keep rollback files. - The upgrade downloads the archive and verifies SHA256 with the GitHub release asset
digestfield. asset: autoselects the archive frombundle;bundle: autofollows the current binary's build bundle, and custom builds must setbundleorassetexplicitly.- On Unix it unpacks
.tar.gz, backs up the current binary, and replaces it. Windows currently does not support plugin upgrades. - By default, after replacing the binary it backs up and installs the archive's
webui/directory intowebui_dir; setskip_webui: trueto skip it. If the archive has nowebui/(older releases), the WebUI upgrade is skipped without affecting the binary upgrade result.
quick setup
- exec: upgrade
- exec: upgrade force
- exec: upgrade force=false
- exec: upgrade bundle=standard
- Empty arguments run apply with the default configuration.
- Only
forceandforce=true|falseare supported. - Other settings use defaults. Use full
argsconfiguration to override the repository, directories, restart mode, or proxy. modeis not supported; the plugin always applies upgrades.
download
Purpose
Downloads one or more http/https files into a local directory and overwrites the target files only after the new content is fully written.
Example Configuration
- tag: rules_download
type: download
args:
timeout: 30s
outbound: remote
socks5: "127.0.0.1:1080"
downloads:
- url: "https://example.com/geosite.dat"
dir: "/etc/oxidns"
- url: "https://example.com/geoip.dat"
dir: "/etc/oxidns"
filename: "geoip.dat"
Quick Setup
- exec: "download https://example.com/rules.txt /etc/oxidns"
Behavior
downloadsrun sequentially in declaration order.- A failed item only emits a warning log and does not stop later items.
- Missing target directories are created automatically.
- Files are written to a temporary path first and then moved into place.
- When
outboundis set, downloads use the resolver and proxy defined by the referencednetwork.outbound.profilesentry. - When
socks5is set, all download connections are routed through that SOCKS5 proxy using the same format asupstream[].socks5. - By default, OxiDNS checks target files during startup and downloads any missing ones before other plugins initialize. A bootstrap failure aborts startup.
- Set
startup_if_missing: falseto disable that bootstrap behavior.
- Only
httpandhttpsare supported. - When
outboundis omitted,network.outbound.defaultis used. If bothoutboundandsocks5are set,socks5overrides the profile proxy while preserving the profile resolver. socks5acceptshost:portandusername:password@host:port; bracket IPv6 addresses such as"[::1]:1080"are supported too.startup_if_missingonly fills missing files; it does not overwrite existing targets on every startup.- When used inside a normal
sequence, the download time is paid directly by that request. - Overwriting a local file does not apply automatically. For file-backed providers that only need to pick up new data, prefer chaining
reload_provider; ifconfig.yaml, dependency topology, or the plugin list changed too, usereload.
Recommended Pairing
- tag: rules_refresh
type: sequence
args:
- exec: "$rules_download"
- exec: "$reload_rules"
- tag: rules_download
type: download
args:
downloads:
- url: "https://example.com/geosite.dat"
dir: "/etc/oxidns"
- tag: provider_geosite
type: geosite
args:
file: "/etc/oxidns/geosite.dat"
- tag: reload_rules
type: reload_provider
args:
- "$provider_geosite"
Subscription Refresh Example
This example fits the common flow of “remote subscription -> scheduled download -> targeted provider refresh”:
plugins:
# 1. Run the subscription refresh flow periodically
- tag: subscription_cron
type: cron
args:
timezone: "Asia/Shanghai"
jobs:
- name: refresh_rule_subscriptions
interval: 6h
executors:
- "$subscription_refresh"
# 2. Chain download and targeted provider reload with a sequence
- tag: subscription_refresh
type: sequence
args:
- exec: "$subscription_download"
- exec: "$reload_rule_providers"
# 3. Download remote subscription files
- tag: subscription_download
type: download
args:
timeout: 60s
startup_if_missing: true
downloads:
- url: "https://example.com/geosite.dat"
dir: "/etc/oxidns/rules"
filename: "geosite.dat"
- url: "https://example.com/geoip.dat"
dir: "/etc/oxidns/rules"
filename: "geoip.dat"
# 4. Reload only the affected providers after download completes
- tag: reload_rule_providers
type: reload_provider
args:
- "$provider_geosite"
- "$provider_geoip"
# 5. These providers re-read the local files after reload
- tag: provider_geosite
type: geosite
args:
file: "/etc/oxidns/rules/geosite.dat"
- tag: provider_geoip
type: geoip
args:
file: "/etc/oxidns/rules/geoip.dat"
downloadwrites the subscription content to local files.reload_providerrefreshes only the affected provider snapshots without rebuilding unrelated plugins.startup_if_missing: trueis useful for first-time deployment when files may not exist yet.- If the subscription source requires a proxy, set a SOCKS5 proxy on
subscription_download.args.socks5. - To avoid overwriting existing files at startup, keep the default behavior and only bootstrap missing files.
- If the update also changes
config.yaml, provider topology, or the plugin list, use a fullreloadinstead.
Full Reload Still Fits Config Changes
- tag: config_refresh
type: sequence
args:
- exec: "$subscription_download"
- exec: "$reload_all"
- tag: reload_all
type: reload
reload_provider
Purpose
Reloads one or more providers in place by tag, rebuilding their internal snapshots with the same startup configuration without triggering a full application reload.
Example Configuration
- tag: reload_rule_providers
type: reload_provider
args:
- "$geosite_cn"
- "$geoip_cn"
Quick Setup
- exec: "reload_provider $geosite_cn"
Behavior
- Providers are reloaded sequentially in the order declared in
args. - The semantics are the same as calling
POST /plugins/<provider_tag>/reloadfor each referenced provider. - Once every provider reload succeeds, the executor returns
Next. - Only provider-local data is refreshed; tags, dependencies, and other plugin configuration are unchanged.
Typical Uses
- Refreshing only the affected
domain_set,ip_set,geosite,geoip, oradguard_ruleproviders afterdownload. - Reducing the blast radius and cost of a full application reload in background maintenance flows.
argsonly accepts provider references such as"$geoip_cn"; inline rules and file references are rejected.- If the update changes
config.yaml, provider topology, the plugin list, or other non-provider structures,reloadis still required. - Running this on a live request path may trigger file reads and recompilation, so it is usually a better fit for background
cronor maintenancesequenceflows.
reload
Purpose
Triggers the same application-level full reload as the management API POST /reload, reloading the active configuration and rebuilding all plugins.
Example Configuration
- tag: reload_all
type: reload
Quick Setup
- exec: "reload"
Behavior
- Execution submits a reload request to the application control layer.
- The semantics are the same as the management API
POST /reload. - Once the reload request is accepted, the executor returns
Next. - This is a full application reload. Reloading selected plugin tags is not supported.
Typical Uses
- Pairing with
downloadin acronjob so refreshed rule files take effect immediately. - Triggering a full configuration reload from a dedicated background
sequence.
- It must run inside a normal OxiDNS process with application control context attached.
- Execution fails when another reload is already
pendingorin_progress. - Using it in a live request
sequencetriggers a full application reload and is usually not appropriate for latency-sensitive request paths.
cron
Purpose
Schedules a list of executors in the background. It does not participate in the live DNS request path and starts running only after plugin initialization.
Example Configuration
- tag: cron_jobs
type: cron
args:
timezone: "Asia/Shanghai"
jobs:
- name: refresh_sets
interval: 5m
executors:
- "$seq_refresh"
- "debug_print cron refresh"
- name: nightly_cleanup
schedule: "15 3 * * *"
executors:
- "sleep 2s"
- "$seq_cleanup"
Configuration Details
args.jobs
- Type:
array; Required: yes - Purpose: Defines one or more background jobs.
- Runtime impact:
- The array cannot be empty.
- Each job maintains its own trigger state and overlap protection.
args.timezone
- Type:
string; Required: no - Default: system local time zone
- Purpose: Overrides the time zone used by all
schedulejobs in thiscronplugin. - Notes:
- Only affects
schedule. - When omitted, OxiDNS uses the system local time zone and falls back to
UTCif unavailable. - Use IANA names such as
Asia/Shanghai,UTC, orAmerica/Los_Angeles.
- Only affects
args.jobs[].name
- Type:
string; Required: yes - Purpose: Job name used in logs and runtime metadata.
- Runtime impact:
- Must be unique within the same
cronplugin.
- Must be unique within the same
args.jobs[].schedule
- Type:
string; Required: exactly one ofscheduleorinterval - Purpose: Schedule a job with a standard 5-field cron expression.
- Notes:
- Only
minute hour day month day-of-weekis supported. - Second-level cron expressions are not supported.
- Next runs are computed in
args.timezoneor the system local time zone.
- Only
args.jobs[].interval
- Type:
string; Required: exactly one ofscheduleorinterval - Purpose: Schedule a job with a fixed interval.
- Supports:
5m1h1d
- Runtime impact:
- Minimum interval is
1m. - The first run happens after one full interval elapses.
- Minimum interval is
args.jobs[].executors
- Type:
array; Required: yes - Purpose: Ordered list of executors to run for the job.
- Supports:
$tagexplicit executor references- bare
tagreferences - quick-setup expressions such as
debug_print cron refresh
- Runtime impact:
- The array cannot be empty.
- Later executors still run even if an earlier executor returns
Stop, produces a response, or fails.
Behavior
scheduleandintervalare mutually exclusive.- If a job is still running when the next trigger arrives, that trigger is skipped and not replayed later.
- Jobs run with an empty
DnsContext, so this plugin is best suited for side-effect executors or dedicated backgroundsequencechains. cronitself cannot be executed inside a normal requestsequence.
Typical Uses
- Periodic side-effect tasks.
- Scheduling a dedicated background
sequence. - Providing a common trigger surface for future executors such as
reload.
- A
cronjob cannot reference anothercronexecutor. - Executors that require a real DNS request usually do not make sense in an empty background context.