How to Configure DirectAdmin Spam Protection

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.

Prerequisites: Root SSH access to your Debian 13 server with DirectAdmin, Exim, Easy Spam Fighter, and SpamAssassin already installed.
Read this first — how the three layers actually work together:

DirectAdmin's mail stack is not three independent filters. It is one pipeline:

  1. ESF (RCPT stage) checks SPF, DKIM, reverse DNS, and RBL membership before the message body arrives, and starts a running score (acl_m_easy69).
  2. 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.4 adds 34 points to ESF's tally).
  3. ESF's final check compares the combined total against EASY_HIGH_SCORE_DROP and 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.

  1. Follow the documentation at:
    https://frankwebhost.com/knowledgebase/3/How-to-Configure-Unbound-DNS-Resolver-on-Debian-13-Cloud-VPS.html
  2. Verify:
    dig +short 2.0.0.127.zen.spamhaus.org
    You should consistently see 127.0.0.2, 127.0.0.4, or 127.0.0.10 — never 127.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.

  1. Edit the data-stage ACL Exim file:
    nano /etc/exim.acl_check_message.pre.conf
  2. 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
  3. 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.

  1. Edit the custom Exim strings file:
    nano /etc/exim.strings.conf.custom
  2. 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"
  3. 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.

  1. nano /etc/exim.easy_spam_fighter/variables.conf.custom
  2. # ============================================================
    # 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
  3. Rebuild Exim configuration:
    da build exim_conf
What this means in practice:
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.

Remember the ×10 scale-up: a SpamAssassin score of 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.
  1. nano /etc/mail/spamassassin/local.cf
  2. 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
  3. # ====================================================
    # 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
  4. # A 'contact address' users should contact for more info.
    report_contact https://yourdomain.com/support.html
  5. Verify syntax to make sure there are no errors in SpamAssassin configuration:
    spamassassin --lint
  6. Restart SpamAssassin and rebuild Exim configuration:
    systemctl restart spamassassin
    da build exim_conf
    Important Note on DMARC Scoring: Only define high DMARC scores (e.g., 10.0 or 8.0) in SpamAssassin if DMARC processing is completely disabled at the ESF level, or if variables.dmarc.conf does 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

  1. Log in to your DirectAdmin Control Panel → User Tab → E-Mail Manager → SpamAssassin Setup.
  2. 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.
  3. 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.
  4. Global threshold: Custom / 3.0 — this is the point at which SpamAssassin marks a message as confirmed spam internally and triggers the EASY_IS_SPAM (02%) bonus from Step 4. It does not drop mail by itself.
  5. Rewrite subject: Yes / Tag: ***SPAM***
  6. Spam delivery: Don't use attachments
  7. Click Save Configuration.

Step 7: Enable RBL Blocking in the DirectAdmin GUI

  1. Admin Tab → Server Manager → Administrator Settings → E-mail Tab.
  2. Enable RBL Blocking in Exim.
  3. 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.

  1. Check current training state (per mail account, not server-wide):
    sa-learn --dump magic --dbpath=/home/<user>/.spamassassin/bayes
    Look at the nspam and nham counts — reliable classification needs at least ~200 of each.
  2. 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/*
  3. Optional: keep training current with a daily cron job:
    crontab -e
    0 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
Tune gradually: After enabling this, monitor 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.
  • 1 Users Found This Useful
Was this answer helpful?

Related Articles

Integrating Roundcube "Mark as Junk" Plugin with SpamAssassin (Bayes) Train on DirectAdmin

This tutorial provides a complete, production-ready guide to integrating the Mark as Junk plugin...