Skip to content

Configuration Reference

The exhaustive reference for every Protection Plus v1.0.0 configuration value. Defaults on this page are verified against applyDefaults() in internal/config/config.go — not against comments in the example file.

Protection reads a single YAML file, by default /etc/protection/config.yaml. Generate a documented starter with:

bash
protection config init /etc/protection/config.yaml

Validate any config without running the daemon:

bash
protection config check /etc/protection/config.yaml

HOW DEFAULTS ACTUALLY WORK

Every value you omit falls back to a built-in default — except enabled flags and dry_run, which are plain booleans whose zero value is false. A near-empty config therefore starts Protection with no detectors, no alert channels, and no action backends (and armed — see the dry_run warning below). The generated starter config is what turns the core feature set on; treat it as the real baseline, not as decoration.

The file has eight top-level sections:

yaml
general:    # daemon-wide settings
detectors:  # per-detector tuning
intel:      # threat-intel (YARA rules + hash blocklist) management
alerts:     # notification channels
actions:    # enforcement backends
whitelist:  # trusted paths/containers, exempt from everything
limits:     # optional resource-safety limits (all opt-in)
rules:      # threat → action policy

LIST VALUES REPLACE, NOT EXTEND

For every built-in signature list (known_processes, pool_ports, whitelist_processes, known_tools, …) the daemon uses the built-in default only when your list is empty. The moment you set one item in YAML, your list replaces the built-in list entirely. To extend a list, copy the defaults (shown below) and add yours. This bites hardest on whitelist_processes: setting it drops the built-in game-server exemptions, and legitimate game servers start tripping the CPU/connection heuristics.


general

Daemon-wide settings.

yaml
general:
  name: "node-fra-01"
  mode: both
  scan_interval: 5s
  cooldown: 5m
  log_level: info
  log_file: /var/log/protection.log
  dry_run: true
PathTypeDefaultDescription
general.namestringhostname → primary IP → ProtectionHuman label for this installation, shown in every alert (Discord author/footer, email subject, webhook payload). Set it to something that tells you which node is paging you at 3am.
general.modeenumbothWhat to protect: server (host processes only), docker (containerised threats only), or both. Anything else is rejected at startup. See Modes.
general.scan_intervalduration5sHow often detectors run; also the CPU/disk sampling cadence. Lower = faster detection, more CPU. 10s is fine on small nodes; do not go below 2s.
general.cooldownduration5mSuppresses duplicate alerts/actions for the same threat on the same target within this window. Raise it against alert fatigue; lower it for faster re-alerts on persistent threats.
general.log_levelenuminfodebug (noisy, troubleshooting only), info, warn, or error.
general.log_filestring(empty — stderr/journald only)If set, logs are tee'd to this file as well. Size-based rotation is available under limits; leave limits.log_max_size_mb: 0 if you prefer logrotate.
general.dry_runboolfalse — no code defaultWhen true, destructive actions are logged but not executed; alerts still fire. See the warning below.
general.hostnamestringauto (os.Hostname())Overrides the detected hostname. Rarely needed.

UNSET dry_run MEANS ARMED

dry_run has no default in code — it is a plain boolean, so if you omit it you get false, and false means Protection will kill processes/containers, quarantine files, and suspend servers the moment a rule matches. The starter config sets dry_run: true explicitly; if you hand-write a minimal config you do not get that safety. Always set dry_run deliberately. Run dry for a few days on a new node, review the alerts, then set false and systemctl restart protection.

Modes

modeEvents kept
serverHost/VPS only — events without a container or Pterodactyl server
dockerContainer-related events only
bothEverything (default)
Common mistake — events silently dropped

If you set mode: docker on a bare VPS, host-process threats are filtered out and you'll see "no threats" even when a host miner is running. Use both if unsure.


detectors

