Skip to content

Deployment configuration

config.yaml contains deployment settings. Create monitoring jobs and manage users, destinations, scanner profiles, baselines, and public status in the web console. See config.example.yaml for the complete validated schema.

Configuration Managed in Purpose
database, retention YAML SQLite location and history retention.
timezone YAML Optional IANA timezone for log, CLI, notification, and console times, and the default for new jobs.
web.listen, web.allowed_hosts, web.trusted_proxies, web.forwarded_header YAML Loopback listener, approved proxy hostnames, trusted proxy networks, and the single forwarding header used for client IPs.
web.ipv6_rate_limit_prefix YAML Prefix length by which the rate limits group IPv6 client addresses.
web.auth_key_file YAML/secrets Optional separate key for the encrypted TOTP seeds.
web.source_url YAML Source of a modified or forked build, the target of the console’s Source code link.
web.max_live_streams, web.max_live_streams_per_unit YAML Live-update streams the deployment keeps open, and the most that one business unit may hold.
log.level YAML Log verbosity: debug, info, warn, or error.
scheduler.* YAML Concurrent scans and probe budgets.
scanner.target_exclusions YAML Addresses that may never be scanned.
scanner.sandbox YAML Whether Nmap and Naabu run as an unprivileged identity; see the scanner sandbox.
scanner.landlock YAML Whether Landlock restricts the files Nmap and Naabu can open; see Landlock.
scanner.max_job_hosts YAML The most addresses the targets of one job may expand to when it scans, for every unit.
enrichment.rdap.enabled YAML Enable or disable on-demand public network-registration lookups.
updates.enabled YAML Enable or disable the three-hour stable-release check.
notifications.encryption_key_file YAML/secrets Optional separate key for the encrypted notification destinations.
notifications.sandbox YAML Whether the notification process runs as an unprivileged identity, restricted with Landlock; see the notification sandbox.
notifications.urls, urls_file YAML/secrets Deprecated. Imported once as web-managed destinations; see Notifications.
backup.directory, backup.schedule, backup.keep YAML Optional verified, rotated backups that the daemon takes on a schedule; see Scheduled backups.
Jobs, users, profiles, notification destinations Web console Runtime administration stored in SQLite.

The YAML jobs section from older deployments is not imported into the scheduler. Such jobs remain inactive and EdgeWatch shows a startup warning so they can be recreated and reviewed explicitly in the console.

Setting Default Allowed values
database /var/lib/edgewatch/edgewatch.db A file path.
retention 90d At least 24h. Durations use Go syntax such as 36h, plus a d suffix for days.
log.level info debug, info, warn, or error.
scheduler.max_concurrent_scans 1 1 to 64.
scheduler.max_probe_count 5000000 1 to 100000000; 0 is rejected.
scheduler.max_naabu_probe_count 20000000 1 to 100000000; 0 is rejected.
scanner.sandbox auto auto, required, or off.
scanner.landlock auto auto, required, or off; required needs scanner.sandbox other than off.
scanner.max_job_hosts 65536 1 to 1000000.
web.ipv6_rate_limit_prefix 64 32 to 128; 128 counts each IPv6 address on its own.
web.auth_key_file auth.key next to the database A regular file of 32 raw bytes or 64 hexadecimal characters, without group or other permissions.
notifications.encryption_key_file notification.key next to the database A regular file of 32 raw bytes or 64 hexadecimal characters with mode 0400 or 0600.
notifications.sandbox auto auto, required, or off.
web.source_url The exact Git tag of an official build An absolute HTTPS URL without credentials, a query, or a fragment, at most 2048 bytes.
web.max_live_streams 256 1 to 4096; 0 is rejected.
web.max_live_streams_per_unit 64, or web.max_live_streams when that is lower 1 to web.max_live_streams; 0 is rejected.
backup.directory Not set: no scheduled backups The absolute path of an existing directory, other than /.
backup.schedule 0 3 * * * (daily at 03:00) A five-field cron expression that fires, in timezone when it is set and UTC otherwise, without a TZ= or CRON_TZ= prefix. Requires backup.directory.
backup.keep 7 1 to 1000 scheduled backups. Requires backup.directory.

Jobs are configured in the console, which enforces these limits:

