Configuring proper server-wide spam protection is essential for maintaining server reputation, preserving bandwidth, and ensuring optimal mail deliverability. This guide outlines a complete, weighted spam-scoring procedure for DirectAdmin running on Debian 13, using Exim + Easy Spam Fighter (ESF) + SpamAssassin together as one coordinated system.
DirectAdmin's mail stack is not three independent filters. It is one pipeline:
- ESF (RCPT stage) checks SPF, DKIM, reverse DNS, and RBL membership before the message body arrives, and starts a running score (
acl_m_easy69). - SpamAssassin (DATA stage) scans the message content and produces its own score. This score is added directly into ESF's running total — multiplied by 10 (a SpamAssassin score of
3.4adds34points to ESF's tally). - ESF's final check compares the combined total against
EASY_HIGH_SCORE_DROPand drops the message if it's equal or higher.
Because of step 2, any SpamAssassin rule score you set is not independent — it always flows into ESF's single combined decision. This guide treats EASY_HIGH_SCORE_DROP = 100 as the reference "100% = kill" scale, and calibrates every other setting against it.
Step 1: Configure a Reliable DNS Resolver Path
RBL and Spamhaus lookups depend on how your resolver reaches the outside world.
Using a large public resolver like 1.1.1.1 directly can get your queries throttled or blocked by Spamhaus, returning 127.255.255.254 instead of a real answer — silently disabling every RBL check on your server.
- Follow
the documentation at:https://frankwebhost.com/knowledgebase/3/How-to-Configure-Unbound-DNS-Resolver-on-Debian-13-Cloud-VPS.html
- Verify:
You should consistently seedig +short 2.0.0.127.zen.spamhaus.org127.0.0.2,127.0.0.4, or127.0.0.10— never127.255.255.254.
Step 2: Enable Reply-To Callout Verification
Reply-To callout verification checks if the sender's "reply-to" address actually exists at the source mail server before accepting incoming mail. This drastically cuts down on dictionary attacks and fake sender spam.
- Edit the data-stage ACL Exim file:
nano /etc/exim.acl_check_message.pre.conf - Append the sender's replyto verification rule:
deny condition = ${if def:h_reply-to: {yes}{no}} condition = ${if eq{${address:$h_reply-to:}}{}{no}{yes}} condition = ${if eqi{${address:$h_reply-to:}}{${address:$h_from:}}{no}{yes}} !verify = sender=${address:$h_reply-to:}/callout=40s message = Verification failed: Reply-To address does not exist - Rebuild the Exim configuration:
da build exim_conf
Step 3: Configure Real-time Blackhole Lists (RBLs)
RBLs allow Exim to block incoming emails from known spammer IP addresses directly during the SMTP connection phase.
- Edit the custom Exim strings file:
nano /etc/exim.strings.conf.custom - Define your preferred RBL lists (Barracuda, Spamhaus, SpamCop, PSBL, Mailspike, MSRBL):
RBL_DNS_LIST==b.barracudacentral.org : zen.spamhaus.org : bl.spamcop.net : psbl.surriel.com : bl.mailspike.net : combined.rbl.msrbl.net
smtp_banner = "$primary_hostname ESMTP Mail Server Ready" - Rebuild the Exim configuration to activate the RBLs:
da build exim_conf
Step 4: Easy Spam Fighter — Weighted Scoring (RCPT stage)
This is where your protection level is defined. Every value below is calibrated against EASY_HIGH_SCORE_DROP = 100 as the "100% = kill" reference point, so each signal's weight can be read directly as a percentage of the kill threshold.
-
nano /etc/exim.easy_spam_fighter/variables.conf.custom -
# ============================================================ # ESF (Exim Spam Filter) — Aggressive Scoring # Consistent with SpamAssassin target: 3.0=***SPAM***, 10.0=Drop # Kill threshold = 100 (i.e. SA's 10.0 scaled x10) # ============================================================ # The kill threshold itself — every weight below is a % of this number EASY_HIGH_SCORE_DROP == 100 # Skip re-running SpamAssassin only once a message is already guaranteed # to be dropped anyway (pure CPU optimization, does not affect decisions) EASY_LIMIT == 100 # SPF EASY_SPF_FAIL == 75 EASY_SPF_SOFT_FAIL == 75 EASY_SPF_PASS == -01 # DKIM EASY_DKIM_FAIL == 50 EASY_DKIM_PASS == -01 # Reverse DNS EASY_NO_REVERSE_IP == 30 EASY_FORWARD_CONFIRMED_RDNS == -01 # RBL match EASY_DNS_BLACKLIST == 25 # Confirmed content-spam bonus (SpamAssassin's own Global threshold reached) EASY_IS_SPAM == 00 - Rebuild Exim configuration:
da build exim_conf
| Situation | Score added | Dropped alone? |
|---|---|---|
| SPF fail (hard or soft) only | 100 | Yes — matches "100% rage" |
| DKIM fail only | 60 | No — needs 40 more points from any other signal |
| rDNS fail only | 50 | No — needs 50 more points |
| DKIM fail + rDNS fail together | 110 | Yes |
| DKIM fail + rDNS fail + Blacklist together | 170 | Yes |
Step 5: SpamAssassin — Content-Only Rules (No ESF Overlap)
SPF, DKIM, and rDNS are already fully handled in Step 4. Setting them again here would double-count the same signal and drop legitimate mail prematurely (a forwarded message with a relay that has no rDNS, for example, would be punished twice for the same reason). The rules below have no ESF equivalent, so they are safe to score here.
10 becomes 100 points once it reaches ESF's tally. To make a rule worth "100%" on ESF's scale, set its SpamAssassin score to 10, not 100.-
nano /etc/mail/spamassassin/local.cf - Append:
# ============================================================ # /etc/mail/spamassassin/local.cf # Aggressive Spam Protection / Custom Scoring # Target: required_score 3.0 = ***SPAM*** tag, 10.0+ = Drop (via ESF) # ============================================================ # ------------------------------------------------------------ # IP REPUTATION / DNSBL # Sender IP reputation and known spam networks # ------------------------------------------------------------ # NOTE: SBL/XBL/SPAMCOP zeroed — already scored by ESF's EASY_DNS_BLACKLIST # (pre-SA check). PBL/RPBL/MSPIKE kept — separate reputation lists ESF # does not check, so no double-count here. score RCVD_IN_SBL 0.0 score RCVD_IN_XBL 0.0 score RCVD_IN_PBL 0.0 score RCVD_IN_BL_SPAMCOP_NET 0.0 score RCVD_IN_VALIDITY_RPBL 3.0 score RCVD_IN_MSPIKE_L5 4.5 score RCVD_IN_MSPIKE_L4 3.0 # ------------------------------------------------------------ # URI / DOMAIN BLACKLIST # Phishing, malware, spam domains and malicious URLs # ------------------------------------------------------------ score URIBL_BLACK 4.5 score URIBL_ABUSE_SURBL 3.5 score URIBL_RED 3.0 score URIBL_DBL_SPAM 4.0 score URIBL_DBL_PHISH 5.0 score URIBL_DBL_MALWARE 6.0 score URIBL_DBL_BOTNETCC 5.0 # ------------------------------------------------------------ # DMARC scoring (spf and dkim scored by ESF) # Spoofing and forged sender detection # ------------------------------------------------------------ # DMARC kept — it's alignment+policy score SPF_PASS 0.0 score SPF_FAIL 0.0 score SPF_SOFTFAIL 0.0 score SPF_NEUTRAL 0.0 score SPF_NONE 0.0 score DKIM_SIGNED 0.0 score DKIM_VALID_EF 0.0 score DKIM_VALID_AU 0.0 score DKIM_VALID 0.0 score DKIM_INVALID 0.0 score DMARC_REJECT 6.5 score DMARC_QUAR 3.5 score DMARC_PASS -1.0 # ------------------------------------------------------------ # BAYESIAN FILTER # Trained Bayes database scoring # ------------------------------------------------------------ score BAYES_99 4.5 score BAYES_999 6.5 score BAYES_00 -0.1 # ------------------------------------------------------------ # PHISHING / CREDENTIAL THEFT # Account takeover, fake login and phishing patterns # ------------------------------------------------------------ score TVD_PH_BODY_ACCOUNTS_PRE 3.5 score TVD_PH_REC 3.0 score TVD_PH_SEC 3.0 score FROM_MISSP_PHISH 4.0 score GOOGLE_DOCS_PHISH 4.5 score GOOGLE_DOCS_PHISH_MANY 5.5 score GOOG_STO_HTML_PHISH 4.5 score POSSIBLE_PAYPAL_PHISH_02 4.0 score HTML_FORMACTION_MAILTO 3.5 # ------------------------------------------------------------ # SPOOFED / FORGED FREEMAIL # Gmail/Outlook/Yahoo etc. impersonation pattern # ------------------------------------------------------------ score SPOOFED_FREEMAIL 4.0 score SPOOFED_FREEMAIL_NO_RDNS 4.5 score FREEMAIL_FORGED_REPLYTO 5.0 score SPOOFED_FREEM_REPTO 3.5 score FROM_NTLD_REPLY_FREEMAIL 3.0 score FREEMAIL_ENVFROM_END_DIGIT 3.5 # ------------------------------------------------------------ # FORGED / MALFORMED HEADERS # Suspicious or malformed message headers # ------------------------------------------------------------ score MISSING_HEADERS 2.5 score MISSING_SUBJECT 3.0 score MISSING_DATE 2.0 score MISSING_FROM 3.5 score NO_HEADERS_MESSAGE 3.5 score FORGED_MUA_OUTLOOK 2.5 score FORGED_MUA_EUDORA 2.5 # ------------------------------------------------------------ # DYNAMIC / RESIDENTIAL IP # Residential/dynamic IP from direct SMTP delivery # ------------------------------------------------------------ # NOTE: RDNS_NONE zeroed — already scored by ESF's EASY_NO_REVERSE_IP. # RDNS_DYNAMIC/HELO_DYNAMIC_IPADDR kept — these pattern-match "dynamic/dsl/ # pool" in PTR/HELO, a different test than "no rDNS at all". score RDNS_NONE 0.0 score RDNS_DYNAMIC 2.5 score HELO_DYNAMIC_IPADDR 2.5 # ------------------------------------------------------------ # HTML / IMAGE SPAM # Image-only and HTML-only spam # ------------------------------------------------------------ score HTML_IMAGE_ONLY_08 2.0 score HTML_IMAGE_ONLY_12 2.5 score HTML_IMAGE_ONLY_20 3.0 score MIME_HTML_ONLY 1.5 score DC_IMAGE_SPAM_HTML 3.5 # ------------------------------------------------------------ # BULK / MARKETING / COLD OUTREACH # Aggressive unsolicited marketing detection # ------------------------------------------------------------ score SUBJ_ALL_CAPS 2.0 score URL_SHORTENER_CHAINED 3.0 score PDS_FROM_NAME_TO_DOMAIN 2.0 -
# ==================================================== # Bayes Auto-Learn Tuning for Aggressive Setup # ==================================================== bayes_auto_learn 1 bayes_auto_learn_threshold_nonspam -0.1 bayes_auto_learn_threshold_spam 5.0 -
# A 'contact address' users should contact for more info. report_contact https://yourdomain.com/support.html - Verify syntax to make sure there are no errors in SpamAssassin configuration:
spamassassin --lint - Restart SpamAssassin and rebuild Exim configuration:
systemctl restart spamassassin da build exim_confImportant Note on DMARC Scoring: Only define high DMARC scores (e.g.,10.0or8.0) in SpamAssassin if DMARC processing is completely disabled at the ESF level, or ifvariables.dmarc.confdoes not exist or is empty. If ESF is already scoring DMARC independently, setting these scores here will result in double-counting the penalty and dropping legitimate mail prematurely.
Step 6: SpamAssassin Tuneup from the DirectAdmin GUI
- Log in to your DirectAdmin Control Panel → User Tab → E-Mail Manager → SpamAssassin Setup.
- Blocking strategy:
User Spambox— messages that are flagged as spam by SpamAssassin's own Global threshold, but don't reach ESF's combined kill score, land in the spambox for review instead of being silently dropped. - High score block:
No (disabled)This setting creates an independent drop path that runs before ESF's combined check even executes (confirmed in/etc/exim/spamd.acl_smtp_data.conf). If left enabled, a purely content-based SpamAssassin score above its threshold will hard-drop a message on its own — completely bypassing the weighted percentages you just configured in Step 4, including the intentional "content alone shouldn't auto-kill" behavior for the 50% weight. Disabling it here means the single combined ESF score (Step 4) is the only thing that decides whether a message is dropped, which is what your weighted design assumes. - Global threshold:
Custom/3.0— this is the point at which SpamAssassin marks a message as confirmed spam internally and triggers theEASY_IS_SPAM(02%) bonus from Step 4. It does not drop mail by itself. - Rewrite subject:
Yes/ Tag:***SPAM*** - Spam delivery:
Don't use attachments - Click Save Configuration.
Step 7: Enable RBL Blocking in the DirectAdmin GUI
- Admin Tab → Server Manager → Administrator Settings → E-mail Tab.
- Enable RBL Blocking in Exim.
- Click Save Settings.
Step 8: Train the Bayesian Filter Manually (Optional)
SpamAssassin's Bayes filter is the single strongest content signal it has, but it is not pre-trained on install, and does not retroactively learn from mail already sitting in a Spam/Junk folder. It only learns automatically, in real time, from newly-scanned mail that crosses its own auto-learn thresholds — a large backlog of manually-sorted spam will not be picked up unless trained explicitly.
- Check current training state (per mail account, not server-wide):
Look at thesa-learn --dump magic --dbpath=/home/<user>/.spamassassin/bayesnspamandnhamcounts — reliable classification needs at least ~200 of each. - Train from an existing spam/junk folder:
sa-learn --spam --dbpath=/home/<user>/.spamassassin/bayes \ /home/<user>/imap/<domain>/<mailbox>/Maildir/.INBOX.spam/cur/* \ /home/<user>/imap/<domain>/<mailbox>/Maildir/.INBOX.spam/new/* - Optional: keep training current with a daily cron job:
crontab -e0 3 * * * sa-learn --spam --dbpath=/home/<user>/.spamassassin/bayes /home/<user>/imap/<domain>/<mailbox>/Maildir/.INBOX.spam/cur/* --no-sync >/dev/null 2>&1 30 3 * * * sa-learn --sync --dbpath=/home/<user>/.spamassassin/bayes >/dev/null 2>&1
Step 9: Apply and Verify
Finally, perform a full system reboot to ensure all network socket changes, Exim ACLs, and SpamAssassin services are reloaded cleanly:
reboot
Watch live processing:
tail -f /var/log/exim/mainlog
Check what's being rejected and why:
tail -100 /var/log/exim/rejectlog
Confirm your DNS/RBL path is healthy:
dig +short 2.0.0.127.zen.spamhaus.org
rejectlog and the SpamTally header on a sample of real mail for a few days. If legitimate mail is being caught — most commonly forwarded mail through a relay with no rDNS — adjust the specific weight in Step 4 rather than the overall kill threshold.