Each detector is independent and toggled with its own enabled flag. Every enabled defaults to false — the starter config turns on miner, portscan, ddos, zipbomb, exploit, abuse, fim and onaccess; yara and trivy stay opt-in.

detectors.miner

Cryptocurrency-miner detection: signature matching, mining-pool connections, and a sustained-CPU heuristic for unknown miners.

yaml
detectors:
  miner:
    enabled: true
    cpu_threshold: 85
    sustained_seconds: 45
    known_processes: []   # replaces the built-in list if set
    pool_ports: []        # replaces the built-in list if set
    pool_domains: []      # parsed, currently unused (see warning)
    whitelist_processes: []  # replaces the built-in game-server list if set
PathTypeDefaultDescription
miner.enabledboolfalse (starter sets true)Enable miner detection.
miner.cpu_thresholdfloat85Per-core CPU percent that counts as "high". Game servers legitimately sit at 90%+, which is what whitelist_processes is for. Recommended range 80–90.
miner.sustained_secondsint45How long CPU must stay above the threshold before flagging. Miners are constant; game servers spike and dip — 45s separates them well. Raise to 120 on noisy nodes instead of disabling the detector.
miner.known_processeslistbuilt-in (xmrig, minerd, t-rex, nbminer, xmr-stak, … 23 names)Miner binary names flagged on sight. Replaces the built-in list when set.
miner.pool_portslist<int>built-in (3333, 4444, 5555, 7777, 8888, 9999, 14433, 14444, 45560, 45700, 20580)Mining-pool ports. Only public remote IPs are checked, so local services on these ports are safe. Replaces the built-in list when set.
miner.pool_domainslistbuilt-in (15 pool domains)Parsed and defaulted but currently unused — reserved for forward compatibility. Setting it has no effect in v1.0.0.
miner.whitelist_processeslistbuilt-in game servers (java, bedrock_server, srcds, RustDedicated, fivem, valheim, cs2, gmod, …)Processes skipped by the CPU heuristic. Known-miner signatures and pool connections still apply to them. Replaces the built-in list when set — set it carelessly and every game server trips the CPU heuristic.

pool_domains DOES NOTHING YET

miner.pool_domains is parsed into the config (and defaulted) but no detector reads it in v1.0.0. Don't rely on it for detection — pool connections are caught by pool_ports and known-process signatures.

Common mistake — too many CPU alerts

A busy game server can legitimately peg a core. Raise sustained_seconds (e.g. 120) and/or cpu_threshold rather than disabling the detector — signature and pool-connection detection still work regardless of CPU.

detectors.portscan

Flags processes fanning out half-open (SYN_SENT) connections — the signature of nmap/masscan-style scanning from your node.

yaml
detectors:
  portscan:
    enabled: true
    distinct_ports: 100
    distinct_hosts: 50
    window: 15s
    known_scanner_processes: []
PathTypeDefaultDescription
portscan.enabledboolfalse (starter sets true)Enable port-scan detection.
portscan.distinct_portsint100Distinct destination ports within window that flag a scan. Lower = more sensitive, more false positives from peer-to-peer software.
portscan.distinct_hostsint50Distinct destination hosts within window that flag a scan.
portscan.windowduration15sSliding window over which half-open connections are counted.
portscan.known_scanner_processeslistbuilt-in (nmap, masscan, zmap, unicornscan, hping3, naabu, rustscan)Scanner binary names flagged on sight. Replaces the built-in list when set.

detectors.ddos

Outbound flood detection — your customers attacking other people. This does not mitigate inbound attacks (use your XDP/firewall layer for that).

yaml
detectors:
  ddos:
    enabled: true
    pps_threshold: 60000
    bps_threshold: 125000000
    conn_threshold: 1500
    known_tools: []
    whitelist_processes: []
