Skip to content

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
udp:
    # UDP listen address
    address: ":4313"
http:
    address: ":8313"
    tls: {}
nats:
    # As defined by: https://github.com/PowerDNS/itsy
    itsy:
        url: "nats://localhost:4222"
        # Optional prefix
        prefix: ""
        topologies: []
        meta: {}
        tls: {}
    split-mode-producer: true

log:
    format: "logfmt"

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:

udp:
    address: ":4313"

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:

http:
    address: ":8313"
    tls: {}

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:

redis:
    address: "127.0.0.1:6379"
    password: "${REDIS_PASSWORD}"
    tls: {}
    workers: 10

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):

nats:
    itsy:
        url: "nats://nats.example.com:4222"
        prefix: "pdnspf"
        tls: {}
    split-mode-consumer: true

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:

dstore:
    alert-address: "192.0.2.42:5555"

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:

blocking:
    block-time: 5m
    block-max-devices: 20
    block-time-window: 30s

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:

log:
    format: logfmt
    level: info