Global Configuration
This page covers YAML loading rules shared by every plugin and the OxiDNS top-level configuration. See the Plugin Reference for plugin-specific fields.
Before Starting
OxiDNS uses YAML configuration. For day-to-day editing, it is easiest to understand the file as six top-level parts:
runtime:
worker_threads: 4
api:
http: "127.0.0.1:9088"
log:
level: info
file: ./oxidns.log
network:
outbound:
default: direct
profiles:
direct:
resolver: system
proxy: none
include: []
plugins:
- tag: seq_main
type: sequence
args:
- exec: "forward 1.1.1.1"
Where:
runtime- Runtime parameters.
api- Management API settings.
log- Log output settings.
network- Shared outbound networking settings, such as resolver and proxy choices for HTTP downloads, upgrade checks, and webhook requests.
include- Load plugin definitions from other configuration files.
plugins- All plugin instance definitions. OxiDNS composes the full DNS pipeline from plugins.
After editing a config, validate it before starting:
oxidns check -c config.yaml
If the config uses relative paths and the runtime working directory is not the config directory, pass the working directory explicitly. -d is the single base for all runtime relative paths, including logs, SQLite files, rule files, and api.http.webui.root; paths do not become relative to /etc/oxidns just because the config file lives there:
oxidns check -c /etc/oxidns/config.yaml -d /var/lib/oxidns
In the Debian default layout, the config file lives at /etc/oxidns/config.yaml, while runtime-relative resources live under /var/lib/oxidns.
When the plugin composition is still undecided, start from Common Scenarios, then return to this page for field details.
Plugin tag rules
plugins[].tag is the globally unique machine identifier for a plugin instance and is used directly in management API paths: /api/plugins/{tag}/.... A tag must meet all of these rules:
- It is 1 to 64 ASCII characters long.
- It uses only ASCII letters, digits,
_,-, and.. - A
.may only separate non-empty name segments; every segment starts and ends with a letter or digit. qs.exec.,qs.match., andqs.cron.are reserved Quick Setup prefixes and cannot be used in user configuration.
For example, cache_main, cache.cn, and prod.cache-01 are valid. ., .., cache..cn, _cache, cache-, 国内缓存, and cache/main are invalid.
Before upgrading from an older version, run oxidns check -c config.yaml with the new binary. When renaming a historical tag, update every $tag, jump/goto, plugin dependency, and other reference to it. OxiDNS does not rename tags automatically because that could silently change request-processing behavior.
Environment Variable Substitution
During startup, oxidns check, management API validation, and validation before saving a config, OxiDNS first parses the YAML into a data structure and then expands ${VAR} placeholders inside string scalars. The config.yaml file itself is not rewritten, so the WebUI still reads and saves the original placeholders.
Supported syntax:
| Syntax | Behavior |
|---|---|
${VAR} | Use the value of process environment variable VAR; fail if it is undefined |
${VAR:-default} | Use default when VAR is undefined or an empty string |
${env:VAR} | Explicitly read process environment variable VAR; useful when the name conflicts with a runtime placeholder |
${env:VAR:-default} | Explicitly read process environment variable VAR; use default when it is undefined or empty |
$${...} | Emit a literal ${...} |
Runtime placeholders used by executors such as script and http_request are preserved until request execution, so values like ${qname}, ${client_ip}, and ${resp_ip} are not treated as process environment variables during config loading. Use the explicit form, such as ${env:qname}, if you really need to read an environment variable with the same name.
Undefined variables fail fast, and the error includes the variable name and the YAML path of the offending scalar (for example plugins[0].args.password) so empty passwords or certificate paths do not silently pass validation.
Example:
api:
http:
listen: ${API_LISTEN:-0.0.0.0:8080}
ssl:
cert: ${API_TLS_CERT}
key: ${API_TLS_KEY}
auth:
type: basic
username: ${ADMIN_USER}
password: ${ADMIN_PASS}
Because substitution happens after YAML parsing, an environment value may contain any characters — *, &, :, #, ', ", \, newlines, even binary bytes — without breaking the config syntax. You do not need to manually quote values that contain special characters. When the entire scalar is exactly one placeholder (e.g. timeout: ${CACHE_TTL}), the expanded value is re-parsed once against the YAML 1.2 scalar rules, so number / boolean / null-shaped environment values still match numeric / boolean / null fields; everywhere else the value lands as a plain string. include paths support placeholders too:
include:
- ${OXIDNS_CONF_DIR}/plugins/common.yaml
Top-Level Fields
include
# []string, load plugin settings from other configuration files.
include:
- ./plugins/common.yaml
- ./plugins/server.yaml
Field notes:
include- Loads only
pluginsfrom included files. It does not merge includedruntime,api, orlogsettings. - Merge order is include-first: recursively load each
includein array order, then append the current file'splugins. - Relative paths are resolved from the directory of the configuration file that declares the
include. - Includes may recurse up to 8 levels.
- All merged plugin
tagvalues must still be globally unique.
- Loads only
runtime
runtime:
worker_threads: 4
Field notes:
worker_threads- Meaning: Number of Tokio multi-thread runtime workers.
- Default: Uses system available parallelism when omitted.
- Constraint: Must not be
0.
log
log:
level: info
file: ./oxidns.log
rotation:
type: daily
max_files: 7
Field notes:
level- Allowed values:
offtracedebuginfowarnerror - Default:
info
- Allowed values:
file- Meaning: Optional log file path.
- If omitted, logs go only to stdout.
- When configured, OxiDNS writes to both stdout and the log file.
- Log files are written as UTF-8 plain text without terminal ANSI color escape codes.
rotation- Meaning: Log file rotation policy.
- Default:
never
rotation supports the following forms:
type: nevertype: minutely- Rotate every minute.
type: hourly- Rotate every hour.
type: daily- Rotate every day.
type: weekly- Rotate every week.
- Optional
max_filescontrols how many rotated files are retained;0disables automatic cleanup.
network
network.outbound centralizes outbound policy for internal HTTP clients and upstreams. When omitted, behavior stays compatible: HTTP clients use system DNS with direct connections, and upstreams keep their own settings.
network:
outbound:
default: direct
profiles:
direct:
resolver: system
proxy: none
remote:
resolver:
nameservers:
- addr: "1.1.1.1:53"
- addr: "tls://dns.google:853"
dial_addr: 8.8.8.8
- addr: "https://cloudflare-dns.com/dns-query"
dial_addr: 1.1.1.1
ip_version: 4
timeout: 5s
proxy: none
proxy:
socks5: 127.0.0.1:1080
Field notes:
outbound.default- Meaning: Which profile HTTP clients and upstreams use when they do not set
outboundexplicitly. - Default: none; without a default profile, OxiDNS uses system DNS + direct connections.
- Constraint: If set, it must reference an existing entry in
profiles. - Note: The default profile proxy is applied strictly to upstreams. Startup fails if a default SOCKS5 proxy is applied to UDP, DoQ, or DoH3 upstreams, because those connection models do not support profile proxying.
- Meaning: Which profile HTTP clients and upstreams use when they do not set
outbound.profiles.<name>.resolversystem: Use system DNS. HTTP clients perform this lookup asynchronously so it does not block runtime worker threads.nameservers: Resolve target names through configured DNS nameservers. Supportsudp://,tcp://,tls://,https://,doh://,h3://,quic://, anddoq://; no scheme defaults to UDP.- Protocol features: UDP/TCP are always available. DoT requires
resolver-dot, DoH requiresresolver-doh, DoQ requiresresolver-doq, and DoH3 requiresresolver-doh3. Legacyupstream-*features still enable the shared DNS client dependencies for existing build scripts, but newnetwork.outbound.resolver.nameserversconfigs should enableresolver-*explicitly. ip_version: Optional,4queries A records and6queries AAAA records. When omitted, IPv4 is used.timeout: Optional resolver query timeout. Defaults to5s.proxy: Optional.noneconnects nameservers directly;profilelets TCP/DoT/DoH nameservers reuse this profile's SOCKS5 proxy. UDP/DoQ/DoH3 nameservers do not support SOCKS5.- Domain-based nameservers must set
dial_addr; the hostname inaddris kept for SNI/certificate validation anddial_addris used for the actual connection.
outbound.profiles.<name>.proxynoneordirect: Connect directly.socks5: Connect through a SOCKS5 proxy. The format is the same as upstreamsocks5.
download, upgrade, and http_request can reference a profile with args.outbound: remote. The legacy socks5 field remains supported. When both outbound and socks5 are set on the same plugin, socks5 overrides the profile proxy while the resolver still comes from the outbound profile. forward upstreams use network.outbound.default when outbound is omitted; they can also set outbound: remote to select another profile. Local upstream dial_addr, bootstrap, and socks5 fields override profile-injected values.
api
api.http supports two forms.
Shorthand:
api:
http: "127.0.0.1:9088"
Expanded form:
api:
http:
listen: "127.0.0.1:9443"
ssl:
cert: "/etc/oxidns/api.crt"
key: "/etc/oxidns/api.key"
client_ca: "/etc/oxidns/client-ca.crt"
require_client_cert: true
auth:
type: basic
username: "admin"
password: "secret"
webui:
root: "/etc/oxidns/webui"
index: "index.html"
Field notes:
http.listen- API listen address. Supports
ip:port,[ipv6]:port, and:port. :portbinds as dual-stack[::]:port; use0.0.0.0:portfor IPv4-only.
- API listen address. Supports
http.ssl.cert- API certificate file.
http.ssl.key- API private key file.
http.ssl.client_ca- Optional client certificate CA.
http.ssl.require_client_cert- Whether mutual TLS is required.
http.auth- Currently supports
basic. - See the Management API chapter for the Basic Auth header encoding rules.
- Currently supports
http.cors.allowed_origins- Optional WebUI/API cross-origin allowlist; when omitted, it is inferred from
http.listen. 0.0.0.0and[::]automatically allow any origin; a specific IP automatically allows any WebUI port on the same host.- When configured explicitly, entries are matched exactly against the browser's
Origin. - Use
"*"to allow any origin, but not for credentialed browser requests.
- Optional WebUI/API cross-origin allowlist; when omitted, it is inferred from
http.webui.root- Optional WebUI static file directory. When enabled, the WebUI is mounted at
/and the management API is available under/api/*. - Relative paths resolve against
-d/--working-dir; with the Debian service default-d /var/lib/oxidns,root: "./webui"means/var/lib/oxidns/webui. - See WebUI Deployment for build steps, publish directories, and standalone nginx deployment.
- Optional WebUI static file directory. When enabled, the WebUI is mounted at
http.webui.index- Optional index file name. Defaults to
index.html.
- Optional index file name. Defaults to
Validation rules:
listenmust not be empty.certandkeymust be configured together.require_client_cert: truerequiresclient_ca.basic.usernameandbasic.passwordmust both be non-empty.webui.rootmust not be empty.webui.index, when configured, must not be empty.
plugins
Each plugin definition uses the same outer structure:
- tag: cache_main
type: cache
args:
size: 4096
General rules:
tag- Unique plugin instance identifier.
- Must not be empty.
- Must be unique across the whole config.
type- Plugin type name.
- Must match a registered plugin factory.
args- Plugin parameters.
- Different plugins accept different shapes: object, string, array, or null.