PathTypeDefaultDescription
ddos.enabledboolfalse (starter sets true)Enable outbound-flood detection.
ddos.pps_thresholdint60000Outbound packets/sec per container (via Docker stats) that flags a flood. Game servers rarely exceed 10–20k pps; lower to e.g. 20000 on small hosts for earlier warnings.
ddos.bps_thresholdint125000000Outbound bytes/sec per container (~1 Gbit/s). Set to ~80% of your node's uplink so a single container can't saturate you.
ddos.conn_thresholdint1500Simultaneous outbound connections (or active UDP sockets) from one process that flags a connection flood. Tor exits and floods hold thousands; game servers hold dozens per player. 500 is aggressive.
ddos.known_toolslistbuilt-in (hping3, t50, mhddos, slowloris, goldeneye, xerxes, loic, … 15 names)Stress-tool signatures, word-boundary matched to avoid false positives. Replaces the built-in list when set.
ddos.whitelist_processeslistbuilt-in game-server listSkipped by the connection-flood heuristic; docker-stats rates and tool signatures still apply. Replaces the built-in list when set.

TIP

Container egress thresholds (pps/bps) only apply when Docker is reachable. Tool-signature and connection-flood detection work without Docker.

detectors.zipbomb

Decompression-bomb detection. Archives are inspected from their metadata (never extracted), so scanning is safe and cheap.

yaml
detectors:
  zipbomb:
    enabled: true
    scan_paths:
      - /var/lib/pterodactyl/volumes
    ratio_threshold: 150
    max_uncompressed: 53687091200   # 50 GiB
    hot_trigger: true
    hot_cpu_percent: 80
    hot_write_mbps: 25
    full_scan_interval: 30m
    full_scan_max_duration: 5m
    min_compressed_size: 10240
    inspect_timeout: 5s
    max_concurrent_inspects: 4
    probe_compressed_limit: 1048576
    probe_uncompressed_limit: 10485760
    max_nesting: 3                  # parsed, currently unused
PathTypeDefaultDescription
zipbomb.enabledboolfalse (starter sets true)Enable zip-bomb detection.
zipbomb.scan_pathslist[/var/lib/pterodactyl/volumes]Directories walked for archives. Point at your real panel volumes and anywhere users can upload. protection config init --scan-paths fills this in (ALL = all user-writable dirs).
zipbomb.ratio_thresholdfloat150Uncompressed÷compressed ratio that flags a bomb. Real bombs are >1000:1; media archives ~1:1; text logs ~10:1 — 150 is safe.
zipbomb.max_uncompressedint (bytes)53687091200 (50 GiB)Absolute uncompressed-size ceiling regardless of ratio. Lower it on small disks (e.g. 10 GiB).
zipbomb.hot_triggerbooltrueEvent-driven path: a process spiking CPU + disk writes (measured per process group, so tar+gzip pipelines are caught) has its open archives inspected immediately instead of waiting for the next sweep. Keep this on.
zipbomb.hot_cpu_percentfloat80Per-core CPU that, with high disk writes, signals an active extraction.
zipbomb.hot_write_mbpsfloat25Disk write rate (MB/s) that, with high CPU, signals an active extraction.
zipbomb.full_scan_intervalduration30mSlow backstop sweep of scan_paths — catches bombs uploaded but not yet extracted.
zipbomb.full_scan_max_durationduration5mA sweep stops after this and resumes on the next tick from the cached cleared-file list. Bounds I/O on huge volumes.
zipbomb.min_compressed_sizeint (bytes)10240 (10 KiB)Archives smaller than this are skipped — they can't hurt you.
zipbomb.inspect_timeoutduration5sHard cap per archive inspection.
zipbomb.max_concurrent_inspectsint4Worker-pool size for sweeps. Raise on fast NVMe nodes, lower on spinning disks.
zipbomb.probe_compressed_limitint (bytes)1048576 (1 MiB)Bounded probe: at most this many compressed bytes are read…
zipbomb.probe_uncompressed_limitint (bytes)10485760 (10 MiB)…and at most this many bytes are decompressed during a probe. Inspection can never be turned against you as a bomb itself.
zipbomb.max_nestingint3Parsed and defaulted but currently unused — reserved for nested-archive inspection depth (forward compatibility). Setting it has no effect in v1.0.0.
Common mistake — scanning the wrong path

On a custom Pterodactyl/Wings layout, point scan_paths at your real volumes directory. And don't add / "just in case" — sweeping the whole filesystem every 30 minutes is pure I/O waste; the hot trigger already covers live extractions.

detectors.exploit

Exploit and container-escape detection: reverse shells, privilege escalation, setuid droppers in world-writable dirs.

yaml
detectors:
  exploit:
    enabled: true
    flag_reverse_shell: true
    flag_privilege_escalation: true
    watch_paths: [/tmp, /dev/shm, /var/tmp]
    suspicious_processes: []
PathTypeDefaultDescription
exploit.enabledboolfalse (starter sets true)Enable exploit / container-escape detection.
exploit.flag_reverse_shellboolfalse (starter sets true)Flag network-bound reverse-shell patterns (/dev/tcp/, nc -e, socat exec, …).
exploit.flag_privilege_escalationboolfalse (starter sets true)Flag privesc/escape patterns (sudoers tampering, chmod +s, setcap, nsenter --target 1, …).
exploit.watch_pathslist[/tmp, /dev/shm, /var/tmp]World-writable dirs walked for setuid payloads and execution-from-scratch. Add custom tmpfs mounts if you have them.
exploit.suspicious_processeslistbuilt-in (dirtycow, dirtypipe, pwnkit, linpeas, nsenter, runc, deepce, …)Exploit/privesc tool names flagged on sight. Replaces the built-in list when set.

THE flag_* BOOLEANS ALSO DEFAULT TO false

Like every other plain boolean in this file, flag_reverse_shell and flag_privilege_escalation are false when omitted — the starter config enables both. If you hand-roll a minimal config and only write exploit.enabled: true, you get setuid scanning of watch_paths but no reverse-shell or privesc pattern matching. Set the flags explicitly.

detectors.abuse

Hosting-abuse services: Tor exits/relays, proxy/VPN tunnels (OpenVPN, WireGuard, Shadowsocks, v2ray/xray, trojan, hysteria, sing-box, ngrok, frp), spam mail servers, IRC bots, and execution out of user upload directories.

yaml
detectors:
  abuse:
    enabled: true
    known_processes: []      # replaces built-in list
    known_cmd_patterns: []   # replaces built-in list
    abusive_ports: []        # replaces built-in list
    watch_upload_paths: []   # replaces built-in list
    whitelist_processes: []  # replaces built-in game-server list
PathTypeDefaultDescription
abuse.enabledboolfalse (starter sets true)Enable abuse detection.
abuse.known_processeslistbuilt-in (tor, openvpn, wireguard, shadowsocks, v2ray, xray, trojan, hysteria, sing-box, cloudflared, frpc/frps, ngrok, sendmail, postfix, exim, znc, eggdrop, …)Abuse-service binary names flagged on sight. Replaces the built-in list when set.
abuse.known_cmd_patternslistbuilt-in (--orport, --socks-port, -f /etc/tor, --protocol vmess, …)Command-line substrings that strongly indicate abuse services. Replaces the built-in list when set.
abuse.abusive_portslist<int>built-in (Tor 9001/9030/9050/9051/9150/9151, SOCKS 1080/1081/1090, SS 8388/8389, trojan 4433/8443, …)Listening ports that indicate abuse tunnels. Game servers should not normally bind these. Replaces the built-in list when set.
abuse.watch_upload_pathslist[/var/lib/pterodactyl/volumes, /var/lib/pterodactyl/mounts, /home/container, /tmp, /var/tmp, /dev/shm]Directories where customer-uploaded files live; executables must never run from here. Replaces the built-in list when set.
abuse.whitelist_processeslistbuilt-in game-server listExempt from abuse heuristics. Replaces the built-in list when set.
Common mistake — flagging your own mail/VPN

If you legitimately run a mail server or a WireGuard endpoint on the host, the abuse detector will flag it. Whitelist the specific container/process or host path via the top-level whitelist section rather than disabling the detector.

detectors.yara

Periodic full YARA sweep of scan_paths using the yara CLI (installed separately, e.g. apt install yara). No-op if the binary is missing. For instant per-file scanning prefer onaccess; this is the backstop sweep.

yaml
detectors:
  yara:
    enabled: false
    rules_dir: /etc/protection/yara
    scan_paths:
      - /var/lib/pterodactyl/volumes
    interval: 10m
PathTypeDefaultDescription
yara.enabledboolfalseEnable the periodic YARA sweep. Opt-in even in the starter config.
yara.rules_dirstring/etc/protection/yaraDirectory containing .yar rule files. Populated by protection rules update / the intel section.
yara.scan_pathslist[/var/lib/pterodactyl/volumes]Directories swept each interval.
yara.intervalduration10mTime between sweeps. YARA on large volumes is I/O-heavy — don't go below a few minutes.

detectors.fim

File-integrity monitoring: alerts when the daemon's own binary, the config file, or any listed path changes on disk. Catches attackers tampering with Protection itself.

yaml
detectors:
  fim:
    enabled: true
    paths: [/etc/ssh/sshd_config]
    interval: 5m
PathTypeDefaultDescription
fim.enabledboolfalse (starter sets true)Enable file-integrity monitoring.
fim.pathslistthe protection binary + the loaded config filePaths to hash and watch. The defaults are added automatically in Load(); anything you list is added on top (this list appends, unlike signature lists). Add high-value targets like /etc/ssh/sshd_config or your panel config.
fim.intervalduration5mTime between integrity checks.

detectors.trivy

Container-image vulnerability scanning via the trivy CLI (installed separately). No-op if the binary is missing. Emits one event per image with HIGH/CRITICAL vulnerability counts. Alert-only by design.

yaml
detectors:
  trivy:
    enabled: false
    binary: trivy
    interval: 1h
    min_severity: medium
PathTypeDefaultDescription
trivy.enabledboolfalseEnable image scanning. Opt-in even in the starter config.
trivy.binarystringtrivyPath/name of the trivy executable. Change only for a non-PATH install.
trivy.intervalduration1hTime between scans. Image scans are expensive; hourly is already aggressive for large nodes.
trivy.min_severityenummediumMinimum vulnerability severity reported (medium, high, critical). Set high to cut noise.

detectors.onaccess

The antivirus hot path. Watches upload dirs with fsnotify and scans every file the moment it is closed after writing: SHA-256 against the hash blocklist, then YARA against the rule bundle. Matched files hit the malware rule (quarantine + alert by default).

yaml
detectors:
  onaccess:
    enabled: true
    watch_paths:
      - /var/lib/pterodactyl/volumes
    hash_check: true
    yara_check: true
    settle_ms: 500
PathTypeDefaultDescription
onaccess.enabledboolfalse (starter sets true)Enable on-access scanning.
onaccess.watch_pathslist[/var/lib/pterodactyl/volumes, /var/lib/pterodactyl/mounts, /home/container, /tmp, /var/tmp, /dev/shm]Directories watched with fsnotify. Replaces the built-in list when set.
onaccess.hash_checkbooltrueSHA-256 against the blocklist. Requires the blocklist to exist — run protection rules update once (see warning).
onaccess.yara_checkbooltrueYARA-scan each written file. Needs the yara CLI installed; silently does nothing without it.
onaccess.settle_msint500Milliseconds to wait for writes to settle before scanning a closed file. Raise if you scan partially-written large uploads; lower for faster verdicts.

RUN protection rules update ONCE

hash_check compares against /var/lib/protection/blocklist.sha256, which only exists after the first intel fetch. On a fresh install, run protection rules update once (or enable intel and let the daemon fetch on its schedule) or the hash half of on-access scanning has nothing to match against. yara_check similarly needs the yara binary installed and rules in intel.rules_dir.


intel

Threat-intel management: where the YARA rule bundle and the SHA-256 hash blocklist come from and how they refresh. protection rules update runs this once manually; the daemon refreshes automatically every update_interval when intel.enabled: true.

yaml
intel:
  enabled: true
  rules_dir: /etc/protection/yara
  rules_url: "https://raw.githubusercontent.com/AnAverageBeing/protection/main/rules/protection.yar"
  hashlist_url: "https://bazaar.abuse.ch/export/txt/sha256/recent/"
  hashlist_file: /var/lib/protection/blocklist.sha256
  custom_hashlist: ""
  update_interval: 24h
PathTypeDefaultDescription
intel.enabledboolfalse (starter sets true)Enable automatic intel refresh. With false, intel is only fetched when you run protection rules update by hand.
intel.rules_dirstring/etc/protection/yaraWhere the downloaded rule bundle is written (also the default yara.rules_dir).
intel.rules_urlstringcurated bundle on GitHub (webshells, miners, tor, mirai, IRC bots, privesc)Point at your own URL to self-host rules.
intel.hashlist_urlstringMalwareBazaar recent SHA-256 export (last ~48h)See the RAM trade-off warning below before switching to full.
intel.hashlist_filestring/var/lib/protection/blocklist.sha256Local on-disk blocklist the on-access scanner matches against.
intel.custom_hashliststring(empty)Path to your own extra hashes, one SHA-256 per line. Merged into the blocklist, never overwritten by updates.
intel.update_intervalduration24hAutomatic refresh cadence. MalwareBazaar's recent feed moves fast; daily is the intended cadence — don't hammer it.

full HASHLIST = HUNDREDS OF MB OF RAM

The default recent export covers roughly the last 48 hours of MalwareBazaar submissions — small and fast. Switching hashlist_url to https://bazaar.abuse.ch/export/txt/sha256/full/ gives complete coverage at ~1.1M+ hashes, which the daemon holds as an in-memory set costing a few hundred MB of RAM. On 1–2 GB game nodes that alone can OOM the box. Only use full on nodes with RAM to spare.


alerts

Notification channels. Each channel has its own min_severity gate; the ordering is info < low < medium < high < critical. An empty or unrecognised min_severity parses to medium — a typo never silently disables a channel (or a rule).

alerts.discord

yaml
alerts:
  discord:
    enabled: false
    webhook_url: ""
    username: Protection
    min_severity: medium
PathTypeDefaultDescription
discord.enabledboolfalseEnable Discord webhook alerts.
discord.webhook_urlstring(required if enabled — startup fails without it)The Discord webhook URL.
discord.usernamestringProtectionWebhook display name.
discord.min_severityenummedium (also the fallback for empty/invalid)Lowest severity that triggers a Discord alert. medium is a good default; use high on quiet channels.

alerts.smtp

yaml
alerts:
  smtp:
    enabled: false
    host: smtp.example.com
    port: 587
    username: alerts@example.com
    password: ""
    from: alerts@example.com
    to: [admin@example.com]
    tls: true
    min_severity: high
PathTypeDefaultDescription
smtp.enabledboolfalseEnable email alerts. Startup fails if host or to are missing while enabled.
smtp.hoststring(required if enabled)SMTP server hostname.
smtp.portintnone — you must set it587 = STARTTLS (negotiated automatically), 465 = implicit TLS (needs tls: true). There is no code default; an unset port produces host:0 and every send fails.
smtp.usernamestring(optional)Auth username. Omit both credentials for an open relay.
smtp.passwordstring(optional)Auth password.
smtp.fromstring(required in practice)Envelope/From address.
smtp.tolist(required if enabled)Recipient addresses.
smtp.tlsboolfalse unless setOnly used with port: 465 (implicit TLS). Ignored on 587, where STARTTLS is always attempted.
smtp.min_severityenummedium fallback (starter suggests high)Lowest severity that triggers email. Email is slow and easy to ignore — gate it high.

