Hands-on: the survival guide for anti-spam filter transition and infrastructure validation
Back to blog

Hands-on: the survival guide for anti-spam filter transition and infrastructure validation

6/7/2026 · 4 min · Infrastructure

Working with email infrastructure is often a game of patience and precision. Recently, I faced a classic yet complex scenario: migrating security tools (such as SpamAssassin, cPguard, and SPFBL) and the critical need to validate staging environments before the final DNS switch.

In this report, I share how I handled filtering configurations, latency diagnostics, record validation checks, and the firewall commands that saved the day.


1) Fine-tuning SpamAssassin (directadmin/cpanel)#

SpamAssassin operates by scoring incoming messages against hundreds of heuristic rules. The sum of these values yields the final spam Score.

1.1 preventative configuration backup#

Before performing modifications to the anti-spam core engine or the MTA configurations, save copies of directory parameters:

# Backup SpamAssassin configuration files
sudo tar czf /root/spamassassin-backup-$(date +%Y%m%d).tar.gz /etc/mail/spamassassin/

# Backup CSF firewall files
sudo tar czf /root/csf-backup-$(date +%Y%m%d).tar.gz /etc/csf/

# Complete backup of Exim and SpamAssassin files
sudo tar czf /root/mail-config-backup-$(date +%Y%m%d).tar.gz \
  /etc/exim.conf \
  /etc/mail/ \
  /etc/csf/ 2>/dev/null || true

1.2 threshold settings and rigor customizations#

The main settings reside inside /etc/mail/spamassassin/local.cf. The target threshold (default required score is usually 5.0) is inversely proportional to strictness. Configuring a very low score (e.g. 2.0) makes the filter aggressive, increasing the frequency of false positives.

To adjust the spam threshold, edit the configuration:

sudo nano /etc/mail/spamassassin/local.cf

Modify the parameter:

# Score threshold to mark a message as SPAM
required_score 4.0

Save the file and restart the daemon:

sudo systemctl restart spamassassin

(Note: on servers running Exim with Spamd integration, MTA restarts may also be needed: sudo systemctl restart exim).

1.3 whitelists, blacklists, and custom rules#

To bypass filters for trusted partners or drop spam from abusing senders, configure rules in your local.cf file:

# Whitelist entries (emails that will NEVER get flagged as SPAM)
whitelist_from [email protected]
whitelist_from *@trusted-domain.com

# Blacklist entries (emails blocked by default)
blacklist_from [email protected]
blacklist_from *@abusive-domain.net

# Disable rules with high false-positive rates locally
score URIBL_BLOCKED 0

# Create custom header matching rules
header LOCAL_SPAM_BUY Subject =~ /buy now/i
describe LOCAL_SPAM_BUY Subject contains purchase triggers
score LOCAL_SPAM_BUY 2.5

1.4 checking the spam score of a message#

To check how a raw message gets scored by SpamAssassin before delivery:

# Parse a raw message file or mailbox file
spamassassin -t < /var/mail/user

# Or pipe a message file directly
cat email.txt | spamassassin -t

# Monitor real-time score logging in syslog files
grep "score=" /var/log/mail.log | tail -n 20

2) MTA integration (exim/postfix)#

SpamAssassin receives messages parsed by the MTA daemon. In Exim, this integration utilizes transport pipes or system filter configurations (system_filter).

Example transport configurations in /etc/exim.conf:

spamassassin_pipe_transport:
  driver = pipe
  command = /usr/bin/spamc -u ${local_part}
  user = nobody
  group = nogroup

To drop spam before it reaches the mailbox using system_filter.conf:

if $h_X-Spam-Level: contains "*****"
then
    fail text "Message rejected due to high Spam score."
    noerror
endif

3) Diagnosing sender authentication (SPF, DKIM, DMARC) and MX#

When facing email delivery latency (delays), isolate the issue by checking authentication records. Receiver MTAs might apply greylisting policies on unauthenticated incoming paths.

# 1. Check SPF record
dig TXT mydomain.com.br | grep -i "v=spf1"

# 2. Check DKIM record (replace selector with target, e.g., default)
dig TXT default._domainkey.mydomain.com.br

# 3. Check DMARC policy
dig TXT _dmarc.mydomain.com.br

# 4. Check MX records
dig MX mydomain.com.br +short

