Rspamd first-time setup: the minimal config that actually works

Was this helpful?

Some spam filters fail loudly. Rspamd fails like a professional: it accepts mail, mutters into logs, and your users still forward you screenshots of “Bitcoin invoice.pdf.exe”. Meanwhile you’re staring at a score breakdown that looks correct, yet nothing is being rejected, signed, or learned.

This is a first-time setup guide for people who run production systems. Minimal config, not minimal thinking. We’ll get you to a working baseline: Rspamd scanning, DKIM signing, Redis-backed state, a secure UI, and an MTA integration that doesn’t silently bypass itself.

What “minimal” actually means in production

Minimal is not “fewest lines of config.” Minimal is “fewest moving parts that still give you predictable outcomes.” For Rspamd, that baseline is:

  • A running rspamd daemon and rspamd_proxy handling scanning.
  • Redis enabled for history, fuzzy, Bayes, and assorted caches (you can limp without it, but you’ll hate your future self).
  • MTA integration that cannot be “accidentally bypassed.” For Postfix that means a milter, and it means the socket is reachable from Postfix’s process context.
  • DKIM signing for outbound mail, with keys in a sane place and selectors you can rotate.
  • Action thresholds that match reality (reject on obviously-bad, add-header on borderline), and logs that tell you what happened.

What minimal is not:

  • Turning on every module because “maybe it helps.” That’s how you end up greylisting your own monitoring mail and calling it “security.”
  • Editing files under /etc/rspamd/ directly when you should use local.d and override.d.
  • Skipping DKIM because you’re “not ready.” Your recipients are ready; they’re ready to score you like a spammer.

One operational truth: if you can’t explain how a message got its final action (reject/add-header/no action), you don’t have a spam filter, you have a rumor.

A few facts and history that change decisions

These are the kind of small, concrete facts that stop you from making the wrong architectural bet at 2 a.m.

  1. Rspamd was designed from the start as a multicomponent system (worker types, proxy, controller). It’s not a monolith; you configure it like one, you’ll mis-diagnose it like one.
  2. It leans heavily on Redis for things beyond Bayes: cached results, reputation, fuzzy checks, and history. Treat Redis as part of the mail pipeline, not a “nice to have.”
  3. Rspamd’s scoring model is symbol-based: it doesn’t say “spam/not spam,” it says “here are reasons with weights.” That’s why tuning is survivable—if you don’t panic-edit scores.
  4. Modern deliverability is authentication-driven: DKIM, SPF, DMARC. Spam filtering helps inbound; DKIM helps outbound not look like a scam.
  5. Milters are old but effective. They’re also easy to miswire. Most “Rspamd isn’t filtering” reports are “the MTA never called it.”
  6. Rspamd supports multiple integration patterns: milter, LMTP proxying, SMTP proxying. “Minimal that works” depends on your MTA, but the failure modes are similar: bypass, timeouts, wrong sockets.
  7. DKIM key rotation is operationally cheap if you use selectors well. If you hardcode one selector everywhere forever, you’ll eventually schedule a scary maintenance window for a non-scary task.
  8. Rspamd has a built-in HTTP controller for stats and commands. Exposing it without auth is a small, avoidable tragedy.

Minimal working architecture (and what to skip)

Here’s the baseline architecture that tends to “just work” on a single mail host running Postfix:

  • Postfix receives mail and hands it to a milter socket.
  • rspamd_proxy accepts milter requests, calls scanning workers, then tells Postfix what to do and adds headers.
  • rspamd uses Redis locally for caches and Bayes, and stores history for debugging.
  • DKIM signing happens inside Rspamd for outbound mail (Postfix passes mail through the same milter).

What to skip at first:

  • Clustering Rspamd across multiple nodes. Not because it’s bad, but because you’ll confuse network issues with scoring issues.
  • Exotic modules (multiple OCR systems, custom Lua rules). First make sure the mail is being scanned at all.
  • Bayes training automation before you have correct ham/spam separation and stable scoring. Automating a wrong dataset is a very efficient way to be wrong.

Joke #1: A spam filter without logs is like a parachute made of opinions—technically lightweight, operationally brave.

Install and verify the daemon is really running

We’ll assume a Debian/Ubuntu-ish host with systemd. If you’re on something else, translate the package manager and service names, not the logic.

Install packages

cr0x@server:~$ sudo apt-get update
...output...
cr0x@server:~$ sudo apt-get install -y rspamd redis-server
...output...

Decision: If your distro packages are ancient, you’ll feel it in missing modules and bugs you shouldn’t debug. But “minimal that works” still works with distro packages—just don’t pretend it’s current.

Check services are up

cr0x@server:~$ systemctl status rspamd --no-pager
● rspamd.service - Rapid spam filtering system
     Loaded: loaded (/lib/systemd/system/rspamd.service; enabled; vendor preset: enabled)
     Active: active (running) since Sat 2026-01-03 09:01:12 UTC; 2min ago
...output...
cr0x@server:~$ systemctl status redis-server --no-pager
● redis-server.service - Advanced key-value store
     Loaded: loaded (/lib/systemd/system/redis-server.service; enabled; vendor preset: enabled)
     Active: active (running) since Sat 2026-01-03 09:01:03 UTC; 2min ago
...output...

Decision: If either is not active (running), stop. Fix that first. Mail scanning is a pipeline; a dead component is not “degraded,” it’s “not happening.”

Confirm Rspamd is listening

cr0x@server:~$ sudo ss -lntp | egrep '11334|11333|11332'
LISTEN 0      4096         127.0.0.1:11334      0.0.0.0:*    users:(("rspamd",pid=1827,fd=6))
LISTEN 0      4096         127.0.0.1:11333      0.0.0.0:*    users:(("rspamd",pid=1827,fd=7))
LISTEN 0      4096         127.0.0.1:11332      0.0.0.0:*    users:(("rspamd",pid=1827,fd=8))

What it means: default Rspamd ports are alive (proxy/controller/worker interfaces depend on packaging).

Decision: If these aren’t listening, you’re either misconfigured or the package uses different sockets (often UNIX). Don’t wire Postfix to a port you haven’t proven exists.

Config layout: how not to fight Rspamd

Rspamd config is layered. Treat the vendor config as read-only. Your job is to override cleanly.

  • /etc/rspamd/: base config shipped by the package
  • /etc/rspamd/local.d/: your local changes (most common)
  • /etc/rspamd/override.d/: hard overrides when you must stomp defaults

Rule: if you edit /etc/rspamd/rspamd.conf directly, you’re signing up for pain during upgrades and for “why did this change” archaeology.

Validate config before restarting

cr0x@server:~$ sudo rspamd -t
syntax OK

What it means: parser agrees with your files.

Decision: If this fails, do not restart. Fix syntax first; a broken spam filter tends to fail open in ways you won’t notice until users do.

Redis: the one dependency you shouldn’t cheap out on

Yes, you can run Rspamd without Redis. It will still scan. And you’ll still lose the visibility and state that make scanning reliable: Bayes, fuzzy, history, and caches. In production, Redis is not optional; it’s your memory.

Minimal Redis config for Rspamd

On a single host, use a local UNIX socket for speed and permissions clarity. Put this in /etc/rspamd/local.d/redis.conf:

cr0x@server:~$ sudo tee /etc/rspamd/local.d/redis.conf > /dev/null <<'EOF'
servers = "unix:/run/redis/redis-server.sock";
EOF

Now ensure Redis exposes that socket and Rspamd can read it. On Debian-like systems, it usually does. Verify:

cr0x@server:~$ ls -l /run/redis/redis-server.sock
srwxrwx--- 1 redis redis 0 Jan  3 09:01 /run/redis/redis-server.sock

What it means: group permissions matter. If rspamd isn’t in the redis group, it can’t talk.

Decision: Add rspamd to the redis group (or configure ACLs) rather than switching Redis to TCP “because it’s easier.” Easy today, incident tomorrow.

cr0x@server:~$ sudo usermod -aG redis rspamd
cr0x@server:~$ sudo systemctl restart rspamd
...output...

Confirm Rspamd can use Redis

cr0x@server:~$ sudo rspamadm configdump redis
servers = "unix:/run/redis/redis-server.sock";

Decision: If it still points elsewhere, you put the file in the wrong place or your package uses a different include order. Fix that before you tune anything else.

Postfix integration (milter) that actually filters mail

The main failure mode is embarrassingly simple: Postfix never calls Rspamd. Everything else you do—Redis, DKIM, Bayes—becomes performance art.

For minimal setup, use a local milter socket provided by Rspamd proxy. Many packages expose /run/rspamd/milter.sock. Verify first:

cr0x@server:~$ sudo find /run -maxdepth 2 -type s -name '*milter*sock' -o -name 'rspamd*.sock'
/run/rspamd/milter.sock