alerts.webhook

yaml
alerts:
  webhook:
    enabled: false
    url: ""
    method: POST
    headers:
      Authorization: "Bearer changeme"
    min_severity: medium
PathTypeDefaultDescription
webhook.enabledboolfalseEnable the generic JSON webhook. Startup fails if url is empty while enabled.
webhook.urlstring(required if enabled)Endpoint that receives { "installation": …, "event": {…} } as JSON. Ideal for SIEM/automation.
webhook.methodstringPOST (applied at send time)HTTP method.
webhook.headersmap(none)Custom headers, e.g. auth tokens.
webhook.min_severityenummedium (fallback)Lowest severity that triggers the webhook.

alerts.batch

Alert aggregation: when more than threshold alerts fire inside window, they collapse into one digest alert. Enable on busy nodes so a burst pages you once, not 50 times.

yaml
alerts:
  batch:
    enabled: false
    threshold: 10
    window: 1m
PathTypeDefaultDescription
batch.enabledboolfalseEnable digest batching.
batch.thresholdint10Burst size that triggers collapsing into a digest.
batch.windowduration1mWindow the threshold is counted over.

See Alerts & Notifications for payload formats.


actions

Enforcement backends. An action in a rule only works if its backend is enabled here.

yaml
actions:
  docker:
    enabled: true
    socket: /var/run/docker.sock
  pterodactyl:
    enabled: false
    url: https://panel.example.com
    api_key: ""
  file:
    enabled: true
    quarantine_dir: /var/lib/protection/quarantine
PathTypeDefaultDescription
docker.enabledboolfalse (starter sets true)Enable container actions (kill_container, stop_container) and container egress stats for the ddos detector.
docker.socketstring/var/run/docker.sockDocker Engine API socket.
pterodactyl.enabledboolfalseEnable suspend_server. Startup fails if url/api_key are missing while enabled.
pterodactyl.urlstring(required if enabled)Panel base URL.
pterodactyl.api_keystring(required if enabled)Application API key with server read + suspend.
file.enabledboolfalse (starter sets true)Enable quarantine_file / delete_file.
file.quarantine_dirstring/var/lib/protection/quarantineWhere quarantined files are moved and chmod 000'd. Keep it on a partition an attacker can't fill to block quarantine.

PTERODACTYL KEY TYPE

pterodactyl.api_key must be an Application API key (ptla_…), not a Client key (ptlc_…). It needs permission to read servers and toggle suspension.

delete_file IS DESTROYING EVIDENCE

Prefer quarantine_file in your rules until you trust your false-positive rate — quarantine preserves the file (mode 000) for inspection; delete_file is unrecoverable.


whitelist

Trusted targets exempt from everything. Whitelisted paths are never scanned or flagged, even if they also fall under a scan/watch path; whitelisted containers are never flagged, killed, or suspended.

yaml
whitelist:
  paths: [/srv/trusted-builds]
  containers: ["3f4a9b2c1d", "my-admin-container"]
PathTypeDefaultDescription
whitelist.pathslist(empty)Matched by prefix: a whitelisted directory exempts everything beneath it (/srv/trusted-builds covers /srv/trusted-builds/x/y). Trailing slashes are trimmed. Use for known-good build dirs or admin script trees.
whitelist.containerslist(empty)Matched by full ID, short ID, or exact name (prefix comparison runs in both directions, so any unambiguous ID prefix works). Use for your own admin/monitoring containers.
Common mistake — whitelisting too broad a path

