Configuration¶
The Device Register service is configured by a single YAML file. On
the shipped RPM the systemd unit passes
/etc/pdns-pf-device-register.yaml to the binary — note the flat
pdns-pf-device-register.yaml form, not the nested
/etc/pdns-pf/<svc>.yml convention some other Platform Filter services
use. Two heavily-commented example templates ship alongside it:
/etc/pdns-pf-device-register-consumer.example.yaml— a NATS split-consumer reference./etc/pdns-pf-device-register-producer.example.yaml— a NATS split-producer reference.
Environment variables in the YAML are always expanded before parsing:
$VAR and ${VAR} references get replaced with their values from the
service's environment. This is the recommended way to inject secrets
(Redis passwords, the Postgres connection string) from a systemd
drop-in rather than hard-coding them in the file. There is no CLI flag
to turn this off.
Every config option is documented on this page. Use your browser's in-page search (Ctrl/⌘-F) to jump straight to the field you care about.
Top-level structure¶
The YAML has nine top-level sections. Which ones you need to populate depends on which mode the instance runs in — combined, split-producer, or split-consumer.
| Parameter | Type | Default | Description |
|---|---|---|---|
udp |
UDP |
{} |
UDP listener for register messages from the Recursor. Used in combined and split-producer mode; leave empty in split-consumer mode. |
http |
HTTP |
{} |
Status page, Prometheus /metrics and pprof webserver. |
redis |
Redis |
{workers: 10} |
Storage Redis where accepted devices are written. Used in combined and split-consumer mode. |
nats |
NATS |
{} |
NATS transport for split-mode deployments. Leaving both split-mode-* booleans off selects combined mode. |
dstore |
DStore |
{} |
Optional protobuf-over-TCP fan-out to dstore_alert. |
database |
Database |
{connection-string: "user=postgres dbname=postgres", workers: 10} |
Postgres connection and worker pool. Used in combined and split-consumer mode unless kafka.kafka_only is set. |
kafka |
Kafka |
see Kafka | Optional Kafka sender. Can replace Postgres entirely (kafka_only: true) or run alongside it. |
blocking |
Blocking |
{block-time: 1m, block-max-devices: 10, block-time-window: 10s} |
Per-user rate limiting and blocklist thresholds. |
log |
Log |
{format: logfmt} |
Logging format and level. |
Deprecation policy¶
None. Every field on this page is current as of this release. The service has no deprecated or removed config fields to migrate away from.
Full examples¶
The two files below are the example templates shipped alongside the RPM. They exercise the two most common deployment shapes — a NATS split-consumer and a NATS split-producer — and they are here for orientation. The per-section reference below them is authoritative.
Click to expand the shipped consumer example
http:
address: ":8313"
tls: {}
redis:
address: "127.0.0.1:5379"
tls: {}
# Number of redis workers, or 0 to disable redis insertion
workers: 10
nats:
# As defined by: https://github.com/PowerDNS/itsy
itsy:
url: "nats://localhost:4222"
# Optional prefix, if not set it will default to svc meaning
# subjects will look like svc.deviceregister.abc
prefix: "pdnspf"
topologies: []
meta: {}
tls: {}
split-mode-consumer: true
dstore:
# dstore_alert address. When unset, no messages are sent to dstore_alert.
alert-address: ""
database:
# Postgres connect string, see https://godoc.org/github.com/lib/pq
# Default: "user=postgres dbname=postgres"
connection-string: "user=postgres dbname=postgres host=127.0.0.1 port=5332 sslmode=disable"
# Number of database workers, or 0 to disable database and redis insertion
# (roughly equals connections when busy)
# Default: 10
workers: 10
kafka:
# Use Kafka instead of Postgres for storing new devices
# - MUST have kafka configured for this to be valid
kafka_only: false
# At least one address needs to be specified
addresses:
- "127.0.0.1:9092"
# topic is mandatory
topic: dstore
# Use TLS?
use_tls: false
# TLS Config
tlsconfig:
insecure_skip_verify: false
# Possible values: roundrobin (default), leastbytes, fnv-1a, crc32, murmur2
balancer: roundrobin
# defaults to 2 (i.e. retry once)
max_attempts: 2
# batch_size defaults to 10000 - the writer blocks until batch_size messages
# have been received, or until batch_timeout is reached
batch_size: 10000
# Timeout before flushing if batch size is not reached (default 1ms)
batch_timeout: 1ms
# Read timeout defaults to 10s
read_timeout: 10s
# Write timeout defaults to 10s
write_timeout: 10s
# How many replicas must ack before returning: "none", "one" (the leader), or "all"
# Defaults to "one"
required_acks: one
# If true, never block and ignore return values from kafka, defaults to false
async: false
# Number of worker goroutines, defaults to 10
num_workers: 5
log:
format: "logfmt"
Click to expand the shipped producer example
For combined mode, no example file ships. Merge the UDP section
from the producer example with the Redis / Database / Kafka sections
from the consumer example, and leave the nats: section empty.
udp¶
The udp section controls the listener for inbound register messages
from the Recursor (or dnsdist). Every "unknown device seen" packet
arrives here as a short UDP datagram.
Leaving address empty is how a split-consumer instance is
configured — the consumer does not listen for UDP at all, it reads
from NATS. In combined and split-producer modes address must be set
or no register messages will ever reach the service.
| Parameter | Type | Default | Description |
|---|---|---|---|
address |
string |
"" |
UDP bind address in host:port form (e.g. :4313 for all interfaces, 127.0.0.1:4313 for local only). Empty disables the listener. |
Example:
http¶
The http section controls the internal webserver that exposes the
Prometheus /metrics endpoint, the HTML status page, and the Go
net/http/pprof profiling handlers. All three share the same bind
address; there is no way to split them onto different listeners.
If http.address is left empty, the webserver does not start at all
and the instance runs headless. You lose metrics and pprof in that
case, so a normal deployment should always configure it.
| Parameter | Type | Default | Description |
|---|---|---|---|
address |
string |
"" |
Bind address in host:port form (e.g. :8313 or 127.0.0.1:8313). Empty disables the webserver. |
tls |
tlsconfig.Config |
{} |
PowerDNS go-tlsconfig block. Leave as {} for plain HTTP; populate to enable HTTPS. |
Example:
See Operations → Observability for the list of served endpoints.
redis¶
The redis section controls the storage Redis that the filtering
Recursors converge on via their local LMDB sync. In combined and
split-consumer modes, every accepted device eventually lands here.
Producer-only instances do not use Redis.
Password masking¶
The service logs its effective configuration on startup. Both the
password field here and the connection-string in the
database section are replaced with (omitted) in that
log line, so a shared journal or log archive does not leak secrets.
Use environment-variable expansion (e.g. password: "${REDIS_PASSWORD}")
to inject the real value from a systemd drop-in.
Disabling Redis¶
Setting workers: 0 disables the Redis write pipeline. The service
still accepts and processes messages, they just never land in Redis.
This is occasionally useful for testing, but it breaks the main
contract with the Recursors and should never be set on a production
instance.
Redis fields¶
| Parameter | Type | Default | Description |
|---|---|---|---|
address |
string |
"" |
Redis server in host:port form. Required when Redis is in use. |
username |
string |
"" |
Optional Redis ACL username. |
password |
string |
"" |
Optional Redis AUTH password. Masked in startup log output. |
tls |
tlsconfig.Config |
{} |
PowerDNS go-tlsconfig block. Leave as {} for plain TCP. |
workers |
int |
10 |
Worker goroutines draining the Redis write queue. Set to 0 to disable Redis writes — see Disabling Redis. |
Example:
nats¶
The nats section is what selects the split-mode deployment shape.
See the Overview → Modes of operation
and Architecture → Split mode flow
for how the two roles fit together.
Mode selection¶
The two booleans at the bottom of this section, split-mode-producer
and split-mode-consumer, are what pick the instance's role. Exactly
one of the following shapes is valid:
| Shape | split-mode-producer |
split-mode-consumer |
|---|---|---|
| Combined mode (no NATS) | false |
false |
| Split producer | true |
false |
| Split consumer | false |
true |
Setting both booleans to true is refused at startup. Leaving
both false selects combined mode, in which case the rest of the
nats: block is ignored and the service never connects to NATS.
In split modes, producer and consumer instances must share a NATS
deployment and the same itsy.prefix, because the subject they
communicate on is derived from that prefix (<prefix>.device-register.seen-device).
NATS fields¶
| Parameter | Type | Default | Description |
|---|---|---|---|
itsy |
itsy.Config |
{} |
NATS connection, topology and TLS. See the itsy sub-table below and the full itsy reference. |
split-mode-consumer |
boolean |
false |
Run as a NATS consumer. See Mode selection. |
split-mode-producer |
boolean |
false |
Run as a NATS producer. See Mode selection. |
itsy¶
Nested NATS connection config, passed through to
github.com/PowerDNS/itsy. The
most commonly-touched fields are listed here; see the full
itsy reference
for all fields including environment variable overrides.
| Parameter | Type | Default | Description |
|---|---|---|---|
url |
string |
— | NATS server URL, e.g. nats://localhost:4222 or tls://nats.example.com:4222. |
prefix |
string |
"svc" |
Subject prefix used for all itsy-managed subjects. The device-register transport subject is <prefix>.device-register.seen-device; producer and consumer instances must agree on the prefix. |
topologies |
[] |
[] |
Advanced NATS topology config. Leave empty unless you have specific requirements. |
meta |
map[string]string |
{} |
Free-form metadata attached to this NATS service registration, visible to NATS monitoring tooling. |
tls |
tlsconfig.Config |
{} |
TLS config for the NATS connection. Populate to enable authenticated/encrypted transport. |
Example (consumer):
dstore¶
The dstore section is optional. If alert-address is set, the
service opens a persistent TCP connection to that address and forwards
a protobuf-encoded alert message for every new device. If it is left
empty, no alerts are sent and no connection is opened.
| Parameter | Type | Default | Description |
|---|---|---|---|
alert-address |
string |
"" |
host:port of the dstore_alert endpoint. Empty disables the alert sender. |
Example:
database¶
The database section configures the Postgres connection used to
insert new device rows and to look up each user's numeric profile.
It is used in combined and split-consumer modes unless
kafka.kafka_only is set, in which case an external system
is expected to own the database writes and this section can be left
at its defaults.
Connection string masking¶
Like the Redis password, the full connection-string is replaced
with (omitted) in the effective-config log output at startup.
Inject secrets via environment expansion (e.g.
connection-string: "user=deviceregister dbname=filter password=${PG_PASSWORD} host=...).
Disabling the pipeline¶
Setting workers: 0 disables the database pipeline entirely. Because
the Redis writer depends on the profile lookup performed on the
database pipeline, disabling the database also disables the Redis
write path.
Note
If you want to skip Postgres but keep Redis working, do not set
workers: 0 — use kafka.kafka_only: true instead,
which keeps the pipeline running with Kafka in place of Postgres
and still feeds the Redis writer.
Database fields¶
| Parameter | Type | Default | Description |
|---|---|---|---|
connection-string |
string |
"user=postgres dbname=postgres" |
libpq connection string. Masked in startup log output. |
workers |
int |
10 |
Worker goroutines on the database pipeline. Roughly equal to the number of concurrent Postgres connections the service holds when busy. Set to 0 to disable — see Disabling the pipeline. |
Example:
database:
connection-string: "user=deviceregister dbname=filter host=db.example.com sslmode=require password=${PG_PASSWORD}"
workers: 10
kafka¶
The kafka section enables an optional Kafka sender. It has two
deployment shapes:
- Alongside Postgres (
kafka_only: false, default). Every accepted device is written to Postgres and published to Kafka, and Redis then converges on the Postgres-provided profile. Useful when an external analytics or fan-out system wants a live stream of device events in addition to the portal database. - Instead of Postgres (
kafka_only: true). The service does not insert device rows into Postgres at all; an external system is expected to consume the Kafka topic and write them. In this mode the device is only added to Redis after the Kafka publish succeeded — if Kafka rejects the message, nothing lands in Redis and the Recursor re-sends the notification later.
Setting kafka_only: true without configuring at least one
addresses entry is refused at startup.
Balancer¶
The balancer field picks the partition balancing algorithm when the
topic has multiple partitions:
| Value | Meaning |
|---|---|
roundrobin |
Distribute messages evenly across partitions in sequence. The default, and a sensible choice when ordering within a user is not required. |
leastbytes |
Send to the partition that has received the fewest bytes so far. Useful when some partitions back up. |
fnv-1a |
Hash the message key with FNV-1a to pick a partition. Deterministic per-key, keeps updates for the same key ordered. |
crc32 |
CRC32-based hash partitioner. Same ordering guarantees as FNV-1a. |
murmur2 |
Murmur2 hash partitioner. Matches the default Java client's hashing, useful for cross-client key-stickiness. |
Required acks¶
required_acks picks the replication acknowledgment the producer
waits for:
| Value | Meaning |
|---|---|
none |
Fire-and-forget; do not wait for any broker ack. Highest throughput, zero durability. |
one |
Wait for the leader replica. The default, and the right choice for most deployments. |
all |
Wait for all in-sync replicas. Highest durability, slower. |
Async writes¶
Setting async: true tells the Kafka writer to never block and to
ignore the return values of the write calls. Use it when throughput
matters more than knowing whether individual writes succeeded. With
async: false (the default) the service reports Kafka send errors
through the deviceregister_kafka_sender_send_errors_total metric.
Sizing defaults¶
The shipped defaults work well for a healthy Kafka cluster:
batch_size: 10000, batch_timeout: 1ms, read_timeout: 10s,
write_timeout: 10s, num_workers: 10, max_attempts: 2. Raise
batch_size if your Kafka cluster prefers larger batches, and raise
batch_timeout only if throughput matters more than latency (the
writer blocks until either limit is reached).
Kafka fields¶
| Parameter | Type | Default | Description |
|---|---|---|---|
kafka_only |
boolean |
false |
Skip Postgres entirely and require an external consumer on the Kafka topic. Requires a populated addresses list. See above. |
addresses |
[]string |
[] |
Kafka broker addresses in host:port form. At least one entry is required whenever kafka_only: true or Kafka is otherwise in use. |
topic |
string |
"" |
Destination topic name. Required when Kafka is in use. |
use_tls |
boolean |
false |
Enable TLS for the Kafka connection. |
tlsconfig |
tlsconfig.Config |
{} |
PowerDNS go-tlsconfig block for Kafka TLS. |
balancer |
string |
roundrobin |
Partition balancer. See Balancer for the five accepted values. |
max_attempts |
int |
2 |
Retry attempts per message before giving up. |
max_msg_size |
int |
65538 |
Maximum message size in bytes. |
single_msgs |
boolean |
false |
When true, emit one Kafka message per device. When false (default), pack multiple devices into a single Kafka message. |
batch_size |
int |
10000 |
Maximum devices per batch. The writer blocks until this many messages have been accumulated or batch_timeout is reached. |
batch_timeout |
go: Duration |
1ms |
Flush timeout when batch_size has not been reached. |
read_timeout |
go: Duration |
10s |
Kafka read timeout. |
write_timeout |
go: Duration |
10s |
Kafka write timeout. |
required_acks |
string |
one |
Replication acknowledgment strategy. See Required acks for the three accepted values. |
async |
boolean |
false |
Fire-and-forget writes. See Async writes. |
num_workers |
int |
10 |
Kafka writer goroutines. |
Example:
kafka:
kafka_only: true
addresses:
- "kafka-1.example.com:9092"
- "kafka-2.example.com:9092"
topic: "device-events"
use_tls: true
tlsconfig: {}
balancer: roundrobin
required_acks: one
num_workers: 10
blocking¶
The blocking section controls per-user rate limiting and the
blocklist cool-off. See Architecture → Blocklist
for what the blocklist actually suppresses (spoiler: only portal UI
visibility, not DNS resolution).
Counting window¶
The effective counting window is 2 × block-time-window because
the service keeps two rolling counter buckets and blocks when their
combined total crosses block-max-devices. With the shipped defaults
(block-time-window: 10s, block-max-devices: 10) a user seen
registering 11 or more devices in any 20-second span trips the
blocklist and gets blocked for block-time (default 1 minute).
When a user trips the blocklist their devices keep resolving DNS normally — only registration is suppressed, so the devices do not appear in the portal UI for the duration of the block.
Blocking fields¶
| Parameter | Type | Default | Description |
|---|---|---|---|
block-time |
go: Duration |
1m |
How long a user is blocked from further registration after tripping the threshold. |
block-max-devices |
int |
10 |
Device-registration count that trips the block, counted over 2 × block-time-window. See Counting window. |
block-time-window |
go: Duration |
10s |
Width of one rolling counter bucket. The effective counting window is twice this. |
Example:
log¶
The log section picks the output format and level for the service's
logs. Logs go to standard error, which under the default systemd unit
is captured by the journal under the pdns-pf-device-register
identifier.
| Parameter | Type | Default | Description |
|---|---|---|---|
format |
string |
logfmt |
Log output format. Accepted values: logfmt (structured key=value, easy to grep) or json. |
level |
string |
info |
Log level. Accepted values: debug, info, warn, error. The --debug CLI flag overrides this to debug at startup. |
Example: