SpamAssassin is an open source spam filter that scores each message against hundreds of tests: header and body patterns, DNS blocklists, SPF and DKIM results, and a Bayesian classifier that learns from your own mail. Messages above a threshold are marked as spam so your mail client or server can file them in a Junk folder, or rejected outright when the score is very high. In this tutorial you will install SpamAssassin on Ubuntu 24.04, connect it to Postfix through spamass-milter, tune its configuration, keep its rules updated and train the Bayesian filter.

Prerequisites

To follow this tutorial, you need:

  • A server running Ubuntu 24.04 LTS with Postfix receiving mail for your domain, for example a CubePath VPS.
  • A non-root user with sudo privileges.
  • At least 2 GB of RAM. Each SpamAssassin child process uses around 100 MB, and the default configuration runs up to five.

Step 1 - Installing SpamAssassin

On Ubuntu 24.04, SpamAssassin is split into several packages: spamassassin (rules and command line tools), spamd (the scanning daemon) and spamc (the lightweight client that talks to it). spamass-milter connects Postfix to spamd. Install them together:

sudo apt update
sudo apt install spamassassin spamd spamc spamass-milter

Enable and start the daemon:

sudo systemctl enable --now spamd
systemctl status spamd --no-pager
● spamd.service - Perl-based spam filter using text analysis
     Loaded: loaded (/usr/lib/systemd/system/spamd.service; enabled; preset: enabled)
     Active: active (running)

spamd listens on 127.0.0.1:783 by default and starts its child processes from the options in /etc/default/spamd. The defaults (--create-prefs --max-children 5 --helper-home-dir) are fine for a small or medium server.

Step 2 - Configuring the scoring rules

Site-wide settings go in /etc/spamassassin/local.cf. Open it:

sudo nano /etc/spamassassin/local.cf

The file ships with every option commented out. Add the following at the end:

# Score at which a message is considered spam (default 5.0)
required_score 5.0

# Mark spam in the subject line
rewrite_header Subject [SPAM]

# Add headers instead of wrapping spam in a new message
report_safe 0

# Bayesian classifier with automatic learning
use_bayes 1
bayes_auto_learn 1
  • required_score 5.0 is the upstream default and a good start. Lower values catch more spam but produce more false positives.
  • report_safe 0 keeps the original message intact and adds X-Spam-* headers, which makes filtering by header and training much easier than the default attachment format.

Check the configuration for syntax errors:

sudo spamassassin --lint

No output means the configuration is valid. Restart the daemon to load it:

sudo systemctl restart spamd

Step 3 - Keeping the rules updated

Spam changes constantly, so the rules must be updated. The spamassassin package on Ubuntu 24.04 installs a systemd timer, spamassassin-maintenance.timer, that runs sa-update daily. Check that it is active:

systemctl list-timers spamassassin-maintenance.timer --no-pager
NEXT                        LEFT     LAST PASSED UNIT                           ACTIVATES
Fri 2026-09-26 00:17:00 UTC 13h left -    -      spamassassin-maintenance.timer spamassassin-maintenance.service

If it is not listed as active, enable it with sudo systemctl enable --now spamassassin-maintenance.timer. Run a first update right away instead of waiting for the timer:

sudo sa-update -v
sudo systemctl restart spamd

sa-update exits with code 0 when it installed new rules and code 1 when the rules were already current; both are fine.

Step 4 - Connecting Postfix through spamass-milter

spamass-milter receives each incoming message from Postfix during the SMTP session, passes it to spamd with spamc, and adds the result as headers. Because it runs during the SMTP session, it can also reject obvious spam before your server accepts it, instead of generating bounces later.

Open its defaults file:

sudo nano /etc/default/spamass-milter

Change the OPTIONS line to:

OPTIONS="-u spamass-milter -i 127.0.0.1 -I -r 15"
  • -u spamass-milter runs the scan as the spamass-milter user, so all mail shares one Bayesian database in that user's home directory.
  • -i 127.0.0.1 skips mail submitted from the server itself.
  • -I skips mail from clients that logged in with SMTP AUTH, so your own users' outgoing mail is not scanned.
  • -r 15 rejects messages that score 15 or more during the SMTP session. Anything between required_score and 15 is accepted and marked. Remove this option if you prefer never to reject.

On Ubuntu, spamass-milter creates its socket at /var/spool/postfix/spamass/spamass.sock, inside the Postfix chroot, owned by postfix. Restart it and confirm the socket exists:

sudo systemctl restart spamass-milter
sudo ls -l /var/spool/postfix/spamass/
srw-rw---- 1 postfix postfix 0 Sep 25 11:02 spamass.sock

Now tell Postfix to use the milter. Open its main configuration:

sudo nano /etc/postfix/main.cf

Add these lines at the end:

# Spam filtering with SpamAssassin
milter_default_action = accept
smtpd_milters = unix:spamass/spamass.sock