Decision: If you don’t have a socket, you either need to enable the milter worker or your package uses TCP. Don’t guess; inspect.

Configure Postfix to use the Rspamd milter

Edit Postfix main config. Use both SMTPd and non-SMTPd paths if you care about local submissions. Minimal, reliable defaults:

cr0x@server:~$ sudo postconf -e 'smtpd_milters=unix:/run/rspamd/milter.sock'
cr0x@server:~$ sudo postconf -e 'non_smtpd_milters=unix:/run/rspamd/milter.sock'
cr0x@server:~$ sudo postconf -e 'milter_protocol=6'
cr0x@server:~$ sudo postconf -e 'milter_default_action=accept'
cr0x@server:~$ sudo postconf -e 'milter_mail_macros=i {mail_addr} {client_addr} {client_name} {auth_authen}'

What it means:

  • milter_default_action=accept is conservative: if Rspamd is down, mail flows. This avoids total outages, but can be abused if attackers can force timeouts.
  • milter_protocol=6 is modern enough for common macro support.

Decision: In some environments, “fail open” is acceptable; in others, it’s a compliance problem. If you’re a high-value target, consider tempfail once you trust stability.

Reload Postfix and verify it’s using the socket

cr0x@server:~$ sudo systemctl reload postfix
...output...
cr0x@server:~$ sudo postconf | egrep 'smtpd_milters|non_smtpd_milters|milter_default_action'
smtpd_milters = unix:/run/rspamd/milter.sock
non_smtpd_milters = unix:/run/rspamd/milter.sock
milter_default_action = accept

Decision: If Postfix shows the right values but mail still isn’t being filtered, you likely have a socket permission problem or a chroot boundary issue.

Check Postfix can access the milter socket

cr0x@server:~$ sudo ls -l /run/rspamd/milter.sock
srwxrwx--- 1 rspamd rspamd 0 Jan  3 09:01 /run/rspamd/milter.sock

What it means: if the socket is rspamd:rspamd with 770, Postfix processes might not be in that group.

Decision: Add Postfix user to the rspamd group, or adjust socket permissions in Rspamd worker config. Don’t chmod 777 a security boundary and call it “temporary.” Temporary is just permanent with denial.

cr0x@server:~$ sudo usermod -aG rspamd postfix
cr0x@server:~$ sudo systemctl restart postfix
...output...

DKIM signing: minimal but correct

DKIM is not just deliverability theater. It’s a hard signal to receivers that your outbound mail is consistent, not forged, and not a fly-by-night.

Minimal DKIM in Rspamd needs:

  • A keypair stored on disk with correct permissions.
  • A selector (like s2026) you can rotate.
  • A mapping from your domain to selector and key path.

Generate a DKIM key

Create a directory for keys and lock it down:

cr0x@server:~$ sudo install -d -m 0750 -o rspamd -g rspamd /var/lib/rspamd/dkim

Generate a 2048-bit key (common baseline):

cr0x@server:~$ sudo rspamadm dkim_keygen -b 2048 -s s2026 -d example.com -k /var/lib/rspamd/dkim/s2026.example.com.key > /tmp/s2026.example.com.txt
cr0x@server:~$ sudo chown rspamd:rspamd /var/lib/rspamd/dkim/s2026.example.com.key
cr0x@server:~$ sudo chmod 0640 /var/lib/rspamd/dkim/s2026.example.com.key

What it means: the private key is readable by Rspamd; not by the world; not by random daemons.

Decision: If you’re tempted to put DKIM keys in /etc with loose permissions “so backups catch it,” fix backups instead. Secrets don’t become less secret because they’re inconvenient.

Configure the DKIM signing module

Create /etc/rspamd/local.d/dkim_signing.conf:

cr0x@server:~$ sudo tee /etc/rspamd/local.d/dkim_signing.conf > /dev/null <<'EOF'
path = "/var/lib/rspamd/dkim/$selector.$domain.key";
selector_map = "/etc/rspamd/dkim_selectors.map";
key_map = "/etc/rspamd/dkim_keys.map";
signing_table = "/etc/rspamd/dkim_signing.map";
use_esld = false;
allow_username_mismatch = true;
EOF

Now create the three small map files. Keep them explicit; “magic auto” is how you sign the wrong domain.

cr0x@server:~$ sudo tee /etc/rspamd/dkim_selectors.map > /dev/null <<'EOF'
example.com s2026
EOF
cr0x@server:~$ sudo tee /etc/rspamd/dkim_keys.map > /dev/null <<'EOF'
example.com /var/lib/rspamd/dkim/s2026.example.com.key
EOF
cr0x@server:~$ sudo tee /etc/rspamd/dkim_signing.map > /dev/null <<'EOF'
*@example.com example.com
EOF

What it means: any mail from @example.com gets signed with the example.com domain key, selector s2026.

Decision: If you host multiple domains, do not “reuse one key for all domains.” It works technically; it’s a deliverability and hygiene smell.

Restart and verify DKIM module loads

cr0x@server:~$ sudo rspamd -t
syntax OK
cr0x@server:~$ sudo systemctl restart rspamd
...output...
cr0x@server:~$ sudo journalctl -u rspamd -n 50 --no-pager
...output...

Decision: In logs, you want absence of DKIM errors. If you see “cannot load key,” it’s usually path mismatch or permissions.

Actions and thresholds: reject, add header, greylist

Rspamd can do a lot of actions. Minimal config is: add headers always, reject only for high confidence spam, and do something sane for borderline mail.

Put this in /etc/rspamd/local.d/actions.conf:

cr0x@server:~$ sudo tee /etc/rspamd/local.d/actions.conf > /dev/null <<'EOF'
actions {
  add_header = 6.0;
  rewrite_subject = 8.0;
  reject = 15.0;
  greylist = 4.0;
}
EOF

What it means: below 4 is clean; 4–6 greylist; 6–8 add headers; 8–15 rewrite subject; 15+ reject. Your numbers will vary. This set tends to be survivable for first deployment.

Decision: If your org has zero tolerance for delayed mail, set greylist higher or disable it initially. Greylisting is not “free accuracy”; it’s trading latency for signal.

Joke #2: Greylisting is like corporate procurement—everything gets approved eventually, just not while you’re still on the call.

Web UI: secure it or turn it off

The controller UI is useful. It’s also an operational foot-gun if you expose it to the Internet, because it can change configuration and trigger learns depending on permissions.

Bind controller to localhost (minimal safe default)

In many packages this is already the case. Verify and enforce in /etc/rspamd/local.d/worker-controller.inc:

cr0x@server:~$ sudo tee /etc/rspamd/local.d/worker-controller.inc > /dev/null <<'EOF'
bind_socket = "127.0.0.1:11334";
password = "$2$5$z0JwF4Tj9p7vVh2q3l8y8u3n7q9rHk0rHk0rHk0rHk0rHk0rHk0";
enable_password = "false";
EOF

What it means: controller is only reachable locally. The password hash shown is a placeholder format; generate your own with rspamadm pw.

Generate a controller password hash:

cr0x@server:~$ rspamadm pw -p 'ChangeThisNow'
$2$5$V1m0JwA3WcEoTg6hS2mY3uWJ5FzqKXHcGJ7x1VZy9uVw9d9QmGq2

Decision: If you need remote access, put it behind SSH port forwarding or a VPN. If you insist on binding it to a public interface, you also need firewall rules and strong auth. “It’s just a UI” is how you get mail policy changed by strangers.

Practical tasks (commands + outputs + decisions)

This section is the meat: concrete tasks you’ll run on day one. Each includes what the output means and the operational decision you make from it.

Task 1: Confirm which Rspamd workers are running

cr0x@server:~$ sudo rspamadm control stat
Uptime: 0 days, 00:03:21
Messages scanned: 122
Messages learned: 0
Connections: 18

What it means: scanning is happening at least somewhere; “messages scanned” non-zero is good.

Decision: If scanned is zero while mail is flowing, you’re bypassing Rspamd. Go straight to Postfix milter wiring and logs.

Task 2: Verify Postfix is actually calling the milter

cr0x@server:~$ sudo journalctl -u postfix -n 80 --no-pager | egrep -i 'milter|rspamd'
... postfix/smtpd[2140]: milter-connect: connect to unix:/run/rspamd/milter.sock: No such file or directory

What it means: Postfix tried, failed to connect. The filter is bypassed (because we set default_action=accept).

Decision: Fix socket path or ensure the rspamd milter worker/proxy is configured to create it. Do not “solve” this by switching to TCP without understanding why the socket is missing.

Task 3: Inspect the milter socket existence and ownership

cr0x@server:~$ sudo ls -l /run/rspamd/milter.sock
srwxrwx--- 1 rspamd rspamd 0 Jan  3 09:01 /run/rspamd/milter.sock

What it means: only owner/group can connect.

Decision: Ensure Postfix has group access (add to group or change socket mode via worker config). Do not chmod it to 777 on a shared system.

Task 4: Confirm Rspamd can talk to Redis

cr0x@server:~$ sudo rspamadm configdump redis
servers = "unix:/run/redis/redis-server.sock";

What it means: Rspamd believes Redis is on that socket.

Decision: If Redis is on TCP or a different socket, align it. “Two Redis configs” is a classic self-inflicted outage during restarts.

Task 5: Check Redis health and latency quickly

cr0x@server:~$ redis-cli -s /run/redis/redis-server.sock ping
PONG
cr0x@server:~$ redis-cli -s /run/redis/redis-server.sock --latency -i 1
min: 0, max: 1, avg: 0.22 (55 samples)

What it means: Redis is responsive; latency is low. If you see spikes to tens/hundreds of ms, Rspamd will feel it as slow scanning.

Decision: If latency is high, check disk IO, swapping, and whether Redis is persisting aggressively. Fix host health before tuning spam rules.

Task 6: Test scanning a sample message locally

cr0x@server:~$ printf 'From: a@example.net\nTo: b@example.com\nSubject: test\n\nHello\n' | rspamc
Results for file: stdin (0.012 seconds)
[Metric: default]
Action: no action
Spam: false
Score: 0.30 / 15.00
Symbol: MIME_GOOD (-0.10)
Symbol: R_SPF_ALLOW (-0.20)
Symbol: R_DKIM_NA (0.60)

What it means: scanning works; symbols are being produced. DKIM is “NA” because it’s inbound and unsigned.

Decision: If rspamc can scan but mail flow doesn’t show headers, your MTA integration is wrong, not Rspamd.

Task 7: Confirm headers are being added on real mail

cr0x@server:~$ sudo postqueue -p
-Queue ID-  --Size-- ----Arrival Time---- -Sender/Recipient-------
9C1B23D4A1      1244 Sat Jan  3 09:10:05  alerts@example.com
                                         user@example.com
-- 1 Kbytes in 1 Request.

What it means: mail is queued; not proof of filtering.

Decision: Grab one delivered message and inspect for X-Spam- or Authentication-Results headers. If missing, Postfix didn’t pass it through the milter.

Task 8: Verify DKIM signing is happening on outbound mail

cr0x@server:~$ sudo journalctl -u rspamd -n 120 --no-pager | egrep -i 'dkim|sign'
... rspamd[1827]: dkim_signing: sign example.com with selector s2026

What it means: Rspamd attempted to sign; good sign.

Decision: If you never see DKIM signing logs, confirm outbound mail traverses the milter (non_smtpd_milters matters for local submission).

Task 9: Generate the DNS record from the public key output

cr0x@server:~$ sudo head -n 5 /tmp/s2026.example.com.txt
s2026._domainkey IN TXT ( "v=DKIM1; k=rsa; "
  "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr..." ) ;

What it means: this is what you publish in DNS. The selector becomes part of the DNS label.

Decision: Until DNS is published and propagated, receivers won’t validate DKIM. That’s not Rspamd’s fault; it’s your change management.

Task 10: Check Rspamd throughput and per-message time

cr0x@server:~$ sudo rspamadm control stat | egrep 'Messages scanned|Uptime'
Uptime: 0 days, 00:08:44
Messages scanned: 486

What it means: messages are being processed. Compare scanning rate to your incoming mail rate.

Decision: If scanning lags behind mail volume, check CPU, DNS, and Redis latency before adjusting worker counts.

Task 11: Identify slow rules with logs (symptom-based)

cr0x@server:~$ sudo journalctl -u rspamd -n 200 --no-pager | egrep -i 'slow|timeout|lua'
... rspamd[1827]: rspamd_task_timeout: processing of task exceeded 8.0 seconds

What it means: Rspamd hit a timeout, often from DNS, HTTP checks, or overloaded resources.

Decision: If timeouts happen during traffic, you’re effectively “fail opening” in practice. Fix dependencies (DNS/Redis/CPU) before tightening milter default action.

Task 12: Check DNS resolution speed from the mail host

cr0x@server:~$ time dig +tries=1 +timeout=1 mx gmail.com @127.0.0.53 | egrep 'status|Query time'
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 59504
;; Query time: 18 msec
real	0m0.040s
user	0m0.010s
sys	0m0.004s

What it means: DNS is fast. If query time is high or timeouts occur, Rspamd will slow down and you’ll see milter delays.

Decision: If DNS is slow, fix local resolver caching or upstream DNS reliability. Spam filtering is a DNS-heavy workload.

Task 13: Check that your changes are coming from local.d (not ignored)

cr0x@server:~$ sudo rspamadm configdump actions | head
actions {
    add_header = 6;
    greylist = 4;
    reject = 15;
    rewrite_subject = 8;
}

What it means: your actions.conf is loaded.

Decision: If it shows different values, you edited the wrong file or another override is winning. Fix config layering before you chase ghost behavior.

Task 14: Confirm Rspamd version and build options

cr0x@server:~$ rspamd --version
Rspamd 3.4

What it means: your runtime version. This matters because module defaults and syntax evolve.

Decision: If you’re on something very old, avoid deep tuning until you’ve planned an upgrade. You don’t want to tune around bugs.

Fast diagnosis playbook

You’re on call. Mail is slow or spam is getting through. You have ten minutes before someone says “can we just disable it.” Here’s the order that finds bottlenecks fast.

1) Is mail actually being scanned?

  • Check Rspamd stats: sudo rspamadm control stat. If scanned is flat while mail flows, the MTA is bypassing.
  • Check Postfix logs for milter connects/errors.

Interpretation: if it’s bypassing, tuning scores does nothing. Fix wiring.

2) Is the milter path reachable from Postfix?

  • Socket exists? ls -l /run/rspamd/milter.sock
  • Permissions allow Postfix to connect? Group membership and mode.
  • Chroot got you? If Postfix runs in chroot for smtpd, the socket must exist inside that chroot path or you must disable chroot for that service.

Interpretation: “connection refused” or “no such file” is almost always local permissions/path, not network.

3) Is Rspamd slow because a dependency is slow?

  • Redis latency: redis-cli --latency
  • DNS time: dig with short timeout and check query time
  • CPU and load: top or systemd-cgtop
  • Disk pressure: if Redis persistence is stalling, you’ll see it as latency spikes

Interpretation: timeouts in Rspamd logs usually point at DNS/HTTP checks or overloaded resources. Fix the platform first.

4) Is Rspamd acting, but your policy is too gentle?

  • Inspect a spam sample’s symbols: rspamc -h 127.0.0.1:11333 (or default)
  • Check actions thresholds via rspamadm configdump actions
  • Confirm Postfix is not stripping headers downstream (some content filters do)

Interpretation: if you always “add header” but never reject, users will say “spam is getting through.” They’re not wrong; you just didn’t tell Postfix to do anything strong.

5) Only then: tune scores or enable extra modules

Once you trust the pipeline and performance, adjust thresholds and consider modules like greylisting tweaks, multimap, fuzzy, and Bayes training cadence.

“Hope is not a strategy.” — General James N. Mattis

Common mistakes: symptom → root cause → fix

1) “Rspamd is running, but no headers appear in delivered mail”

  • Symptom: UI shows activity, but messages in mailboxes lack X-Spam headers; spam slips through unchanged.
  • Root cause: Postfix is not using the milter for that mail path (often missing non_smtpd_milters), or the socket is inaccessible due to permissions/chroot.
  • Fix: Set both smtpd_milters and non_smtpd_milters. Validate socket accessibility and Postfix chroot settings. Restart Postfix and confirm logs show successful milter connections.

2) “Postfix logs show milter timeouts; mail is slow”

  • Symptom: SMTP sessions stall; Postfix log mentions milter timeout; Rspamd logs show task timeouts.
  • Root cause: DNS resolver slowness, Redis latency, overloaded CPU, or long external checks (HTTP-based RBL-like modules).
  • Fix: Measure DNS query time, Redis latency, and CPU. Fix resolver caching, resource contention, or reduce expensive modules before changing timeouts.

3) “DKIM signing not happening at all”

  • Symptom: No DKIM-Signature header outbound; logs silent.
  • Root cause: Outbound mail doesn’t traverse the milter (submission path bypass), or signing maps don’t match the sender domain.
  • Fix: Ensure non_smtpd_milters is set. Verify signing table patterns match your envelope/header From patterns. Watch logs for signing decisions.

4) “DKIM signing errors: cannot load key”

  • Symptom: Rspamd logs show key loading failures; signing skipped.
  • Root cause: Wrong file path in maps, wrong selector/domain combination, or permissions too strict (or ownership wrong).
  • Fix: Confirm key path exists, owner is rspamd, mode allows read, and maps point to correct location. Re-run rspamd -t.