Job setting Default Allowed values
Timeout 1h 1s to 30d.
Resume window 8d 1h to 30d.
Timing profile Balanced Conservative, balanced, or fast.
Maximum expanded hosts 256 1 to 1000000; scans also stop at scanner.max_job_hosts.
Baseline samples 2 in the console; 1 when omitted through the API 1 to 100.
Change confirmations 1 1 to 100.
  • timezone is omitted by default: the daemon and CLI keep the process timezone (UTC in the container image), and each signed-in console shows its browser’s timezone. Set it to an IANA name such as Europe/Amsterdam to use one timezone everywhere. The public status page keeps the visitor’s browser timezone and never receives the configured value. Invalid names stop startup; host recovery commands ignore them.
  • The web listener defaults to 127.0.0.1:8080; non-loopback listeners are rejected.
  • Requests using a proxy or tunnel host must match web.allowed_hosts; foreign Host headers are rejected before authentication. Keep this list limited to names you control.
  • Forwarding headers are ignored unless the connecting proxy addresses are explicitly listed in web.trusted_proxies. By default, EdgeWatch reads only web.forwarded_header: x-forwarded-for; set it to forwarded only when your trusted proxy controls that header, or none to ignore forwarded client IPs. EdgeWatch never combines the two conventions, so configure the header that your proxy sanitizes or constructs for the trusted proxy chain.
  • Session cookies use the same trusted-proxy boundary for forwarded HTTPS protocol headers. A trusted TLS-terminating proxy must send X-Forwarded-Proto: https or Forwarded: ...;proto=https when it forwards a loopback Host; otherwise EdgeWatch keeps the direct-loopback HTTP behavior.
  • If a tunnel or reverse proxy is not listed in web.trusted_proxies, every client may appear as the same loopback peer. After five failed login or TOTP attempts within five minutes, all logins through that shared peer receive a short two-second cooldown instead of a five-minute lockout. A successful sign-in through the peer, with any account, does not reset the count; each failure expires five minutes after it happened. Applying the same cooldown to known and unknown usernames avoids revealing account existence. The first-run setup, the platform setup, and account activation through that peer get the same cooldown after five wrong tokens, so wrong tokens cannot block them for five minutes. Password and TOTP confirmations through that peer are limited per account: an account that fails five confirmations within five minutes is refused for five minutes, and other accounts, in any unit or on the platform, are not affected. Configure the proxy network and forwarding header when you need per-client rate limits and audit identities. EdgeWatch logs a startup warning when approved proxy hosts lack trusted client-IP forwarding.
  • A client identified by its own address may fail five sign-ins within five minutes. Every failed sign-in counts the same: an unknown username, a disabled account, an account whose unit is not active, and a wrong password, one-time code, or recovery code. After that, every sign-in from that client, with any username, receives the same 429 rate_limited answer for five minutes, so neither the answer nor the number of attempts left reveals which accounts exist. A successful sign-in does not reset the count; each failure expires five minutes after it happened. Clients that share one address, such as the clients of an untrusted proxy on another host, share this budget, and a hundred wrong setup or activation tokens from that address block setup and activation for all of them for five minutes. When requests come through a proxy that EdgeWatch does not trust, EdgeWatch logs a warning at most once an hour and shows the proxy’s address on the dashboard of a single unit’s administrators and on the platform status page. Such a proxy is a peer that is not listed in web.trusted_proxies and sends X-Forwarded-For or Forwarded, such as an unlisted proxy on the host, or, behind the listed proxies, the first unlisted address in the forwarding chain when the chain names another client before it, such as an unlisted proxy on another host in front of the proxy on the host. A client can send these headers itself and have its own address shown, so add the address to web.trusted_proxies only when it is a proxy that you run.
  • The rate limits count an IPv6 client by its network of web.ipv6_rate_limit_prefix bits, a /64 by default, so all addresses of one /64 share the sign-in budget and the other per-client limits, including the anonymous limits of the public pages. IPv4 and loopback addresses are counted as they are, and audit records keep the full address. Set web.ipv6_rate_limit_prefix: 128 when unrelated clients share one /64, or a shorter prefix, down to 32, when one client holds a larger network.
  • Each account with TOTP may fail ten one-time or recovery codes within 24 hours, whichever clients send them; only a sign-in with the right password or a TOTP confirmation of a signed-in account counts. After that, the account’s codes are not checked for 15 minutes: every sign-in with the right password gets the answer of a wrong code, whatever code it carries, and a confirmation is refused without spending its code. While the ten latest wrong codes are less than 24 hours old, each further wrong code starts another lockout, twice as long as the one before, up to four hours. Each lockout is recorded once as auth.second_factor_locked. Unless the account’s owner sent the codes, someone else holds the account’s password, so change it. The counts are kept in memory, so restarting the daemon starts them over.
  • Each open console holds one live-update stream, and one account at most four. With more than one active business unit, each unit may hold an equal share of web.max_live_streams, at least four and at most web.max_live_streams_per_unit, so busy units cannot lock the others out while the active units number at most a quarter of web.max_live_streams. A stream over a limit is told to retry later and the console keeps reconnecting. Raise web.max_live_streams for more than 64 active units or many consoles per unit.
  • Sessions end after 24 hours without activity and 30 days after sign-in; the daemon removes ended sessions at startup and once a day. An account keeps at most 20 sessions: a new sign-in beyond that ends the account’s least recently used session. A TOTP code or recovery code counts as used only when its sign-in creates a session.
  • By default, scanner.target_exclusions covers the loopback and link-local ranges 127.0.0.0/8, ::1/128, 169.254.0.0/16, and fe80::/10. The IPv4 link-local range includes the 169.254.169.254 cloud metadata endpoint. Other metadata endpoints are not excluded by default; add the ones your provider uses, such as fd00:ec2::254/128 on AWS with the IPv6 instance metadata endpoint enabled or 100.100.100.200/32 on Alibaba Cloud. An explicit list replaces the defaults, so keep the default ranges when you add entries. Change scanner.target_exclusions only when you understand the host-network exposure. The same list keeps a unit’s notification destinations away from these addresses, and EdgeWatch refuses a unit’s destination on an unspecified, loopback, or link-local address whatever the list holds. An explicitly empty list, [], allows every address for both; see destination addresses.
  • scanner.sandbox: auto starts Nmap and Naabu as UID 65532 with only their raw-packet capabilities when the container grants SETUID, SETGID and KILL, as the bundled compose.yaml does. Otherwise they run as UID 0 and EdgeWatch warns. Set required to refuse to scan without the sandbox. off also turns off Landlock.
  • scanner.landlock: auto also restricts Nmap and Naabu with Landlock to the system files a scan reads and the files EdgeWatch passes to them, and lets only Naabu create files, below /tmp, when the kernel provides it. It installs a seccomp filter that refuses the system calls no scanner needs and makes the kernel’s out-of-memory killer stop a scanner before the daemon. Set required to refuse to scan without Landlock, or off, which turns off the filter and the limits too, if a scanner needs files outside those paths.
  • EdgeWatch keeps the files it passes to scanners, Nmap’s XML output and Naabu’s target list, in tmp/scanner beside the database, which only the daemon can open, whatever TMPDIR names; see scanner files.
  • scanner.max_job_hosts, 65,536 by default, a /16, caps the addresses the targets of one job expand to when it scans, whatever the job’s own max_expanded_hosts allows: a scan keeps a scope and a result for every address in the daemon’s memory, which every business unit shares. A job whose targets expand to more fails to scan with expanded targets exceed scanner.max_job_hosts=65536; split its targets into several jobs. The daemon logs such jobs at startup, edgewatch health lists them in warnings, and the job editor warns while you enter them. Raise the setting only with matching container memory: a resumable scan of a /16 peaked at about 344 MB in the 512 MiB limit of the bundled compose.yaml, and a /14 did not fit. Only config.yaml sets it, never a unit or the API. A resumable cycle that a release before v0.36.0 planned keeps its pinned addresses and is not checked again; discard it to replan a job whose targets now exceed the setting.
  • notifications.sandbox: auto delivers notifications from a process that runs as UID 65531 without capabilities, restricted with Landlock, when the container grants SETUID, SETGID and KILL and that process can read the certificate authorities SSL_CERT_FILE and SSL_CERT_DIR name. Set required to refuse to start without it, or off to deliver from an unconfined process.
  • RDAP is enabled by default and is requested only when an authenticated user opens a public host. Private and special-use addresses are never queried. Set enrichment.rdap.enabled: false for isolated or privacy-sensitive deployments. RDAP lookups need direct HTTPS egress to IANA and the regional registries: they ignore HTTPS_PROXY and the other proxy variables, which update checks and notifications use, because a proxy would resolve and connect to the registry itself, past the checks that keep lookups away from private addresses. Where only a proxy reaches the internet, host pages show RDAP as unavailable; set enrichment.rdap.enabled: false there. The daemon logs a warning at startup when RDAP is enabled and a proxy variable is set.
  • Scheduled backups are off by default. Setting backup.directory turns them on: the daemon writes a backup there on backup.schedule, checks it as the backup command does before it publishes it, and keeps the newest backup.keep of them. It removes only its own files, named edgewatch-scheduled-YYYYMMDDTHHMMSSZ.db, and never another file in the directory. edgewatch health and the console report the newest good backup and any failure.
  • Update checks are enabled by default, run at startup and every three hours, and consider stable GitHub releases only. Set updates.enabled: false for offline deployments. Checks reveal the host’s public IP and EdgeWatch user agent to GitHub; EdgeWatch reports updates but never upgrades itself.

Validate the edited file in a fresh container before you restart the service. A running container can still see the previous file when an editor replaces the bind-mounted file instead of rewriting it, so docker compose exec could validate the old configuration:

Terminal window
docker compose run --rm --no-deps edgewatch config validate \
--config /etc/edgewatch/config.yaml

When the result is "valid": true, recreate the container so the daemon reads the new file:

Terminal window
docker compose up -d --force-recreate edgewatch