2026年11月12日
2026年11月12日
Postfix header_checks and body_checks: Syntax, Actions and Testing
This article is a localized edition of the Japanese original. Read the Japanese original
header_checks and body_checks are Postfix's built-in, lightweight content inspection. They compare each header or body line of a message with regular-expression patterns, and when a line matches they reject the message, log it, rewrite it, and so on.
The official Postfix manual (header_checks(5)) describes this feature as designed to stop floods of mail from worms or viruses, and states that it does not decode attachments. Dedicated software such as SpamAssassin or rspamd is better suited for serious spam filtering. This article covers the syntax and actions of header_checks and body_checks, how to test them, and how to change the sender of mail sent from WordPress.
The content is based on the official Postfix 3.x manual as of October 2026. Paths and package names are Ubuntu / Debian examples.
Types of checks you can configure
In /etc/postfix/main.cf, you point each type of check at a rule file.
| Parameter | What it inspects | When |
|---|---|---|
header_checks |
The message's primary headers (Subject:, From: and so on) |
After receipt, before queuing (cleanup) |
mime_header_checks |
MIME-related headers (Content-Type: and so on). Defaults to the same as $header_checks |
Same as above |
nested_header_checks |
Headers of attached messages. Defaults to the same as $header_checks |
Same as above |
body_checks |
Each body line (including multipart boundary lines) | Same as above |
smtp_header_checks, smtp_body_checks |
Headers and body when delivering to external servers | When delivering via SMTP (Postfix 2.5 and later) |
header_checks and body_checks apply to every message that passes through Postfix: mail received from outside and mail sent by programs on the server such as WordPress.
The manual lists these limitations on what is inspected:
- Headers are inspected one logical header at a time (a folded multi-line header counts as one), and body lines one line at a time. A decision about one line is not carried over to the next
- If the body is encoded with Base64 or quoted-printable, the patterns must be written for the encoded form
- The same applies when headers are encoded per RFC 2047
- Headers that cleanup adds itself (
From:,Date:,Message-ID:and so on, when the original message lacked them) are not inspected - Body inspection covers each part from the start up to
body_checks_size_limit(default 51200 bytes)
Japanese subjects are normally encoded as =?UTF-8?B?...?=, so a pattern containing Japanese text, such as /^Subject:.*当選/, will not match. HTML mail bodies are also often sent in Base64, in which case searching for HTML tags or wording with body_checks finds nothing.
regexp vs pcre
Rule files are specified as regular-expression tables (regexp: or pcre:).
| Format | Regular expression type | Notes |
|---|---|---|
regexp: |
POSIX regular expressions | Works without extra packages |
pcre: |
Perl-compatible regular expressions | The manual says this is usually faster. On Debian / Ubuntu it needs the postfix-pcre package |
You can see which formats your system supports with postconf -m. If pcre is missing, install it like this on Ubuntu / Debian:
postconf -m
sudo apt install postfix-pcre
Both formats are case-insensitive by default. The i flag after the pattern toggles case sensitivity (writing /pattern/i makes the match case-sensitive instead). Perl-style syntax such as (?i) is not available in POSIX regular expressions (regexp:) and causes a pattern error. Since matching is already case-insensitive, you do not need it anyway.
regexp: and pcre: files do not need to be converted with postmap (only formats such as hash: produce a .db file).
Rule file syntax
Each line has a pattern and an action.
/pattern/flags action optional message
- Lines starting with
#and empty lines are ignored - A line that starts with whitespace continues the previous line (so you can wrap long rules)
- Patterns are tried from the top, and the action of the first matching rule runs
!/pattern/means "when it does not match", andif /pattern/...endifgroups rules under a condition
In the action, $1, $2 are replaced with the text matched by the parts of the pattern enclosed in ( ). If the pattern has no ( ), there is nothing for $1 to be replaced with.
Main actions
| Action | What it does |
|---|---|
REJECT text |
Rejects the whole message. Without text, a generic error is used; the default status code is 5.7.1 |
WARN text |
Logs a warning: record and moves to the next line. Use it to try out patterns |
INFO text |
Logs an info: record and moves to the next line (Postfix 2.8 and later) |
HOLD text |
Puts the message on the hold queue. Release or delete it with postsuper |
DISCARD text |
Claims successful delivery and silently discards the message |
IGNORE |
Deletes that line (header). There is also STRIP, which logs it (3.2 and later) |
PREPEND text |
Adds one line before the matching line. For headers, it must start with a valid header name |
REPLACE text |
Replaces the matching line. For headers, it must start with a valid header name |
REDIRECT address |
Sends the message to the given address instead of the original recipients |
BCC address |
Adds a BCC recipient (3.0 and later) |
REJECT, HOLD, DISCARD, REDIRECT, BCC and some others cannot be used in the delivery-time smtp_header_checks and smtp_body_checks. At delivery time you can use WARN, INFO, IGNORE, PREPEND, REPLACE and similar.
Set up header_checks
This example uses pcre:. Point main.cf at the rule file:
sudo postconf -e "header_checks = pcre:/etc/postfix/header_checks.pcre"
Create /etc/postfix/header_checks.pcre and write the rules. Start with everything as WARN and use the log to confirm that legitimate mail does not match.
# Messages whose subject contains certain English words (case-insensitive)
/^Subject:.*\b(free money|casino bonus)\b/ WARN spam keyword in subject
# Messages whose subject contains $$$ (escape $ with \ to treat it as a literal)
/^Subject:.*\$\$\$/ WARN dollar signs in subject
# Senders from a specific domain
/^From:.*@spam-domain\.example\b/ WARN blocked sender domain
# A specific mailer name
/^X-Mailer:.*\bmass mailer\b/ WARN mass mailer
If the log shows no problems, change WARN to REJECT. With REJECT you can also put a status code at the start of the message (for example REJECT 5.7.1 Message rejected).
Set up body_checks
sudo postconf -e "body_checks = pcre:/etc/postfix/body_checks.pcre"
# A body line contains a URL with a bare IP address
/https?:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\// WARN URL with IP address
# A line with 10 or more asterisks in a row
/\*{10,}/ WARN many asterisks
Because body_checks inspects one line at a time, it cannot judge text that spans several lines or the contents of encoded parts. Body inspection also runs regular expressions over every line of every message, so adding too many rules makes processing heavier.
Test before applying
postmap -q shows which rule a string matches. The output is the matching action; if nothing is printed, nothing matched.
# Test a single string
postmap -q "Subject: FREE MONEY for you" pcre:/etc/postfix/header_checks.pcre
# → WARN spam keyword in subject
# Test all headers of a saved message (.eml)
postmap -hmq - pcre:/etc/postfix/header_checks.pcre < sample.eml
# Test the body
postmap -bmq - pcre:/etc/postfix/body_checks.pcre < sample.eml
-h processes the input as headers, -b as body, and -m enables MIME parsing.
Apply and verify
# General check of the configuration
sudo postfix check
# Show the current values (-d shows defaults, so don't use it here)
postconf header_checks body_checks
# Apply
sudo postfix reload
postconf -d header_checks only shows Postfix's default value (empty), so it does not confirm what you configured. Use postconf header_checks to see the configured value, and postconf -n to list settings changed from the defaults.
When a rule matches, cleanup writes a line to the log (/var/log/mail.log on Ubuntu / Debian; /var/log/maillog or journalctl -u postfix on RHEL-family systems).
sudo grep -E "(warning|reject|info): (header|body) " /var/log/mail.log | tail -n 20
The entry reads like warning: header Subject: ... or reject: header Subject: ...: the action, the check type, the matching line and, at the end, the text you specified.
Change the sender of mail sent from WordPress
By default, WordPress's wp_mail() uses wordpress@your-site-domain as the sender (with any leading www. removed). To change it, setting it on the WordPress side is clearer than rewriting it in Postfix, and the effect is limited to that site's mail.
<?php
// Put this in your theme's functions.php or a custom plugin
add_filter( 'wp_mail_from', function ( $from ) {
return 'noreply@example.com';
} );
add_filter( 'wp_mail_from_name', function ( $name ) {
return 'Example Site';
} );
If several sites or programs send from the same server and you want to change this centrally, use Postfix's sender_canonical_maps. For example, to rewrite mail whose envelope sender (Return-Path) is the web server user (www-data on Ubuntu):
sudo postconf -e "sender_canonical_maps = pcre:/etc/postfix/sender_canonical.pcre"
# /etc/postfix/sender_canonical.pcre
/^www-data@.*$/ noreply@example.com
sudo postfix reload
By default, sender_canonical_maps rewrites both the envelope sender and the header sender. Alternatively, you can rewrite a header with REPLACE in header_checks. This example uses $1 for the part captured with ( ):
# Replace a leading [Old Name] in the subject with [New Name] (matches ASCII subjects only)
/^Subject:\s*\[Old Name\]\s*(.*)$/ REPLACE Subject: [New Name] $1
header_checks also applies to mail received from outside, so keep rewrite rules narrowly targeted. Rewriting headers after DKIM signing also breaks the signature. Avoid rewriting signed headers (From:, Subject: and so on) with the delivery-time smtp_header_checks.
Operational notes
- Introduce new rules as
WARN, confirm in the log that nothing is misclassified, and only then switch toREJECT. Rejecting legitimate mail bounces it back to the sender and can stall correspondence with business partners. - After changing a rule file, test it with
postmap -qbefore runningpostfix reload. - For serious spam filtering, don't try to do it all with header_checks; combine SPF, DKIM and DMARC verification with a dedicated filter.
Related articles
Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)