Repository navigation
Troubleshooting
msfe-ng doctorIt checks every link of the chain — engine installed and configured, Exim wiring, queue flow, spool placement, quarantine writable by the scan user, ClamAV socket actually connectable, database, logging plugin, Telegram — and prints a fix for each failing check. The same result drives the banner at the top of the admin UI and the health dot in the rail.
msfe-ng doctor --fix (the banner's Fix what can be fixed) applies the
mechanical fixes first, then reports: installs the logging plugin's perl
modules (DBI and a MySQL driver — DBD::mysql, or DBD::MariaDB where
perl-DBD-MySQL cannot be installed beside the MariaDB repo's packages) and
restarts MailScanner; re-runs Configure for
Exim for anything it owns (Razor identity, Pyzor/Bayes homes, envelope-sender
header, archive dir, spam-check size, the Exim message-id shim) — only on an
engine already set to MTA = exim; runs the phishing-list updater; sync
when archive rules are missing; spool repair; starts MailScanner when it is
wired and the startup latch is on but it is stopped; disables and parks a
MailScanner.service whose binary is gone (a ConfigServer-era unit failing at
every boot, kept under /etc/msfe-ng/legacy-engine-etc); fetches the
upstream SpamAssassin ruleset when it is missing or older than 30 days
(SpamAssassin upstream rules — the engine's own ms-update-sa: sa-update,
sa-compile, MailScanner restart); turns off cPanel SpamAssassin plugins the
engine's perl cannot load, while cPanel's own spamd is disabled; takes the
first configuration snapshot. Decisions are never made for you: the mailflow kill
switch, cPanel SpamAssassin, cPanel's own ClamAV pass in Exim (cPanel virus
scan in Exim (exiscan) — touch /etc/exiscandisable and rebuild Exim to
stop the double virus scan, or keep it to reject infected mail at SMTP time),
the DNS resolver, database creation, enabling message logging and config
paths stay listed with their fix. Findings whose fix is one concrete
setting carry a proposal instead — Apply: … in the banner, apply: in
the CLI output, msfe-ng doctor --apply "<check name>" from a shell — which
does exactly the stated change after you confirm: Max Spam Check Size to
the value the month's mail suggests, Mark Infected Messages = no, the
255-character filename rule, switching off cPanel's ClamAV pass or its
attachment filter in Exim. MailScanner files go through the validated save
(lint, rollback, previous version kept in the file's history). The installer runs doctor --fix at the end of every install and
upgrade, and the guided migration runs it as its last step.
SpamAssassin loads the upstream ruleset from /var/lib/spamassassin/<its version>/updates_spamassassin_org, written by sa-update for that exact
version. A SpamAssassin installed from CPAN (/usr/local/share/perl5, version
4.000002 say) is not fed by cPanel's nightly sa-update, which serves its
own bundled copy (4.000001): MailScanner then scans with the site rules
alone (/etc/mail/spamassassin: KAM plus cPanel's few picked rules) and the
spamassassin command, which insists on rules, refuses to report or learn.
The doctor check SpamAssassin upstream rules fails on the missing directory
and warns when it is over 30 days old; doctor --fix runs the engine's
ms-update-sa. The RPM engine's daily cron keeps it updated afterwards.
The P0f.pm … IO/SigGuard.pm lines in a learn transcript are cPanel's
SpamAssassin plugin (/etc/mail/spamassassin/P0f.cf, symlinked in by WHM →
Exim Configuration Manager → Apache SpamAssassin: P0f) failing to load
under the system perl: it needs cPanel's private perl and serves cPanel's own
spamd only. Noise, not a failure — but logged at every scan. The doctor check
cPanel SpamAssassin plugins load names any of cPanel's plugins the engine's
perl cannot load; while cPanel's spamd is off (/etc/spamddisable) doctor --fix turns them off through whmapi1 set_tweaksetting module=Mail key=spamassassin_plugin_<Name> value=0, otherwise the command is left to
you.
That wording is not MailScanner's: it is Exim's classic system filter, which
cPanel ships as /etc/cpanel_exim_system_filter and enables with
Attachments: Filter messages with dangerous attachments (filter_attachments=1
in exim.conf.localopts). Its extension list includes eml, so every PEC
(Italian certified mail) message — the original travels as postacert.eml —
is refused at SMTP time, before MailScanner sees it, and nothing reaches the
Messages log. The doctor check cPanel attachment filter in Exim names it.
Turn it off in WHM → Exim Configuration Manager → Filters (or whmapi1 set_tweaksetting module=Mail key=filter_attachments value=0): MailScanner's
filename.rules.conf / filetype.rules.conf keep blocking dangerous
attachments, per domain and logged — and MailScanner's stock rules do not
deny .eml. Editing the filter file itself is undone by cPanel updates.
MailScanner's filename rules removed an attachment (nameinfected in the
log; the Messages tab shows Other infected). The removed file sits alone in
the quarantine directory of that message — with Quarantine Whole Message = no the message itself is not stored there, so release cannot rebuild it;
the archive copy is what the Messages view shows, and the file can be
handed over by hand. Two stock settings are worth revisiting:
-
filename.rules.confline Very long filename, possible OE attack (deny .{150,}) — a 2001-era Outlook Express defence that long descriptive PDF names trip routinely (an encoded=?iso-8859-1?Q?…?=name counts decoded). The doctor check attachment filename length rule proposes.{255,}; Apply does it. -
Mark Infected Messages = yesinMailScanner.confinserts an inline warning into the HTML part — right after the<html>tag, before<head>— which Outlook and Apple Mail render badly. The doctor check inline attachment-removed warning proposesno: the recipient still gets the{Filename?}subject tag and the…-Attachment-Warning.txtattachment saying what was removed and why.
Symptom. An outgoing or incoming message with a digitally signed
attachment (Verbale.pdf.p7m, bilancio.xbrl.p7m) is quarantined and the
sender receives an infected e-mail notice; the Messages tab shows it as
Other infected.
Cause. Italian and EU signatures (CAdES) wrap the document in an envelope
named <original name>.p7m. The stock rule in filename.rules.conf, Found
possible filename hiding (deny \.[a-z][a-z0-9]{2,3}\s*\.[a-z0-9]{3,4}$),
reads file.pdf.p7m as a hidden double extension. Rules are read top to
bottom and the first match wins, so an allow \.pdf\.p7m$ appended at the end
of the file never fires. The Config tab's file-rules editor marks such a rule
with ⚠ — drag it above the rule that wins.
The check. The doctor check signed documents (.p7m) blocked (Warn)
fires when that stock rule would refuse x.pdf.p7m, x.xbrl.p7m or
x.docx.p7m; it is silent once they are allowed, or when the rule that
refuses them is your own. Apply it with the banner button or
msfe-ng doctor --apply "signed documents (.p7m) blocked": it inserts this
block right before the generic deny (validated, previous version kept,
applying twice changes nothing):
allow \.(pdf|xml|xbrl|txt|csv|rtf|jpe?g|png|tiff?|odt|ods|odp|docx|xlsx|pptx)\.p7m$ - -
A host that applied the shorter line of an earlier release (without
docx|xlsx|pptx) gets the check again for x.docx.p7m; Apply then replaces
that one line in place rather than adding a second block.
What stays blocked. Only document types that cannot run code are
allowed: PDF, images, text, XML/XBRL, OpenDocument and the Office formats
that cannot hold macros (.docx, .xlsx, .pptx). Macro formats
(file.docm.p7m, .xlsm, legacy .doc/.xls), executables
(file.exe.p7m) and every other double extension (invoice.pdf.exe) are
still refused by the rules below. Your own later allow/deny lines for
.p7m are left untouched; an explicit deny \.docx\.p7m$ of your own
placed after the block still wins for that type, since nothing later is
rewritten.
Open Config → Auto-ban spam sources → Recent auto-bans and click
unban (or csf -tr <ip>); the row tells you which rule fired. A
threshold that bans on the first message is fast and blunt: the clean-mail
safeguard protects an IP only when it also delivered clean mail inside the
window, so a shared relay whose only recent message scored as spam is not
protected by it. Raise the count, shorten the ban, or add the address to
csf.allow (never banned again). Match rules are checked with Test
against recent mail before saving for exactly this reason.
A match rule with Also reject at SMTP time refused the message in
Exim's DATA ACL; it was never queued, so it is not in the Messages tab.
Rules tab → Auto-ban match rules → Refused at SMTP time lists it
(time, IP, rule, sender, subject), as does msfe-ng autoban rejects; the
rule number in the 550 is the row to look at. If the sender is legitimate:
edit the rule and untick the box (the rule keeps blocking and banning
through MailScanner), tighten the pattern, or add the sender to the
whitelist (Lists tab) — whitelisted senders are never refused and the
whitelist file is regenerated with the fragment on the next save or sync.
If the source was banned as well, unban it from Config → Recent
auto-bans (reason smtp). To stop every SMTP-time reject at once:
msfe-ng autoban smtp-off (removes /etc/msfe-ng/exim/smtp-deny.conf
and restarts Exim; the next save or sync puts it back unless the boxes are
unticked).
The reverse problem — a flagged rule that does not refuse anything while
MailScanner still catches the messages — is wiring: msfe-ng doctor
(SMTP-time rejects wired) tells whether the fragment exists, whether the
cPanel hook ACL_CHECK_MESSAGE_PRE_BLOCK/custom_begin_check_message_pre
has the include line, and whether the built /etc/exim.conf carries it.
The block directory is cPanel's documented hook point but is not verified
by MSFE-NG itself: ls /usr/local/cpanel/etc/exim/acls/ must list it; if
cPanel calls it differently, set MSFE_NG_SMTP_DENY_BLOCK=<name> for the
daemon and the cron, re-save a rule and run /scripts/buildeximconf && /scripts/restartsrv_exim. Exim must also have been restarted after the
last change to the fragment (the save does that; check the toast or
msfe-ng autoban rules).
MailScanner reads MailScanner.conf, spamassassin.conf, spam.lists.conf,
virus.scanners.conf, conf.d/ and mcp/ only at start. A save from the
Config tab restarts it unless apply without restart was ticked; a
hand edit does not. The doctor's MailScanner config newer than the running
service names the files the running engine has not read — msfe-ng service restart (or the tab's Restart MailScanner now banner) applies them.
Rulesets (rules/*.rules) and phishing lists are re-read per batch.
filename.rules.conf, filetype.rules.conf and the archives.* rule files
are read when a child starts (Config::Read), so a hand edit takes effect
only after Reload MailScanner (msfe-ng service reload, which restarts
the children) or when they recycle on their own (Restart Every, two hours
by default). A save from the Config tab reloads for you; a message scanned
in between still hits the old rule.
Every save is linted on a staged copy first; the report under the editor shows
the exact MailScanner --lint / spamassassin --lint lines. Test
configuration on the Config tab lists the same findings for the files on disk
with links to the file and setting; msfe-ng conf test prints them. A conf.d
fragment is the one kind validated after apply — if that lint fails the
previous file is written back and the report says rolled back. Every replaced
version is in the file's History.
- Service tab — is MailScanner running, wired, scanning enabled, and the safety switch ON? A stopped or unwired scanner with wired Exim means mail sits in the scanning queue.
-
Queues tab → MailScanner queue — messages older than a few seconds are
waiting for a scan that never happens. Run Test that everything is OK
(
MailScanner --lint): a wedged virus scanner or a missing Perl module shows up there, not in the mail log. - Activity console on the Service tab follows the systemd journal; a child crash-loop shows there as Process did not exit cleanly.
- Kill switch: Disable MailScanner (mailflow) delivers mail directly until the scanner is fixed — nothing is lost, it just isn't scanned.
Turn on the scanning queue stuck Telegram alert so this pages you instead of your users.
cPanel's clamav package can move the clamd socket. MailScanner's stale
Clamd Socket then wedges every batch silently. msfe-ng doctor now
connects to the socket and names the live one; set it in Config →
MailScanner.conf and restart MailScanner.
Exim 4.100 (cPanel 138) is read by MailScanner's version probe as 4.1, so
it files outgoing spool files under input/0/ where Exim never looks and
copies quarantined bodies from the wrong offset. MSFE-NG handles this without
patching MailScanner: Configure for Exim points Exim Command at a shim that
normalises the version banner, the doctor fails the check MailScanner reads
the Exim message-id format when the probe would go wrong, and the monitor
cron moves misfiled files every 5 minutes (Queues → Fix misplaced spool
files / msfe-ng service spool-repair do it by hand).
Exim creates its split-spool subdirectories (input/a…input/9,
msglog/a…) lazily, the first time it queues a message there; MailScanner
never creates them. On a server that has not queued mail before wiring,
every scanned message fails at the write-out with `Could not open file
/var/spool/exim/input//-D: No such file or directory
and waits in the scanning queue for the next attempt, while the queue looks healthy. Lint says the same earlier: *directory /var/spool/exim/input for outqueuedir does not exist*.engine wireandengine configurenow create the missing ones as Exim would (mailnull:mail, 0750, nothing existing touched), the doctor check *delivery spool directories present* fails while any is missing, andmsfe-ng doctor --fix` creates them.
With engine_perl = "cpanel" the engine's scripts name the real path of
cPanel's perl (…/3rdparty/perl/542/bin/perl). When cPanel moves to a newer
perl the old directory disappears and the unit fails with bad interpreter:
No such file or directory. The monitor cron re-points the scripts within
five minutes and restarts MailScanner (and alerts via Telegram); the doctor
check MailScanner engine perl fails meanwhile and msfe-ng doctor --fix
or msfe-ng engine perl --apply does it at once. An RPM upgrade of
MailScanner resets the shebangs to /usr/bin/perl instead: the same check
warns and the same fix applies (the Service tab's upgrade already runs it).
Servers migrated from ConfigServer MSFE often still run its bundled engine —
/usr/mailscanner/usr/sbin/MailScanner (5.4.x, under cPanel's perl) with
/usr/mailscanner/etc/MailScanner.conf — while msfe-ng engine install put
the MailScanner 5.5 RPM at /usr/sbin/MailScanner. MSFE-NG follows the engine
the service actually runs: it takes the binary from MailScanner.service's
ExecStart (or the tree next to mailscanner_conf), and reads that engine's
perl, ms-update-phishing and defaults file from the same place. The conf
follows suit: when the mailscanner_conf in /etc/msfe-ng/config.toml does
not exist, MSFE-NG uses the first of /etc/MailScanner/MailScanner.conf and
/usr/mailscanner/etc/MailScanner.conf that does, and sync writes the rule
files into that conf's %rules-dir% (unless mailscanner_rules_dir is set).
Set mailscanner_conf explicitly to pick one engine when both confs exist.
msfe-ng engine status and the doctor's first line print which engine,
version and conf were found; the doctor checks MailScanner.conf found and
rules dir is the engine's %rules-dir% fail when they disagree with the
engine — before that, a missing conf showed up as "still carries sendmail
defaults", "unknown version" and rules the engine never read.
A 5.4 engine has no Exim Command directive (it is a syntax error there), so
Configure for Exim leaves it out — and comments out one left by an older
MSFE-NG. It also has no support for Exim ≥ 4.97 long message ids at all; the
doctor warns about that and the monitor keeps re-filing misplaced spool
files, but the real fix is the 5.5 engine.
The named-queue wiring is two files MSFE-NG writes (the ACL fragment
/etc/msfe-ng/mailscannerq.conf and its include line in the cPanel hook
ACL_MAIL_PRE_BLOCK/custom_begin_mail_pre) plus one thing cPanel does:
/scripts/buildeximconf copies the hook into /etc/exim.conf, and
/scripts/restartsrv_exim makes Exim read it. When that last step did not
happen — the build failed, or something rebuilt exim.conf without the
hook — every file looks wired, MailScanner runs idle, Exim delivers every
message straight away, and nothing is ever recorded. The doctor tells the two
apart:
-
named-queue include built into exim.conf (Fail) —
/etc/exim.conflacks the include line. The usual cause on a freshly set-up server: WHM's Exim Configuration Manager writesacl_<hook name>=0into/etc/exim.conf.localoptsfor every custom ACL hook that was empty when the configuration was last saved, andbuildeximconfthen leaves that hook out however often it runs — the build reports success. The check names the file whenacl_custom_begin_mail_pre=0is there.msfe-ng doctor --fix(or the banner's Fix what can be fixed) sets it to1and runs/scripts/buildeximconf && /scripts/restartsrv_exim; Wire Exim → MailScanner does the same. By hand:sed -i 's/^acl_custom_begin_mail_pre=.*/acl_custom_begin_mail_pre=1/' /etc/exim.conf.localopts, then the two scripts. If the line is still missing afterwards, check thatls /usr/local/cpanel/etc/exim/acls/listsACL_MAIL_PRE_BLOCK, and that/etc/exim.conf.localhas no%ACLBLOCK%section of its own (a replaced ACL template has no place for cPanel's hooks). -
Exim running its current configuration (Warn) —
exim.confchanged after the Exim daemon started, so the running Exim still uses the previous one.msfe-ng doctor --fixrestarts it.
Wire Exim → MailScanner checks the same thing right after its rebuild and
prints a WARNING: line in the activity console when the include did not
land. To confirm by hand that a message went through the scanner, look for
its id in /var/log/maillog (MailScanner[…]: … Logging message <id>) and
for its X-…-MailScanner headers in the delivered copy.
The original MSFE's "old method" routes mail with two Exim configurations:
/etc/exim.conf spools into /var/spool/exim_incoming with queue_only, and
/etc/exim_outgoing.conf delivers from /var/spool/exim. MSFE-NG recognises
that layout — the doctor reports Exim wired to MailScanner (two-config
method) — and Configure for Exim keeps what it needs:
Incoming Queue Dir = /var/spool/exim_incoming/input/* (the /* takes the split subdirs)
Split Exim Spool = yes
Sendmail2 = /usr/sbin/exim -C /etc/exim_outgoing.conf
Without the -C, MailScanner hands released messages to an Exim that looks
for them in the incoming spool and logs Spool file … -D not found. The
same applies to every queue command: a bare exim -Mrm <id> (or -bp,
-Mvh, -M) on this layout only sees the scanning spool and answers Spool file <id>-D not found for a message waiting in the delivery queue. The
Queues tab and the monitor's auto-clean select the queue for you — -C /etc/exim_outgoing.conf for the delivery queue, bare for the scanning queue
(with the named-queue wiring it is -qGmailscanner and bare) — and the
transcript shows the arguments used. By hand:
/usr/sbin/exim -C /etc/exim_outgoing.conf -bp # the delivery queue
/usr/sbin/exim -C /etc/exim_outgoing.conf -Mrm <id> # delete from it
/usr/sbin/exim -bp # the scanning queueThere is no mailflow kill switch on this layout: /etc/exim.conf itself
spools into the scanning queue, so mail always passes through MailScanner.
The Service tab does not show the toggle, msfe-ng exim disable-scanning
refuses, and the doctor says so. Wire and Unwire refuse to touch
that layout: MSFE-NG never layers its named queue over it. To switch to the
named-queue method, undo the two-config setup with the tools that created it
first.
With ConfigServer's front-end, /etc/exiscandisable bypassed MailScanner —
through ConfigServer's patch of Cpanel::Exim, long since overwritten by
cPanel updates. On a stock cPanel that file does exactly one thing: it turns
off cPanel's own exiscan, the ClamAV pass in Exim's SMTP DATA ACL
(av_scanner in exim.conf). MSFE-NG's kill switch is the named-queue ACL
fragment renamed to .disabled (see the Service page) and never touches that
file, so a ConfigServer-era /etc/exiscandisable stays as it is — which is
the right setting with MailScanner scanning for viruses. Where the file is
absent, cPanel's Exim scans every message with ClamAV at SMTP time and
MailScanner scans it again: the doctor check cPanel virus scan in Exim
(exiscan) warns about the double scan. Stopping it is a policy choice, so
doctor --fix leaves it alone — SMTP-time rejection of infected mail is the
upside of keeping it:
touch /etc/exiscandisable && /scripts/buildeximconf && /scripts/restartsrv_eximWhich to choose: with exiscan on, infected mail is refused at SMTP time (the
sender's MTA reports it) but never reaches MSFE-NG — not the Messages
log, not the quarantine, not the statistics; only exim_rejectlog records
it — and every message is ClamAV-scanned twice. With it off, MailScanner is
the one virus scanner: infected mail is quarantined with its report in the
Messages tab and the per-domain virus actions apply. The conventional
MailScanner setup is off. To keep exiscan on purpose, set accept_exiscan = true in config.toml (Config tab → msfe-ng config): the doctor then shows
the check as OK, kept on purpose.
Roundcube submits to Exim over authenticated SMTP on the server itself, and
Exim's stock received_header_text prints the sending host for every hop:
Received: from localhost (server.example.com) [127.0.0.1] by … with esmtpsa. The log line H=localhost (server.example.com) [127.0.0.1] is the
same fact and is fine to keep; the header is what recipients see. This is
Exim's doing, identical with or without MSFE-NG — MailScanner only adds its
own X-…-MailScanner headers and MSFE-NG never rewrites a message.
Do not strip Received: lines after reception (in MailScanner or anywhere
else): that breaks the tracing you want to keep and cannot tell webmail from
an external client reliably. Fix it in Exim's template instead: Settings →
Local submission privacy → Hide the loopback hop (or msfe-ng localsub on)
installs a conditional received_header_text through cPanel's
/etc/exim.conf.local (@CONFIG@ section) that leaves out the from …
clause only for hops that were authenticated and from loopback — with a
backup, a rebuild, exim -bV and an effective-template check, and an
automatic rollback when either fails. External authenticated clients, TLS
details, envelope sender and message id are untouched. The doctor check
webmail submissions hide the loopback hop says which state Exim is in and
warns when the block is installed but a rebuild left it out. To show the real
origin instead of nothing, Roundcube's http_received_header (ideally with
http_received_header_encrypt) adds the browser's address — see the
Settings page for the privacy trade-off.
The RPM engine runs as mailscanner.service, generated from its LSB
ms-init. A hand-written /usr/lib/systemd/system/MailScanner.service (or
one under /etc/systemd/system) from another era — ConfigServer's, pointing
at /usr/mailscanner/usr/sbin/MailScanner — survives the migration, stays
enabled, and fails with status 203 at every boot next to the working unit.
The doctor check stale MailScanner service unit finds any
MailScanner.service whose ExecStart binary is gone; doctor --fix
disables it and parks the file under /etc/msfe-ng/legacy-engine-etc/
(the guided migration does the same).
The doctor check cPanel SpamAssassin double scan counts accounts whose home
holds .spamassassinenable — but two WHM switches sit above those flags and
are honoured: the Apache SpamAssassin service off in Service Manager
(/etc/spamddisable, tested by cPanel's Exim ACL before every SpamAssassin
step) and Enable Apache SpamAssassin spam filter off in Tweak Settings
(skipspamassassin=1, which removes the feature; accounts created afterwards
still get the flag, and uapi … disable_spam_assassin answers feature is
not enabled on this system). With either off the check passes as off
server-wide … the stale Spam Filters flag on N account(s) is ignored and the
Service tab's button does nothing.
MailScanner's cron.daily job (ms-cron DAILY → ms-update-phishing) fetches
the phishing site lists from phishing.mailscanner.info, which sits behind
Cloudflare. Cloudflare answers the HTTP/2 fingerprint of older curl builds
with a 403 challenge page; the upstream script saves that HTML as the .gz,
gunzip rejects it, and the lists silently stay at whatever the RPM shipped.
The same request over HTTP/1.1 is served, so Configure for Exim (msfe-ng engine configure, also run on every upgrade) patches ms-update-phishing to
use curl -f --http1.1 (backup in ms-update-phishing.msfe-ng.bak) and
deletes the HTML saved as .gz, whose timestamp would otherwise keep the
next download answering 304 Not Modified. The
doctor check phishing site lists updating warns when
phishing.bad.sites.conf is more than three days old and names the fix; a
MailScanner package upgrade reverts the patch, which the next engine configure reapplies. Set ms_cron_ps=0 in /etc/MailScanner/defaults to turn
the daily update off instead — the doctor then reports the check as OK.
spamassassin -r (the spam & report action) submits the message to Razor
and Pyzor. Razor only accepts reports from a registered identity, and
SpamAssassin's Bayes DB and the Razor/Pyzor client state all live in the
home directory of whoever runs them — a problem on cPanel, where
MailScanner's scanning children run as mailnull but inherit root's HOME,
which they cannot read. The symptoms: BAYES_* and PYZOR_CHECK never fire
under MailScanner, training from the UI goes into root's ~/.spamassassin
(a DB MailScanner never opens), and reports fail as above.
Configure for Exim (msfe-ng engine configure, also run on every upgrade)
sets it up the way MailScanner documents it:
-
SpamAssassin User State Dir = /var/spool/MailScanner/spamassassin, owned by the scan user — the one Bayes DB that MailScanner scans with and every UI learn action trains (sa-learn --dbpathon that DB). - Shared, world-readable
.razorand.pyzorhomes under/etc/mail/spamassassin, referenced fromspamassassin.confviarazor_configandpyzor_options --homedir(managed block, backup kept).razor-admin -registerruns once to create the reporting identity.
The doctor checks Bayes DB shared with MailScanner, Razor reporting identity and Pyzor shared home warn when any piece is missing. DCC is not set up: it is not open source and not packaged for EL.
Those 0.0 "ADMINISTRATOR NOTICE" rules mean the blocklist answered the query
with a refusal code rather than data: Spamhaus (ZEN, DBL) and DNSWL reject
queries relayed through shared or public resolvers (a hosting provider's
resolver, 8.8.8.8, OpenDNS…), and URIBL rate-limits them. SpamAssassin then
scores without its most valuable lists, silently. The doctor check DNS
blocklists answering asks each list (Spamhaus ZEN and DBL, DNSWL, URIBL, and
every entry of MailScanner's Spam List) for its documented test record
through the system resolver and reports which ones refuse or never answer,
plus the resolver in use when it is not on loopback. The lists are probed at
most every ten minutes; each change of verdict is logged by the daemon
(journalctl -u msfe-ng | grep 'DNS blocklists').
The fix is a private recursive resolver on the server itself. Service →
Private DNS resolver → Install (or msfe-ng resolver install; msfe-ng resolver status first) does it as a logged job: installs unbound, binds
PowerDNS to the public addresses when it holds port 53 (each address once —
Hetzner hosts list the same IPv4 as a peer route and as a /32, and PowerDNS
refuses to bind one address twice; if PowerDNS does not come back with the
new local-address, its previous config is restored and the job stops
there), configures unbound on loopback with Restart=always, verifies a
Spamhaus test query through 127.0.0.1 before touching anything else, then
points resolv.conf — and NetworkManager or the ifcfg files, so it is not
written back — at loopback only. Every file edited keeps a .msfe-ng.bak.
BIND (named) on port 53 is reported as a blocker: set its listen-on to
the public addresses first.
Restart=always matters on cPanel: when a service fails, chkservd restarts
it with cPanel's restartsrv, which first kills whatever holds the
service's port — a PowerDNS that cannot start therefore takes unbound off
port 53 with a plain SIGTERM (exit 0, not a "failure"), and resolv.conf
keeps pointing at a dead 127.0.0.1: every lookup on the server fails, mail
delivery included. The doctor check local DNS resolver answering fails in
that state and doctor --fix starts unbound again. By hand, the same steps
are:
dnf -y install unbound
# /etc/pdns/pdns.conf — cPanel keeps custom lines across updates
echo "local-address=<public IPv4>, <public IPv6>" >> /etc/pdns/pdns.conf
/scripts/restartsrv_pdns
# /etc/unbound/local.d/local.conf
# interface: 127.0.0.1
# interface: ::1
# access-control: 127.0.0.0/8 allow
# access-control: ::1 allow
systemctl enable --now unbound
# /etc/resolv.conf: nameserver 127.0.0.1 ONLY — do not keep the provider's
# resolver as a fallback: glibc and SpamAssassin switch to it on any error
# and the lists' refusal codes come back. With network-scripts set
# PEERDNS=no and DNS1=127.0.0.1 in ifcfg-eth0 so dhclient does not write it
# back; give unbound Restart=always (a drop-in under
# /etc/systemd/system/unbound.service.d/).
dig +short 2.0.0.127.zen.spamhaus.org # expect 127.0.0.2/4/10, not 127.255.255.254Lists that never answer are dead: SORBS shut down in 2024, yet MailScanner
5.5.3 ships Spam List = BARRACUDA SORBS SPAMCOP (and no longer defines
SORBS), so every message waited on it until Spam List Timeout. Configure
for Exim drops undefined and known-defunct entries from Spam List.
MailScanner stamps the envelope sender into the header named by its
Envelope From Header directive (X-%org-name%-MailScanner-From) and
SpamAssassin must be told the same name in spamassassin.conf
(envelope_sender_header), or SPF and whitelist_from look at the wrong
sender. Configure for Exim / msfe-ng engine configure sets it from the
directive; the doctor check SpamAssassin envelope-sender header warns while
they differ.
MailScanner skips every spam check — RBLs and SpamAssassin — for messages
bigger than Max Spam Check Size, and 5.5.3 ships that at 200k, a size any
HTML newsletter with inline images exceeds. Such mail is logged with the
report not spam (too large) and delivered unscored (on cPanel the
account's own Apache SpamAssassin may still tag it — that is the
X-Spam-Status header, not MailScanner's). Configure for Exim raises a
stock-sized limit to 2M; a larger or ruleset value set by hand is kept.
SpamAssassin itself still only reads the first Max SpamAssassin Size
(200k) of each message, so the cost is small. The doctor check large
messages get spam-checked reads the size distribution of the last 30 days'
mail. At the stock size a single message over the limit is a warning, since
the fix is mechanical; once the limit is raised, mail over it is attachments
rather than spam, so the check only warns when 1 % or more of the month's
mail went unscored — a message or two is the trade-off the limit exists
for, and the check stays green (covers 99.9 % of the last 30 days' mail).
When it does warn, it names the smallest limit from 1M 2M 3M 5M 10M 20M 50M that would cover more than 99 % of that mail (5M would cover
99.4 %); set it in the Config tab and restart MailScanner.
The quarantine must be owned by the user MailScanner runs as. Configure for
Exim (msfe-ng engine configure) sets it; the doctor check quarantine
writable by scan user catches regressions.
MailScanner only reads its configuration at startup, so MSFE-NG reloads it only when a generated rule file actually changed. If you see restarts every 10 minutes, something else is rewriting the rules — check the stray flags on the Rules tab.
Run a Delivery test for the address (Delivery test tab, the
test link next to a recipient in Messages, or `msfe-ng delivery test
The Logs tab searches the whole mail log or Exim mainlog —
rotated weeks included — for a message id, address or host, no need to grep
on the server. Start from the newest hit and step ◀ older to see the
message arrive, get scanned and be delivered or deferred; every occurrence is
highlighted in context. If you know roughly when it happened, pick the day
from the 📅 calendar first.
msfe-ng health # version + panel, over the socket
systemctl status msfe-ng # the daemon
journalctl -u msfe-ng -n 50The UI talks to the daemon through the panel's CGI shim over a Unix socket;
MSFE-NG daemon is not responding in the browser means systemctl restart msfe-ng.