Trace Postfix Mail Through cleanup(8) Safely

A message went in with one set of headers and came out of the queue with another, and Postfix cleanup(8) is the daemon that did it. This guide shows you what cleanup does on this host, which settings steer it, and how to verify a change without treating the daemon as an interactive mail command.

Allow about 15 minutes for inspection, or longer if you send a controlled test message. It describes Postfix 3.8.6, installed from the Ubuntu package postfix 3.8.6-1ubuntu0.1 on the reference system. Configuration names and defaults come from the installed cleanup(8) manual page, so check your own package version before applying an example.

1. Confirm the daemon and package version

cleanup(8) is a Postfix service, not something you normally run against a message file. Postfix's process manager starts it from master.cf. Mail arrives through services such as smtpd(8) or the local sendmail(1) compatibility interface. Cleanup then canonicalises the message, places it in the incoming queue, and informs the queue manager.

Run these read-only checks as your normal user:

$ postconf -h mail_version
3.8.6
$ postconf -M cleanup
cleanup    unix  n       -       y       -       0       cleanup
$ postconf -h config_directory
/etc/postfix
$ postconf -h queue_directory
/var/spool/postfix

The service line is the important part: it proves this installation has a cleanup service entry. Do not try to run an undocumented cleanup --help command. The manual synopsis is just cleanup plus generic Postfix daemon options, because master supplies the service context.

Checkpoint: Note the version, configuration directory and queue directory. If postconf fails, stop and use the package's normal repair procedure rather than editing master.cf by hand.

2. Understand what cleanup changes

Cleanup sits between an arriving message and its queue file. It does these jobs:

That explains a classic diagnostic trap: the message a sender submits is not necessarily byte-for-byte the queued message. A header may have been inserted, a blind-copy header removed, or an address rewritten before delivery.

Cleanup is not a general-purpose content filter. The manual describes header_checks and body_checks as protection against floods of worms or viruses, not as attachment extraction or archive inspection.

3. Inspect effective settings first

Use postconf to ask Postfix for the effective value of a setting. This is read-only and tells an empty table apart from an inherited default:

$ postconf -h always_add_missing_headers
no
$ postconf -h message_drop_headers
bcc, content-length, resent-bcc, return-path
$ postconf -h local_header_rewrite_clients
permit_mynetworks, permit_sasl_authenticated, defer_unauth_destination
$ postconf -h canonical_maps
$ postconf -h virtual_alias_maps

Blank output means a lookup table setting is empty, not that cleanup is broken. Defaults for local_header_rewrite_clients and other parameters are installation-specific, so trust your own output rather than copying a value from another host.

For a full view of non-default settings, run:

$ postconf -n

Look for address mapping, header and body checks, Milters, recipient limits and the soft_bounce safety setting.

Warning: Treat postconf -n as diagnostic output. Do not paste the whole result into a ticket or public issue without removing hostnames, internal domains and mailbox details.

4. Change one setting deliberately

Most investigations need no change. If you have a specific operational reason to alter a setting, save the current value first and change one thing at a time. Editing Postfix configuration affects mail processing and needs elevated privileges.

For example, to request insertion of missing standard headers, record the current value first:

$ postconf -h always_add_missing_headers
no
$ sudo postconf -e 'always_add_missing_headers = yes'
$ postconf -h always_add_missing_headers
yes

This does not add headers to messages already queued. It changes how mail processed from now on is handled. The manual says cleanup processes run for a limited time and pick up main.cf changes automatically; postfix reload speeds that up.

Warning: Do not casually change message_drop_headers, address rewriting maps, recipient limits or Milter actions on a production relay. Removing Return-Path or allowing an address rewrite can affect downstream delivery and investigation.

Recovery: If you changed a setting only for a test, restore the saved value with sudo postconf -e and reload again.

$ sudo postconf -e 'always_add_missing_headers = no'
$ sudo postfix reload

A configuration edit has no undo transaction, so the old value you recorded is your recovery reference.

5. Reload and verify the running configuration

Reload only after checking the edited value. It asks Postfix to reread configuration and is normally brief, but it still changes a live mail service. Use an administrator account:

$ sudo postfix reload
postfix/postfix-script: refreshing the Postfix mail system

The wording can vary with the package. Confirm the value after the reload and check service health:

$ postconf -h always_add_missing_headers
yes
$ sudo postfix status
postfix/postfix-script: the Postfix mail system is running: PID: ...

Checkpoint: The PID and exact status text vary. A clean reload proves only that Postfix accepted the configuration operation, not that a message passed every check. Use logs and a controlled message for behavioural verification.

6. Test the receive path

A real test message changes queue and delivery state. Use a mailbox you control, a disposable local account, or a test domain whose delivery you understand, and never send test mail to an unrelated person. This local submission uses the installed Sendmail-compatible interface and needs no root privileges:

$ /usr/sbin/sendmail -t -oi <<'EOF'
From: [email protected]
To: YOUR_TEST_ADDRESS
Subject: cleanup test

cleanup test message
EOF
$ printf 'sendmail exit status: %s\n' "$?"
sendmail exit status: 0

Replace YOUR_TEST_ADDRESS with a real test address before running it. The example.invalid sender is deliberately non-deliverable, so use it only where your local policy accepts that envelope and header. A zero exit status means the submission interface accepted the request, not that the recipient received it.

Now inspect the queue and logs with the tools your distribution has:

$ postqueue -p
$ sudo journalctl -u postfix --since '5 minutes ago' --no-pager
$ sudo tail -n 50 /var/log/mail.log

Tip: Some systems use syslog files rather than a postfix journal unit, and the log may be /var/log/maillog instead of /var/log/mail.log. Use the command that exists. Search for the queue ID, cleanup and any rejection or defer diagnostic. Cleanup problems go to syslog or postlogd(8), so the log is far more useful than trying to attach to the daemon.

7. Diagnose failures and recover safely

If submission fails, separate the layers:

For an unexpected queued message, note its queue ID from postqueue -p and inspect it with the installed Postfix queue tools before removing anything. Do not delete queue files directly from /var/spool/postfix.

Warning: If a test message must go, use the documented queue administration command for that queue ID, and first confirm it is your test. Queue deletion is destructive and has no general undo.

If a configuration change causes widespread deferrals or rejections, restore the previous value, reload Postfix and watch the logs. Keep the original test message and queue ID until you have captured the diagnostic. Do not answer a transient error by disabling all checks or setting broad address rewrites; that can turn a local fault into unwanted mail delivery.

Done means