Optimizing SpamAssassin (spamd) Memory Footprint: Techniques for Resource-Constrained Servers


2 views

When running SpamAssassin's spamd daemon on Debian systems, memory usage can become a significant concern. The default configuration typically shows these characteristics:

  • 32-bit servers: 100-150MB virtual memory (50MB resident) per child process
  • 64-bit servers: Approximately double the 32-bit footprint
  • Default maximum of 5 child processes during peak loads

Add these settings to /etc/spamassassin/local.cf:

# Reduce rule memory footprint
bayes_ignore_header X-Mozilla-Status
bayes_ignore_header X-Mozilla-Status2
bayes_ignore_header X-UID
skip_rbl_checks 1

# Disable rarely used plugins
loadplugin Mail::SpamAssassin::Plugin::Rule2XSBody
loadplugin Mail::SpamAssassin::Plugin::AWL
loadplugin Mail::SpamAssassin::Plugin::AutoLearnThreshold

Modify /etc/default/spamassassin to include:

# Limit child processes
SPAMDOPTIONS="-d --max-children=2 --helper-home-dir"

# Set memory limits
ulimit -v 150000

Create a custom rules file to disable memory-intensive rules:

# In /etc/spamassassin/custom.cf
ifplugin Mail::SpamAssassin::Plugin::Rule2XSBody
  body MY_LOWMEM_RULE /.../   __HIT__ __MISS__
  score MY_LOWMEM_RULE 0.1
endif

After making changes, recompile rules for better performance:

sa-compile --install
systemctl restart spamassassin

Verify memory usage improvements with:

ps -eo pid,user,args,%mem,rss --sort=-rss | grep spamd

Consider setting up a cron job to log memory usage periodically for trend analysis.


When running SpamAssassin on Debian systems, many administrators notice each spamd child process consumes 100-150MB on 32-bit systems and nearly double that on 64-bit architectures. With default configurations (Pyzor/AWL/Bayes disabled, sa-compile enabled) and peak loads triggering multiple child processes, memory usage can balloon to 600MB.

First, let's examine critical local.cf adjustments:

# Reduce rule memory footprint
score ANY_BOUNCE_MESSAGE 0
score HTML_MESSAGE 0
score HTML_FONT_LOW_CONTRAST 0

# Disable heavy plugins
loadplugin Mail::SpamAssassin::Plugin::TextCat 0
loadplugin Mail::SpamAssassin::Plugin::ASN 0

Modify /etc/default/spamassassin:

OPTIONS="--max-children=2 --min-children=1 --child-helper-threads=2 \
         --nouser-config --min-spare=1 --max-spare=1"
MAX_CHILDREN=2

This forces tighter process control while maintaining reasonable throughput.

Rebuild rules with aggressive optimizations:

sa-compile --keep-tmprules --optimize-cf --no-network

Then add to local.cf:

use_bayes 0
bayes_auto_learn 0
use_pyzor 0
use_auto_whitelist 0

For bare metal servers, consider these sysctl tweaks:

vm.swappiness = 1
vm.vfs_cache_pressure = 50
vm.dirty_ratio = 10
vm.dirty_background_ratio = 5

Implement this pre-filter in your MTA (Postfix example):

smtpd_mime_restrictions = 
    check_mime_access regexp:/etc/postfix/mime_size_limits

# /etc/postfix/mime_size_limits
/^Content-Length:.*[5-9][0-9][0-9][0-9][0-9][0-9]/ REJECT

To identify exactly which rules consume memory:

spamassassin --debug --lint 2>&1 | grep 'memory' | sort -k5 -n

For Docker deployments, enforce memory limits:

docker run -d --memory=256m --memory-swap=512m \
           --name spamd spamassassin