5) “Everything is marked spam after enabling greylisting”

  • Symptom: Legit mail delayed or repeatedly tempfailed; complaints start immediately.
  • Root cause: Greylist thresholds too low, or you’re greylisting internal relays and SaaS senders that don’t retry in the expected pattern.
  • Fix: Increase greylist threshold, whitelist known good senders, or disable greylisting until you can model sender behavior properly.

6) “Rspamd works for inbound, but outbound gets rejected by recipients”

  • Symptom: Recipient bounces cite DMARC/DKIM failures; your mail looks unauthenticated.
  • Root cause: DKIM not signing, selector DNS missing, or From domain doesn’t match what you sign.
  • Fix: Publish DKIM DNS record, validate signing for the correct domain, and ensure SPF/DMARC alignment is not being broken by rewriting From headers elsewhere.

7) “Controller UI accessible from the Internet”

  • Symptom: Security scan flags open port 11334; you get weird config changes or learn events.
  • Root cause: Controller bound to 0.0.0.0 without firewall and auth.
  • Fix: Bind controller to localhost; require password; use SSH port forwarding/VPN for access. Treat it like an admin interface, because it is.

Checklists / step-by-step plan

Checklist: minimal first-time setup (single host)

  1. Install rspamd and redis-server.
  2. Confirm both services are running with systemd.
  3. Configure Redis via /etc/rspamd/local.d/redis.conf to use a local socket.
  4. Ensure rspamd user can access the Redis socket (group membership).
  5. Find or enable the Rspamd milter socket.
  6. Configure Postfix smtpd_milters and non_smtpd_milters to use that socket.
  7. Ensure Postfix can access the milter socket (permissions, chroot).
  8. Set minimal actions thresholds in local.d/actions.conf.
  9. Generate DKIM keys and configure dkim_signing.conf plus maps.
  10. Restart Rspamd; validate config with rspamd -t.
  11. Reload Postfix; confirm milter connections succeed in logs.
  12. Scan a sample message with rspamc and inspect a real delivered message for headers.

Checklist: “ship it” production hardening (after it works)

  1. Decide fail-open vs tempfail for milter outages (milter_default_action).
  2. Bind controller to localhost and set strong password hashes.
  3. Set log retention and a simple alert on repeated milter failures/timeouts.
  4. Document your DKIM selector rotation policy (even if it’s “rotate annually”).
  5. Define a ham/spam training workflow and who owns it.
  6. Record baseline latency: Rspamd task time, Redis latency, DNS query time.

Three corporate-world mini-stories

Incident caused by a wrong assumption: “It’s configured, so it must be filtering”

The company was mid-migration: old on-prem mail to a new VM-based stack. They installed Rspamd, enabled Redis, built DKIM keys, and even had a neat dashboard screenshot in the change ticket. Everyone moved on.

Two weeks later, the security team escalated: phishing volume was up, and user reports were getting weirdly consistent. The mail admins swore the new spam filter was in place. After all, the service was running and rspamc worked.

The wrong assumption was subtle: they thought “Rspamd is running” implies “mail is being scanned.” Postfix was configured with smtpd_milters but not non_smtpd_milters. Most outbound and a chunk of internal mail was injected locally by an application via sendmail and never hit the SMTP daemon.

Worse, they had milter_default_action=accept. So even when the socket path was wrong on one host, mail still flowed. It was a graceful degradation into a full bypass.

The fix was boring: apply the milter to both paths, verify successful milter connections in Postfix logs, and add a daily check that “messages scanned” increases with traffic. They also added a header-based canary: a tiny internal message that should always get a known Rspamd header, and an alert if it doesn’t appear.

An optimization that backfired: “Let’s save a hop by moving Redis off-box”

A different org had a strong platform team and a reflex: centralize state. They moved Redis from each mail host to a shared “cache cluster” because “it’s more efficient.” Networking was fast. Latency looked fine in synthetic tests.

Then Monday arrived. Burst traffic plus a few unrelated Redis clients caused intermittent latency spikes. Not big enough to trigger Redis alarms—just enough to make Rspamd tasks occasionally exceed their time budget. Postfix sessions started stacking up. SMTP clients retried. Volume increased. The system entered the familiar spiral: retries create load, load creates timeouts, timeouts create retries.