The path is relative to the Postfix queue directory, /var/spool/postfix. If you already use OpenDKIM, keep both milters on one line, for example smtpd_milters = unix:spamass/spamass.sock, inet:localhost:8891, and keep your existing non_smtpd_milters line as it is. milter_default_action = accept lets mail through unscanned if the filter is down, instead of rejecting it.

Validate and reload Postfix:

sudo postfix check
sudo systemctl reload postfix

Step 5 - Testing with GTUBE

GTUBE is a standard test string that every SpamAssassin installation scores as spam, much like the EICAR file for antivirus. First test spamd directly. Create a test message:

nano ~/gtube.eml
Subject: GTUBE test
From: [email protected]
To: you@your_domain

XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X

Pass it through spamc and check the verdict:

spamc -c < ~/gtube.eml
1000.0/5.0

The first number is the score, the second the threshold. A clean message shows something like 0.8/5.0.

To test the whole path through Postfix, send a normal message from an external mailbox (Gmail or Outlook) to an address on your server and look at the headers of the received message:

X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-14) on mail.your_domain
X-Spam-Level:
X-Spam-Status: No, score=-0.1 required=5.0 tests=DKIM_SIGNED,DKIM_VALID,
	DKIM_VALID_AU,SPF_PASS autolearn=ham autolearn_force=no version=4.0.0

The mail log shows each scanned message:

sudo journalctl -u spamd -n 20 --no-pager | grep result
spamd[6012]: spamd: result: . 0 - DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,SPF_PASS scantime=0.4,size=4312,user=spamass-milter,...

The first field after result: is Y for spam and . for ham. If you send the GTUBE message from outside with -r 15 enabled, the sender gets a rejection instead.

Step 6 - Training the Bayesian filter

The Bayesian classifier learns what your spam and legitimate mail (ham) look like. It only starts contributing to scores after it has learned from at least 200 spam and 200 ham messages. Auto-learning feeds it slowly; manual training with real mail speeds it up a lot.

The scans run as the spamass-milter user, so the database lives in that user's home directory. Find it:

getent passwd spamass-milter | cut -d: -f6
/var/lib/spamass-milter

The .spamassassin directory is created on the first scan. Create it now in case no mail has been scanned yet, so sa-learn writes the database in the right place:

sudo install -d -o spamass-milter -g spamass-milter -m 700 /var/lib/spamass-milter/.spamassassin

Train it with a folder of spam and a folder of ham, for example the Junk and INBOX folders of a Maildir. Replace the paths with the real ones on your server:

sudo sa-learn --dbpath /var/lib/spamass-milter/.spamassassin --spam /var/mail/vhosts/your_domain/user/.Junk/cur
sudo sa-learn --dbpath /var/lib/spamass-milter/.spamassassin --ham /var/mail/vhosts/your_domain/user/cur
Learned tokens from 214 message(s) (230 message(s) examined)

Because sa-learn ran as root, give the database back to the scanning user:

sudo chown -R spamass-milter:spamass-milter /var/lib/spamass-milter/.spamassassin

Check the counters:

sudo -u spamass-milter sa-learn --dump magic | grep -E "nspam|nham"
0.000          0        214          0  non-token data: nspam
0.000          0        356          0  non-token data: nham

Repeat the training regularly, for example once a week, with messages users moved to Junk and messages they rescued from it.

Step 7 - Allowing and blocking senders

To make sure a trusted sender is never marked as spam, or a persistent one always is, add entries to /etc/spamassassin/local.cf:

# Always accept mail from this sender when relayed by a host in example.com
welcomelist_from_rcvd [email protected] example.com

# Always mark as spam
blocklist_from *@spammy.example

SpamAssassin 4 renamed whitelist_* and blacklist_* options to welcomelist_* and blocklist_*; the old names still work. welcomelist_from_rcvd is safer than a plain welcomelist_from, because it also requires the message to come through a host in the given domain (example.com here), which spammers cannot fake. Run sudo spamassassin --lint and sudo systemctl restart spamd after changing the file.

Troubleshooting

  • No X-Spam-Status header on incoming mail. Postfix is not calling the milter. Run postconf smtpd_milters, check that the socket exists in /var/spool/postfix/spamass/, and look for milter errors in /var/log/mail.log.
  • connect to Milter service unix:spamass/spamass.sock: No such file or directory. spamass-milter is not running or was started before its directory existed. Restart it with sudo systemctl restart spamass-milter.
  • Legitimate mail marked as spam. Read the tests= list in the X-Spam-Status header to see which rules fired. Fix the root cause when it is on your side (missing SPF or DKIM on the sender, for example), train the message as ham, or add a welcomelist_from_rcvd entry.
  • High memory use. Lower --max-children in /etc/default/spamd, for example to 3, and restart spamd.

Conclusion

Incoming mail on your server is now scored by SpamAssassin during the SMTP session, very obvious spam is rejected before it is accepted, rules update daily, and the Bayesian filter learns from your own mail. As next steps, add a Sieve rule in Dovecot that moves messages with X-Spam-Flag: YES to the Junk folder, and review your own outgoing setup with How to Stop Your Server's Emails from Going to Spam.