whitelist.paths: [/var/lib/pterodactyl] exempts every customer volume — you've just disabled most of Protection. Whitelist the narrowest path that solves your false positive.


limits

Optional resource-safety limits for the daemon itself. Every field is opt-in and disabled at its zero value — upgrades never change behavior. Turn these on if the daemon ever misbehaves on a huge or heavily-loaded node.

yaml
limits:
  detector_timeout: 0s
  max_alerts_per_minute: 0
  max_setuid_walk_files: 0
  cache_directory_mtimes: false
  log_max_size_mb: 0
  log_max_backups: 0
PathTypeDefaultDescription
limits.detector_timeoutduration0s (disabled)Hard cap on how long a single detector may run per tick. Set e.g. 30s if a slow detector (zipbomb sweeps on huge volumes) ever stalls the loop.
limits.max_alerts_per_minuteint0 (disabled)Global alert rate limit across all channels. A last-resort flood brake; prefer alerts.batch for normal burst control.
limits.max_setuid_walk_filesint0 (disabled)Caps how many files the exploit detector walks per setuid scan of its watch paths.
limits.cache_directory_mtimesboolfalseSkip walking directories whose mtime hasn't changed since the last zip/exploit scan. Big I/O win on large, mostly-static volumes; tiny risk of missing a change that doesn't bump dir mtime.
limits.log_max_size_mbint0 (disabled)Rotate general.log_file at this size. Leave 0 if you use logrotate.
limits.log_max_backupsint0Rotated log files to keep when rotation is enabled.

rules

Rules map detected threats to enforcement. They're evaluated top-to-bottom; every matching rule contributes its actions (union). If you omit the section entirely, the built-in policy below is used.

yaml
rules:
  - name: miners
    categories: [miner]
    min_severity: high
    actions: [neutralize, suspend_server, alert]
  - name: ddos
    categories: [ddos]
    min_severity: high
    actions: [neutralize, suspend_server, alert]
  - name: abuse
    categories: [abuse]
    min_severity: high
    actions: [neutralize, suspend_server, alert]
  - name: malware
    categories: [malware]
    min_severity: high
    actions: [quarantine_file, alert]
  - name: exploits
    categories: [exploit]
    min_severity: high
    actions: [neutralize, alert]
  - name: zipbombs
    categories: [zipbomb]
    min_severity: medium
    actions: [quarantine_file, alert]
  - name: portscans
    categories: [portscan]
    min_severity: medium
    actions: [alert]
  - name: catch-all
    categories: ["*"]
    min_severity: low
    actions: [alert]
FieldTypeDescription
namestringHuman label for the rule.
categorieslistOne or more of miner, portscan, ddos, zipbomb, exploit, abuse, malware, or * (any). Unknown categories simply never match.
min_severityenumMinimum event severity for this rule to match. Empty/unrecognised parses to medium — a typo never silently disables a rule.
actionslistActions to run: alert, neutralize, kill_container, stop_container, suspend_server, quarantine_file, delete_file, kill_process, log_only. Each action also needs its backend enabled under actions.

THE neutralize ACTION

neutralize is smart: it kills the container for containerised threats and the process for host threats, so one rule works on Docker nodes and bare VPS hosts alike. See Actions & Rules.

RULES DON'T BYPASS dry_run OR BACKENDS

A matching rule only produces an action if (a) general.dry_run is false and (b) the action's backend is enabled under actions:. With dry_run: true every enforcement action is logged, not executed. And malware events only exist if onaccess (or YARA) is running with intel in place — the rule alone detects nothing.

Common mistake — trailing/missing list items

YAML lists need consistent indentation. categories: [miner] (flow) and the block form both work, but don't invent categories — unknown ones never match, and Protection won't warn you.


Full annotated example

The bundled starter config (protection config init) contains every option above with inline comments. Pair this reference with that file when tuning a node — and remember the starter is what sets dry_run: true and enables the core detectors; a from-scratch minimal config gets neither.

Next steps