Full Reference Config
This page shows one commented JSON example with every supported configuration section and every supported option.
Comments are supported in real keen-pbr config files, so you can use this example directly as a starting point.
List names, outbound tags, and DNS server tags must match
^[a-z][a-z0-9_]*$ and must be at most 24 characters.config.json
{
// Global daemon settings.
// All fields in this section are optional.
"daemon": {
// Path to the PID file.
// Default: no PID file is written when omitted.
"pid_file": "/var/run/keen-pbr.pid",
// Directory used for remote-list cache files and cache metadata.
// Default: (shown below)
"cache_dir": "/var/cache/keen-pbr",
// Firewall backend selection.
// Supported values: "auto", "iptables", "nftables".
// Default: (shown below)
"firewall_backend": "auto",
// Skip packets that already have an fwmark before keen-pbr touches them.
// Default: (shown below)
"skip_marked_packets": true,
// Default strict routing behavior for interface outbounds.
// Default: (shown below)
"strict_enforcement": false,
// Maximum allowed size for downloaded remote content such as URL-backed lists.
// Default: (shown below)
"max_file_size_bytes": 8388608,
// Maximum stdout bytes captured per firewall verification command.
// Use 0 for unlimited capture.
// Default: (shown below)
"firewall_verify_max_bytes": 262144
},
// Embedded HTTP API and Web UI settings.
// WARNING: This section is NOT available in keen-pbr-headless package
"api": {
// Enable or disable the HTTP API / Web UI.
// Default: false.
"enabled": true,
// Listen address for the API server.
// Default: (shown below)
"listen": "0.0.0.0:12121"
},
// All supported outbound types.
// Tags are referenced by route rules, DNS server detours, and list detours.
"outbounds": [
{
// "interface" sends traffic through a specific network interface.
"type": "interface",
// Unique outbound tag.
// Default: no default, required.
"tag": "vpn",
// Egress interface name.
// Default: no default, required for type="interface".
"interface": "wg0",
// Optional IPv4 gateway for the interface outbound.
// Default: null
"gateway": "10.8.0.1",
// Optional IPv6 gateway for the interface outbound.
// Default: null
"gateway6": "2001:db8::1",
// Per-outbound strict-enforcement override.
// Overrides daemon.strict_enforcement when set.
// Default: inherit daemon.strict_enforcement.
"strict_enforcement": true
},
{
// Another interface outbound example, often used as the normal WAN path.
"type": "interface",
"tag": "wan",
"interface": "eth0",
// Set the gateway only if you are sure it will not change in the future
// Better way is to create an outbound with table=254 which corresponds to default (main) routing table
"gateway": "172.12.33.1"
},
{
// "table" reuses an existing kernel routing table.
"type": "table",
// Unique outbound tag.
"tag": "wan_as_table",
// Existing Linux routing table ID.
// Default: no default, required for type="table".
"table": 254
},
{
// "blackhole" drops matching traffic.
"type": "blackhole",
// Unique outbound tag.
"tag": "block"
},
{
// "ignore" lets matching traffic bypass keen-pbr handling
// and continue with the system's normal routing.
"type": "ignore",
// Unique outbound tag.
"tag": "direct"
},
{
// "urltest" probes several candidate outbounds and selects one automatically.
"type": "urltest",
// Unique outbound tag.
"tag": "auto_select",
// Probe URL used for latency / reachability checks.
// Default: no default, required for type="urltest".
"url": "https://www.gstatic.com/generate_204",
// Probe interval in milliseconds.
// Default: (shown below)
"interval_ms": 180000,
// Timeout for each individual probe attempt in milliseconds.
// Default: (shown below)
"probe_timeout_ms": 5000,
// Do not switch if the new candidate is only slightly better than the current one.
// Default: (shown below)
"tolerance_ms": 100,
// Compatibility field for older configs.
// Urltest always appends terminal IPv4/IPv6 unreachable routes as a kill-switch.
// This setting currently has no additional effect for urltest outbounds.
"strict_enforcement": true,
// Ordered outbound groups.
// Default: no default, required for type="urltest".
// Lower weight is preferred before higher weight.
"outbound_groups": [
{
// Relative priority of this group.
// Default: (shown below)
"weight": 1,
// Candidate outbound tags inside this group.
// Supported child types: interface, table, blackhole.
"outbounds": ["vpn", "custom_table"]
},
{
// If the first group is unhealthy, keen-pbr can fall back to this one.
"weight": 2,
"outbounds": ["wan", "block"]
}
],
// Probe retry behavior.
"retry": {
// Retry attempts before a probe is treated as failed.
// Default: (shown below)
"attempts": 3,
// Delay between retries in milliseconds.
// Default: (shown below)
"interval_ms": 1000
},
// Circuit breaker settings for unstable outbounds.
"circuit_breaker": {
// Consecutive failures before the circuit opens.
// Default: (shown below)
"failure_threshold": 5,
// Consecutive successes needed to close the circuit again.
// Default: (shown below)
"success_threshold": 2,
// Cooldown before moving from open to half-open.
// Default: (shown below)
"timeout_ms": 30000,
// Max probe requests allowed while half-open.
// Default: (shown below)
"half_open_max_requests": 1
}
}
],
// All supported list styles.
// Each list must provide at least one of: url, domains, ip_cidrs, file.
"lists": {
"inline_domains": {
// Inline domains.
// Domains match the domain itself and its subdomains.
// Wildcards like "*.example.com" are accepted, but not required. Behavior will be the same as if you enter "example.com"
"domains": ["example.com", "othersite.net"],
// How long dnsmasq-resolved IPs for these domains stay in the dynamic set.
// 0 means no timeout.
// Should be higher than dnsmasq max-cache-ttl option.
// NOTE: dnsmasq max-cache-ttl is in seconds, but ttl_ms here is in milliseconds. So if your max-cache-ttl=300, I would recommend you to set ttl_ms at least to (max-cache-ttl * 1000 + 30min) = 2100000 (35 min)
// Default: value shown below (24 hours).
"ttl_ms": 86400000
},
"inline_ips": {
// Inline IPv4/IPv6 addresses or CIDRs.
"ip_cidrs": [
"93.184.216.34",
"10.0.0.0/8",
"2001:db8::1",
"2001:db8::/32"
]
// No need to set ttl_ms if your list doesn't contain any domains
// "ttl_ms": 0
},
"remote_list": {
// Remote list URL.
"url": "https://raw.githubusercontent.com/v2fly/domain-list-community/refs/heads/master/data/apple",
// Optional outbound used to download this list.
// Supported detour targets are routable outbounds such as interface, table, or urltest.
// Default: null (use the system's normal routing)
"detour": "auto_select",
// How long dnsmasq-resolved IPs for these domains stay in the dynamic set.
// 0 means no timeout.
// Should be higher than dnsmasq max-cache-ttl option.
// NOTE: dnsmasq max-cache-ttl is in seconds, but ttl_ms here is in milliseconds. So if your max-cache-ttl=300, I would recommend you to set ttl_ms at least to (max-cache-ttl * 1000 + 30min) = 2100000 (35 min)
// Default: value shown below (24 hours).
"ttl_ms": 86400000
},
"local_file_list": {
// Local list file path.
"file": "/etc/keen-pbr/local.lst",
// How long dnsmasq-resolved IPs for these domains stay in the dynamic set.
// 0 means no timeout.
// Should be higher than dnsmasq max-cache-ttl option.
// NOTE: dnsmasq max-cache-ttl is in seconds, but ttl_ms here is in milliseconds. So if your max-cache-ttl=300, I would recommend you to set ttl_ms at least to (max-cache-ttl * 1000 + 30min) = 2100000 (35 min)
// Default: value shown below (24 hours).
"ttl_ms": 86400000
},
"mixed_sources": {
// keen-pbr currently supports combining multiple sources in one list.
// This is supported, but separate lists are usually easier to maintain.
"domains": ["intranet.example"],
"ip_cidrs": ["192.168.50.0/24"],
"file": "/etc/keen-pbr/mixed.lst",
"url": "https://example.com/mixed.lst",
"detour": "vpn",
"ttl_ms": 86400000
}
},
// DNS configuration.
// dns.system_resolver is required for daemon runtime.
"dns": {
// Resolver used for runtime integration and TXT health checks.
// Default: no default, required by the running daemon.
"system_resolver": {
"address": "127.0.0.1"
},
// Optional built-in DNS probe server used by the Web UI and troubleshooting tools.
// You can test if your PC has correct DNS configuration by running the following command:
// > nslookup check.keen.pbr
// This command should return answer_ipv4 IP address (127.0.0.88), what means that your PC DNS configuration is correct and keen-pbr can see your DNS requests
"dns_test_server": {
// IPv4 listen address in host:port form.
// Default: no default, required when dns_test_server is present.
"listen": "127.0.0.88:12153",
// IPv4 A-record answer returned by the probe server.
// Default: value shown below if omitted from this example.
"answer_ipv4": "127.0.0.88"
},
// All supported DNS server styles.
"servers": [
{
// Plain static DNS server with no detour.
"tag": "google_dns",
// Supported values: "static" (default) or "keenetic".
// Default: value shown below when omitted.
"type": "static",
// Address for static DNS servers.
// Default: no default, required for type="static".
"address": "8.8.8.8"
},
{
// Static DNS server reached through an interface outbound.
"tag": "vpn_dns",
"address": "10.8.0.1:5353",
// Optional outbound used to contact this DNS server.
// Supported detour targets: interface, table, urltest.
// Not allowed: blackhole, ignore.
// Default: use the system's normal routing.
"detour": "vpn"
},
{
// Static DNS server reached through a urltest outbound.
"tag": "auto_dns",
"address": "[2606:4700:4700::1111]:53",
"detour": "auto_select"
},
{
// Keenetic DNS source that reuses the router's built-in DNS settings via RCI.
// This is the easiest way to configure DoT/DoH on Keenetic. Just configure DoT/DoH in your router and add DNS with Keenetic type to the keen-pbr.
// WARNING: This option is supported only on Keenetic and Netcraze routers
"tag": "keenetic_dns",
"type": "keenetic"
}
],
// Domain-to-DNS-server rules.
"rules": [
{
// Whether this DNS rule is active.
// Default: true when omitted or set to null.
"enabled": true,
// Lists whose domains should be resolved by this server.
"list": ["inline_domains", "remote_list"],
// DNS server tag to use.
"server": "vpn_dns",
// Allow answers that resolve to private/local IP ranges.
// Default: (shown below)
"allow_domain_rebinding": false
},
{
// Example rule for local services that intentionally resolve to RFC1918 addresses.
"list": ["mixed_sources"],
"server": "keenetic_dns",
"allow_domain_rebinding": true
},
{
// Example of a disabled DNS rule kept for later use.
"enabled": false,
"list": ["inline_domains"],
"server": "table_dns"
}
],
// Upstream DNS servers used when no DNS rule matches.
// Default: no upstream servers.
// WARNING: if you don't provide at least one DNS server here, your Internet connectivity may fail.
"fallback": ["google_dns", "auto_dns", "keenetic_dns"]
},
// Firewall mark allocation.
// This section is optional.
"fwmark": {
// First fwmark assigned to routable outbounds as a hex string.
// Default: (shown below)
"start": "0x00010000",
// Fwmark bitmask as a hex string.
// Must contain one or more consecutive F nibbles.
// Default: (shown below)
"mask": "0x00FF0000"
},
// Policy-routing table allocation.
// This section is optional.
"iproute": {
// First routing table ID used for auto-allocated outbound tables.
// Default: (shown below)
// Avoid reserved IDs such as 128 and 250-260.
"table_start": 150
},
// Route-processing rules.
"route": {
// Optional ingress interface filter.
// If omitted or empty, keen-pbr processes packets from any interface.
// Default: no filter.
"inbound_interfaces": ["br0", "wg-lan"],
"rules": [
{
// Basic list-based routing rule.
// Default for enabled: true when omitted or set to null.
"list": ["inline_domains", "remote_list"],
"outbound": "auto_select"
},
{
// Route inline IPs through an existing routing table.
"list": ["inline_ips"],
"outbound": "custom_table"
},
{
// Full filter example using proto, source/destination address, and destination port.
"enabled": true,
// Match traffic only if dest domain/IP is in the list
"list": ["mixed_sources"],
// Match traffic only if protocol is TCP
// Possible values: null (for any protocol), tcp, udp, tcp/udp
"proto": "tcp",
// Match traffic only if source IP matches
// Supported: single IP, CIDR
"src_addr": "192.168.10.0/24,192.168.20.0/24",
// Match traffic only if dest IP matches
// Supported: single IP, CIDR
"dest_addr": "203.0.113.0/24",
// Match traffic only if source port matches
// Supported: single port, multiple port comma-separated, range
"src_port": "1024-65535",
// Match traffic only if dest IP matches
// Supported: single port, multiple port comma-separated, range
"dest_port": "443,8443",
// Route all matched traffic to this outbound
"outbound": "vpn"
},
{
// Rules may omit "list" if another condition is present.
// This example blocks DNS to any destination except the trusted resolver subnet.
"proto": "udp",
"dest_port": "53",
"dest_addr": "!10.10.0.0/16",
"outbound": "block"
},
{
// Example "ignore" rule used as an exception before broader VPN rules.
"src_addr": "192.168.1.0/24",
"dest_addr": "192.168.0.0/16",
"outbound": "direct"
},
{
// IPv6 example: route HTTPS traffic to a documentation subnet via VPN.
"proto": "tcp",
"src_addr": "2001:db8:10::/64",
"dest_addr": "2001:db8:203::/48",
"dest_port": "443",
"outbound": "vpn"
},
{
// Example disabled rule kept for later use.
"enabled": false,
"list": ["inline_domains"],
"proto": "tcp/udp",
"dest_port": "!80,443",
"outbound": "wan"
}
]
},
// Automatic refresh for URL-backed lists.
// This section is optional.
"lists_autoupdate": {
// Enable or disable periodic refresh.
// Default: false.
"enabled": true,
// Standard 5-field cron expression.
// Required when enabled=true.
// No default value.
"cron": "0 4 * * 0"
}
}Notes
dns.servers[].detoursupportsinterface,table, andurltestoutbounds, but notblackholeorignore.lists[].detouris useful when a remote list should be downloaded through a VPN or other non-default path.route.rules[]must include at least one matching condition:list,src_port,dest_port,src_addr, ordest_addr.dns.rules[].allow_domain_rebindingis mainly for internal domains that intentionally resolve to private IP ranges.