The mail team tuned Rspamd timeouts upward to “stabilize.” That reduced SMTP stalls but increased queueing and memory pressure. Deliverability suffered because mail arrived late. Users blamed the spam filter. Technically, they weren’t wrong; operationally, it was the dependency chain.

The resolution was to put Redis back locally for mail scanning state (or at least use local replicas). Centralizing state was fine for some apps; for the mail pipeline, predictable low-latency mattered more than theoretical efficiency. The optimization saved a server and bought a month of intermittent pain.

The boring but correct practice that saved the day: “Config layering and a single source of truth”

A finance-adjacent company had the kind of governance people joke about—until it pays rent. They had a strict rule: no edits to vendor configs. All changes went into local.d, and every file was managed via a deployment system with review.

During a routine OS upgrade, the Rspamd package updated defaults. On many teams, that’s where “why is scoring different now?” begins. Here, the upgrade went through, Rspamd restarted, and mail kept flowing. A few symbols changed behavior, but core actions and DKIM signing remained stable because local overrides remained intact.

Then a real incident hit: outbound mail started failing DKIM checks for one domain. Because configs were layered and tracked, they quickly diffed the DKIM maps and found a recently added domain with a typo in the signing table. Fix, restart, done.

What saved them wasn’t a clever Lua rule. It was the unglamorous discipline of keeping local policy separate from packaged defaults, plus the ability to reason about changes. In mail systems, boring is a feature.

FAQ

1) Do I need Redis for a “minimal” setup?

If you want a spam scanner demo, no. If you want an operational system you can debug and improve, yes. Redis is where Rspamd keeps useful state.

2) Should I use UNIX sockets or TCP for the milter?

On a single host: UNIX sockets. Fewer firewall questions, less latency, clearer permissions. Use TCP when you actually need network separation, and then treat it as an integration project, not a shortcut.

3) Why do I see “Rspamd is running” but scanned messages stay at zero?

Because nothing is calling it. Verify Postfix milters are set, the socket exists, and Postfix can connect. Use logs; don’t rely on vibes.

4) What’s the safest milter_default_action?

accept is safest for availability; tempfail is safer for security posture. Choose consciously. If you pick accept, add monitoring that screams when the milter is down.

5) Do I need Bayes training on day one?

No. Get scanning, actions, and DKIM correct first. Bayes helps once you have stable mail classification and a workflow for feeding good/bad examples.

6) Why is DKIM signing configured but recipients still show “DKIM=fail”?

Most often: DNS record not published, wrong selector in DNS, signing the wrong domain, or message modified after signing (some downstream filters rewrite headers). Check your pipeline order.

7) Can I run the controller UI on a public interface if I set a password?

You can, in the same way you can store backups on a USB stick labeled “DO NOT LOSE.” Bind to localhost and access via SSH tunnel/VPN. Make the safe path the easy path.

8) What’s the minimal set of thresholds I should start with?

Start with conservative reject (high number), add-header at a moderate number, and consider delaying tactics (greylist) only after you’ve measured business impact. The example in actions.conf is a decent starting point.

9) How do I know which rule caused a rejection?

Inspect the symbols and score from headers on the message or by scanning with rspamc. The entire point of Rspamd’s model is explainability—use it.

10) Should I deploy Rspamd as an SMTP proxy instead of a milter?

Not for “first-time minimal.” It can be great, but it adds routing complexity and a larger blast radius if misconfigured. Get the milter pipeline stable first.

Next steps you should actually do

You now have a minimal config that works, in the only definition that matters: mail is scanned, actions happen, DKIM signs outbound, and you can debug failures quickly.

  1. Prove the pipeline daily: create a simple canary email and alert if expected Rspamd headers don’t appear.
  2. Decide your failure stance: keep accept or move to tempfail once performance is stable and dependencies are reliable.
  3. Publish DKIM DNS records and verify externally (from a receiver viewpoint) before declaring victory.
  4. Measure latency under load: Redis latency, DNS query time, and Rspamd task time. When mail is slow, those three will tell you where to look.
  5. Only then tune scoring: adjust thresholds based on actual false positives/negatives, not executive inbox anecdotes.

If you want the system to stay working, treat it like part of the mail transport, not an accessory. Filters don’t fail gracefully; they fail silently. Your job is to make silence impossible.

← Previous
ZFS vdev imbalance: Why One Slow VDEV Drags the Whole Pool
Next →
Docker “volume is in use”: remove or replace without data loss

Leave a comment