Configuration Reference
Valerter is configured via a YAML file, typically at /etc/valerter/config.yaml.
See config/config.example.yaml for a complete
annotated example.
This page covers the configuration file itself. Related pages:
- Templates: variables, filters, Markdown bodies, template validation
- Notifiers: Mattermost, Telegram, email and webhook notifiers, delivery
- Operations: service, permissions, logs, upgrades, shutdown
Configuration file
| Location | Description |
|---|---|
/etc/valerter/config.yaml |
Default location (systemd) |
Custom path via -c flag |
valerter -c /path/to/config.yaml |
The file holds secrets (webhook URLs, tokens, passwords). The Debian package
installs it owned by root:valerter with mode 640, in a directory with mode
750: the service reads it, other users cannot (see
Files and permissions).
Multi-file configuration
For large deployments, you can split rules, templates, and notifiers into separate files in .d/ directories alongside config.yaml:
/etc/valerter/
├── config.yaml # Main config
├── rules.d/
│ ├── security.yaml # Security team rules
│ └── infra.yaml # Infrastructure rules
├── templates.d/
│ └── custom.yaml # Custom templates
└── notifiers.d/
└── team-channels.yaml
Format in .d/ files
Files in .d/ directories use a map format where the name is the YAML key:
# rules.d/security.yaml
auth_failure:
query: "_msg:authentication AND status:failed"
parser:
json:
fields: [user, ip]
notify:
template: "security_alert"
destinations:
- security-team
brute_force:
query: "_msg:blocked"
parser:
regex: "IP (?P<ip>\\d+\\.\\d+\\.\\d+\\.\\d+)"
notify:
template: "security_alert"
destinations:
- security-team
# templates.d/custom.yaml
security_alert:
title: "Security: {{ rule_name }} on {{ vl_source }}"
body: "{{ _msg }}"
email_body_html: "<p>{{ _msg }}</p>"
accent_color: "#ff0000"
# notifiers.d/team-channels.yaml
security-team:
type: mattermost
webhook_url: "https://mattermost.example.com/hooks/security"
infra-team:
type: mattermost
webhook_url: "https://mattermost.example.com/hooks/infra"
Loading rules
- Files processed:
*.yamland*.ymlonly - Order: Files are read in alphabetical order of their path. Rules keep a
stable order on every load: the rules of
config.yamlin their declared order, then the rules ofrules.d/sorted by file path, then by rule name within a file (not by declaration order in the file) - Hidden files: Files starting with
.are ignored - Empty files: Silently skipped
- Name uniqueness: Names must be unique across
config.yamland all.d/files - Cross-references: Rules in
.d/can reference templates inconfig.yamland vice versa
Collision detection
If the same name is defined in two files, the configuration is refused at
startup and by valerter --validate (exit code 1), with an explicit error:
ERROR valerter: Failed to load configuration error=duplicate rule name 'auth_failure': defined in 'rules.d/extra.yaml' and 'rules.d/security.yaml'
The message has the same form for templates and notifiers (duplicate template
name '...', duplicate notifier name '...'), whether the collision is between
config.yaml and a .d/ file or between two files of the same .d/ directory
(the first file in alphabetical order is cited first).
Structure overview
victorialogs: # VictoriaLogs sources (REQUIRED, at least one)
metrics: # Prometheus metrics (optional, enabled on port 9090 by default)
notifiers: # Named notification channels (REQUIRED, at least one)
defaults: # Default throttle, timestamp timezone and stream cap (REQUIRED)
templates: # Message templates (REQUIRED)
rules: # Alert rules (REQUIRED, at least one enabled)
Templates and notifiers have their own pages.
VictoriaLogs sources
victorialogs is a map of named sources. A single valerter instance can tail
multiple VL backends concurrently and route alerts per source. At least one
source is required.
victorialogs:
default: # Source name (used as `vl_source`)
url: "http://victorialogs:9428" # REQUIRED (a trailing "/" is ignored)
# Optional: Basic Authentication (per-source)
basic_auth:
username: "${VL_USER}"
password: "${VL_PASS}"
# Optional: Custom headers (for tokens, API keys).
# Sent last: they replace a default or basic_auth header of the same name.
# Use either basic_auth or an Authorization header, not both: here the
# Bearer token would replace the basic_auth credentials.
headers:
Authorization: "Bearer ${VL_TOKEN}"
# Optional: TLS configuration
tls:
verify: true # Set to false for self-signed certs
URL and headers
urlis the base URL of VictoriaLogs, optionally with a path prefix (https://proxy.example.com/vl). Trailing slashes are dropped before/select/logsql/tailis appended, sohttp://victorialogs:9428/andhttp://victorialogs:9428are equivalent.- Each request carries
Accept: application/x-ndjson,Connection: keep-aliveand, whenbasic_authis set, a BasicAuthorizationheader. Customheadersare applied last and replace any of these with the same name (case-insensitive) instead of being sent alongside:accept: "application/json"yields a singleAccept: application/json. - A custom
Authorizationheader therefore takes precedence overbasic_auth. Valerter logs a warning at the start of each affected (rule, source) task (Custom Authorization header overrides basic_auth for this VictoriaLogs source, withrule_nameandvl_sourceonly, never the header value nor the credentials). Headers with another name, such asX-TenantorAuthorization-Token, are sent together with Basic Auth. - Header values and the Basic Auth password are never written to the logs.
The source URL (which may carry credentials or a token resolved from
${VAR}) only appears masked in the streaming logs (http://***@vl:9428/select/logsql/tail?***, the LogsQL query being logged in a separatequeryfield), and transport errors are logged without it.
The configuration is refused at load time (and by valerter --validate) when,
after ${VAR} substitution:
urldoes not parse or does not usehttp/https:victorialogs.<source>.url: invalid URL: .... A value that still contains${after substitution is checked like any other URL. The URL is never printed.- a
headersname is not a valid HTTP header name:victorialogs.<source>.headers: invalid header name '<name>'. - a
headersvalue is not a valid HTTP header value (line break, control character):victorialogs.<source>.headers: invalid value for header '<name>'. The value is never printed.
Multi-source example
victorialogs:
vlprod:
url: "https://victorialogs.prod.example.com:9428"
basic_auth:
username: "${VL_PROD_USER}"
password: "${VL_PROD_PASS}"
vldev:
url: "http://victorialogs.dev.internal:9428"
Rules can target a subset of sources via vl_sources: [name, ...], or omit
the field to fan out across every configured source. Source names must match
^[a-zA-Z0-9_]+$. The current source name is available as {{ vl_source }}
in every template and in throttle.key (see Templates).
Reverse proxy
If VictoriaLogs is behind a reverse proxy (nginx, Traefik, etc.), you must disable buffering and caching for the /select/logsql/tail endpoint. Valerter uses HTTP streaming to receive logs in real-time, and proxy buffering will cause delays or connection issues.
Nginx example:
# Add this BEFORE your general /select location block
location = /select/logsql/tail {
proxy_pass http://victorialogs_backend;
# CRITICAL: Disable buffering and caching for streaming
proxy_buffering off;
proxy_cache off;
# Long timeouts for persistent connections
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
Key settings:
proxy_buffering off— Send data immediately to clientproxy_cache off— Don’t cache streaming responsesproxy_read_timeout 3600s— Keep connection alive for 1 hour
Defaults
Default values applied to all rules unless overridden.
defaults:
throttle:
count: 5 # Max alerts per window (>= 1)
window: 60s # Time window (e.g., 60s, 5m, 1h; > 0)
# key: "{{ host }}" # Optional: grouping key template
# timestamp_timezone: "Europe/Paris" # Optional: timezone for formatted timestamps (default: UTC)
# max_streams: 50 # Optional: hard cap on total VictoriaLogs streams (default: 50)
defaults.throttle applies to every rule without its own throttle block and
gets the same checks as a rule throttle (see Throttling):
count: 0 (defaults.throttle.count must be >= 1 (0 would suppress every alert)),
window: 0s (defaults.throttle.window must be > 0 (0s disables throttling)),
a key with a syntax error (defaults.throttle.key: ...) or an unknown filter,
test or function (defaults.throttle.key render: ...) are refused at load time.
max_streams — fan-out guardrail
Multi-source deployments spawn one stream per (enabled rule, target source)
pair. With unscoped fan-out rules and many sources the total scales as
rules × sources, which can DoS a backend by accident. defaults.max_streams
caps that total at load time:
total = sum(if rule.vl_sources is empty then sources.len() else rule.vl_sources.len()
for rule in enabled_rules)
Disabled rules do not contribute. Breaching the cap fails the configuration at
load time (and valerter --validate) with a message stating both the actual
count and the cap (defaults.max_streams exceeded: N stream(s) required by
enabled rules > cap of M), so an operator knows whether to raise the cap or
trim rules. Default: 50. max_streams: 0 is refused
(defaults.max_streams must be >= 1).
Timestamp timezone
The timestamp_timezone setting controls the timezone of {{ log_timestamp_formatted }} in notifier templates and of the Mattermost footer.
| Value | Example Output |
|---|---|
UTC (default) |
15/01/2026 10:00:00 UTC |
Europe/Paris |
15/01/2026 11:00:00 CET (winter) / CEST (summer) |
America/New_York |
15/01/2026 05:00:00 EST |
Uses IANA timezone names. Invalid timezone will fail at startup.
Metrics
metrics:
enabled: true # Default: true
port: 9090 # Default: 9090
The Prometheus endpoint is served on http://<host>:<port>/metrics while the
daemon runs (never by --validate). The section is optional: without it,
metrics are enabled on port 9090. See Metrics for the exposed
series.
Templates
templates:
default_alert:
title: "{{ rule_name }}" # REQUIRED
body: "{{ _msg }}" # REQUIRED
email_body_html: "<p>{{ _msg }}</p>" # REQUIRED for email destinations (text bodies)
accent_color: "#ff0000" # Optional: hex color
body_format: text # Optional: text (default) or markdown
Variables, filters, Markdown bodies and the checks applied to templates are described in Templates.
Rules
Alert rules define what logs to monitor and how to process them.
rules:
- name: "high_cpu_alert" # REQUIRED: unique name
enabled: true # Default: true (at least one rule must be enabled)
query: '_stream:{app="node"} | unpack_json | filter cpu:>90' # REQUIRED: LogsQL
parser: # REQUIRED key, may be empty: parser: {}
regex: '(?P<level>\S+) (?P<message>.*)' # Optional, applied to _msg first
json: # Optional, applied after the regex
fields: ["host", "cpu"]
throttle: # Optional: overrides defaults
key: "{{ host }}" # Group throttling by field
count: 3
window: 5m
vl_sources: [vlprod] # Optional: target specific sources
# Empty/omitted = fan out across all
# sources defined in `victorialogs:`.
# Unknown names rejected at load.
notify: # REQUIRED
template: "custom_template" # REQUIRED: template name
destinations: # REQUIRED: at least one notifier, each at most once
- mattermost-ops
- email-ops
mattermost_channel: "alerts" # Optional: channel for Mattermost destinations
# (rule > notifier `channel` > webhook default;
# if rejected, resent to the notifier `channel`
# or the webhook default, see
# notifiers.md#channel-per-rule)
notify.destinations must list each notifier at most once: a duplicate, which
would deliver every alert twice to the same notifier, refuses the configuration
at load time and in --validate, for enabled and disabled rules alike
(rule '<rule>': notify.destinations contains duplicate entry '<name>' (each
notifier may appear at most once)).
LogsQL query restrictions
Valerter uses the VictoriaLogs /select/logsql/tail endpoint, which streams
matching logs as they arrive. Pipes that need the full result set are not
supported by /tail and are rejected by valerter --validate:
stats, sort, top, uniq, limit, offset, first, last, facets,
join, field_names, field_values, block_stats, blocks_count, union.
Pipes that transform or filter each line work as expected: filter,
unpack_json, extract, extract_regexp, format, fields, rename,
math, replace… A filter placed after a pipe goes through the filter
pipe and uses the LogsQL comparison syntax: | unpack_json | filter cpu:>90
(not cpu > 90). See the
LogsQL reference.
For aggregation-based alerts (“more than N distinct versions per host”),
run the stats query on a schedule with vmalert
and let Valerter alert on individual events instead.
Parser
The parser key is required, but may be empty (parser: {}): every field of
the line returned by VictoriaLogs (_msg, _time, _stream and the fields of
the log) is available to templates whatever the parser. A parser adds fields:
regex: applied to_msgfirst. Each named capture group ((?P<name>...)) becomes a field. A line whose_msgdoes not match is dropped (counted invalerter_parse_errors_total{error_type="regex_no_match"}): the regex also acts as a filter.json.fields: applied next, to the fields of the line. Each listed path (data.server.hostname) is copied to a top-level field named after its last segment (hostname). A top-level path (host) is already available as is; a missing path is ignored.
parser:
regex: '(?P<timestamp>\S+) (?P<level>\S+) (?P<message>.*)'
parser:
json:
fields: ["data.server.hostname", "data.status"]
A line that is not valid JSON is dropped
(valerter_parse_errors_total{error_type="invalid_json"}).
Throttling
Prevents alert spam by limiting notifications per time window.
| Field | Description |
|---|---|
key |
Template to group alerts (e.g., {{ host }}) |
count |
Max alerts per window |
window |
Time window (e.g., 30s, 5m, 1h) |
Example: Max 3 alerts per host per 5 minutes:
throttle:
key: "{{ host }}"
count: 3
window: 5m
count must be >= 1 and window > 0. key is checked at load time like the
templates (syntax, then test render): a syntax error is
reported as invalid template in rule '<name>': throttle.key: ..., an unknown
filter, test or function as invalid template in rule '<name>': throttle.key
render: ..., for enabled and disabled rules alike. A key whose rendering fails
at runtime because of the event’s values (e.g. {{ port + 1 }} with a string
port) uses the fallback key <rule>:error.
The window is fixed: a key’s counter starts with its first alert and expires
window later. Without key, all alerts of the rule share one counter per
source (default key {rule}-{source}:global).
Throttling across sources
A rule keeps one throttle cache shared by all its sources (all configured
sources, or those listed in vl_sources). Every source that renders the same
key increments the same counter:
- Default key: it contains the source name, so each source has its own counter. Nothing to configure.
- Custom key without
{{ vl_source }}(e.g.{{ rule_name }},{{ host }}): the counter is shared by the rule’s sources. The same outage seen byvlprodandvldevproduces a single alert. - Custom key with
{{ vl_source }}: each source has its own counter.
rules:
# One alert per host across all sources
- name: "switch_down"
query: '_stream:{app="network"} AND "link down"'
parser:
regex: '(?P<host>SW-\d+)'
throttle:
key: "{{ host }}"
count: 1
window: 10m
notify:
template: "default_alert"
destinations: ["mattermost-ops"]
# One alert per host and per source
- name: "switch_down_per_source"
query: '_stream:{app="network"} AND "link down"'
parser:
regex: '(?P<host>SW-\d+)'
throttle:
key: "{{ vl_source }}-{{ host }}"
count: 1
window: 10m
notify:
template: "default_alert"
destinations: ["mattermost-ops"]
At startup, valerter logs at INFO level, once per rule, every rule targeting
at least two sources whose custom key does not reference vl_source, to make
the shared counter visible. Two rules never share a counter, even when their
keys render the same value.
When a source reconnects after an error, only the counters fed exclusively by that source are reset; counters another source has contributed to are kept.
Secrets management
Recommended: direct values
Put secrets directly in the config file, protected by its permissions
(640 root:valerter, set by the Debian package):
notifiers:
mattermost-ops:
type: mattermost
webhook_url: "https://mattermost.example.com/hooks/abc123def456"
For an installation without the package:
sudo chown root:valerter /etc/valerter/config.yaml
sudo chmod 640 /etc/valerter/config.yaml
Alternative: environment variables
${VAR_NAME} placeholders are resolved at startup in notifier secrets
(webhook_url, url, headers, bot_token, SMTP username/password), in
the source of the webhook body_template (never in values rendered from logs,
see Notifiers) and
in VictoriaLogs sources (url, basic_auth.username/password, headers),
both at daemon startup and by valerter --validate. An undefined variable is
an error: they must therefore also be defined when running --validate.
For Kubernetes or orchestrators, use ${VAR_NAME} syntax:
notifiers:
mattermost-ops:
type: mattermost
webhook_url: "${MATTERMOST_WEBHOOK}"
Variables are resolved at startup from the process environment. You can use any environment variable - there is no predefined list. Common examples:
${VL_USER},${VL_PASS}- VictoriaLogs credentials${SMTP_USER},${SMTP_PASSWORD}- Email credentials${SLACK_WEBHOOK_URL},${PAGERDUTY_ROUTING_KEY}- Notification services
CLI and environment variables
valerter [OPTIONS]
Options:
-c, --config <CONFIG> Path to configuration file [default: /etc/valerter/config.yaml]
--validate Validate configuration and exit
--log-format <LOG_FORMAT> Log format: text or json [env: LOG_FORMAT=] [default: text]
-h, --help Print help
-V, --version Print version
These variables are read directly by the process (not substituted in the configuration):
| Variable | Description |
|---|---|
RUST_LOG |
Log level: error, warn, info (default), debug, trace, or per module (info,valerter::notify=debug) |
LOG_FORMAT |
Log format: text (default) or json; --log-format wins |
NO_COLOR |
When set to a non-empty value, text logs have no colors even on a terminal (they never have colors under journald or in a file) |
See Logs for the format of the logs.
Validation
Always validate before deploying:
valerter --validate -c /etc/valerter/config.yaml
--validate runs every blocking check of the daemon startup, in this order, with the same error messages and exit code 1 on failure:
- Loading — YAML syntax, unknown fields,
rules.d/,templates.d/andnotifiers.d/merge (name collisions),${VAR}substitution in VictoriaLogs source URLs,basic_authandheaders - Validation — required fields, regexes, template syntax and test render (including
throttle.key), source names, URLs and headers,defaults.throttle,max_streamscap, at least one enabled rule - Notifier construction — every notifier is built:
${VAR}placeholders in notifier secrets (webhook URLs, headers, bot tokens, SMTP credentials) are resolved, resolved webhook and Mattermost URLs are checked,body_template_fileis read (size and UTF-8 checked), email addresses, HTTP methods, headers,chat_ids, Telegramparse_mode,formatand notifier templates (syntax and test render) are checked - Rule destinations — every rule destination (enabled or not) names a declared notifier
- Email body — templates of enabled rules sent to email destinations define
email_body_html, unless their body is Markdown (body_format: markdown) - Warnings —
mattermost_channel ignored - no mattermost notifier in destinationsis logged when a rule setsmattermost_channelwithout any Mattermost destination, andNotifier template references unknown variablewhen a notifier template reads a variable that does not exist at its level, such as{{ host }}instead of{{ log.host }}(see Template validation); the exit code stays 0
The daemon runs the same checks at startup, in the same order, before connecting to anything.
Errors from steps 3 to 5 are all reported in one pass. No daemon, metrics server or network connection is started: VictoriaLogs sources, SMTP servers and webhooks do not need to be reachable.
Because notifiers are built, every environment variable referenced by a notifier must be defined when running --validate, including in CI (dummy values are fine, nothing is sent). Run it as a user that can read the configuration and the body_template_file files: with the Debian package, root or a member of the valerter group (see Validating the configuration).
On success, a summary is printed on stdout:
Configuration is valid: /etc/valerter/config.yaml
VictoriaLogs sources: 2 [default=http://localhost:9428, prod=https://***@vl.example.com:9428/?***]
Rules: 3 (2 enabled)
Templates: 2
Notifiers: 2 [email-ops=email, mattermost-ops=mattermost]
Metrics: enabled (port 9090)
Source URLs are redacted: credentials are replaced by ***, the query string by *** and the fragment is dropped. URLs without such parts are printed unchanged.
See Also
- Templates - Variables, filters, Markdown bodies
- Notifiers - Mattermost, Telegram, email and webhook notifiers
- Operations - Service, logs, upgrades, shutdown
- Metrics - Prometheus metrics reference
- Architecture - How Valerter works