# 5. Check all records in a loop
for record in TXT MX NS; do
    echo "=== Record: $record ==="
    dig $record mydomain.com.br +short
done

4) Delivery verification and mail queues#

4.1 sending test emails and checking queues#

Validate outbound connectivity and check for IP blockages on RBL lists:

# Send a test email from the command line
echo "Operational test email body" | mail -s "Anti-Spam Migration Test" [email protected]

# View the mail queue (Exim)
exim -bp

# List queue count statistics
mailq

4.2 monitoring logs for delivery status#

Track delivery loops in real time:

# Exim main log (cPanel/DirectAdmin hosts)
tail -f /var/log/exim_mainlog | grep "=>"

# Postfix/SpamAssassin system log (Debian/Ubuntu hosts)
tail -f /var/log/mail.log | grep -E "postfix|spamd"

4.3 SMTP connection verification via telnet (port 25)#

Check SMTP transaction flows directly:

telnet mydomain.com.br 25
EHLO test.com
MAIL FROM:<[email protected]>
RCPT TO:<[email protected]>
DATA
Subject: Manual SMTP Test
Email body goes here
.
QUIT

4.4 inspecting email headers#

To diagnose false positives, inspect raw headers at the receiver's end:

Authentication-Results: mx.google.com;
       dkim=pass [email protected] header.s=default header.b=XyZ;
       spf=pass (google.com: domain of [email protected] designates 192.168.1.100 as permitted sender) smtp.mailfrom=[email protected];
       dmarc=pass (p=REJECT sp=NONE dis=NONE) header.from=mydomain.com.br

5) Local resolution via hosts file#

During migration tasks, test host behavior using local pointer resolution before shifting live public DNS records (which takes time to propagate depending on TTL values).

5.1 hosts file paths by operating system#

5.2 editing the hosts file on linux/macos#

Open the hosts file with admin privileges:

sudo nano /etc/hosts

Add the entry mapping target IPs to your domain:

192.168.1.100 mydomain.com.br www.mydomain.com.br

5.3 DNS TTL (time to live) and propagation Windows#

Verify TTL values before committing to production changes:

# Check zone answer TTL
dig mydomain.com.br | grep -A1 "ANSWER SECTION"

Monitor MX/TXT propagation status across multiple resolvers:

for resolver in 8.8.8.8 1.1.1.1 208.67.222.222; do
    echo "Resolver: $resolver -> MX: $(dig @$resolver mydomain.com.br MX +short)"
done

6) CSF firewall configurations and CLI management#

ConfigServer Security & Firewall (CSF) is a popular wrapper for iptables on Linux mail servers.

6.1 configuring inbound and outbound port access#

Open configuration file /etc/csf/csf.conf and ensure standard secure email ports (25, 465, 587, 143, 993, 110, 995) are allowed:

# Inbound TCP ports
TCP_IN = "25,80,443,110,143,465,587,993,995,2087,2083"

# Outbound TCP ports
TCP_OUT = "25,80,443,110,143,465,587,993,995"

After editing, reload the firewall rules:

sudo csf -r

6.2 tuning the login failure daemon (LFD)#

The LFD daemon monitors system security logs (/var/log/secure and /var/log/mail.log) and blocks IPs showing too many failed login attempts:

# /etc/csf/csf.conf

# Failed login limit before block trigger
LF_TRIGGER = "5"

# Lock permanence setting (1 = permanent block, 0 = temporary)
LF_PERMBLOCK = "1"

# Permanent block time window (in seconds, e.g., 86400 = 24 hours)
LF_PERMBLOCK_INTERVAL = "86400"

6.3 useful CSF CLI commands#

# Query if an IP address is blocked by the firewall
csf -g 1.2.3.4

# Remove an IP from temporary or permanent blocks
csf -dr 1.2.3.4

# Add an IP to the permanent Whitelist (always allow)
csf -a 1.2.3.4 "Client Office IP"

# Add an IP to the permanent Blacklist (always block)
csf -d 1.2.3.4 "Attacker IP address"

Checklist: anti-spam filter transition and migration#

1. Pre-migration phase#

2. Migration phase#

3. Post-migration validation#

Was this article helpful?

Leave a quick reaction to help prioritize future technical guides:

CC BY-NC

This post is licensed under CC BY-NC.

Comments

Join the discussion below.

0 comments