2026年11月12日

2026年11月12日

Postfix Rate Limiting: Inbound (anvil) and Delivery-Side Limits

This article is a localized edition of the Japanese original. Read the Japanese original

Postfix has two separate mechanisms for rate limiting: the "inbound side (smtpd plus anvil)," which restrains clients connecting from outside, and the "delivery side (queue manager)," which restrains how fast you send mail out. When you want to curb bulk sending from WordPress, the delivery-side settings are the ones that work in most setups. After reading this article, you will be able to decide which settings apply to your goal, set limits with parameters that actually exist, and confirm in the logs that they are working.

The difference between the two mechanisms

Inside Postfix, mail goes through roughly two stages: "acceptance" and "delivery."

  • Inbound side: When an outside client connects over SMTP, smtpd responds. A service called anvil counts connections and messages, and returns a temporary error (4xx) to clients that exceed the limits. The goal is to restrain spam sources and bulk sending from accounts that have been compromised.
  • Delivery side: The queue manager (qmgr) hands queued mail to the smtp client, destination by destination. Tuning concurrent deliveries and the delivery interval here lets you match the acceptance limits of the destination server or email delivery service.

Why inbound limits do not apply to mail sent from WordPress

By default, WordPress's wp_mail() uses PHP's mail() function and hands the message to Postfix's sendmail command. This path does not go through smtpd and enters the queue directly from a service called pickup, so anvil's limits do not apply.

If you use an SMTP plugin that sends to 127.0.0.1:25 on the same server, mail does go through smtpd, but the inbound limits have an exemption setting, and by default clients in mynetworks (which normally includes localhost) are not counted.

In short, it is easier to think of it this way: use the delivery-side settings when you want to limit how much WordPress sends, and the inbound settings when you want to prevent abusive bulk sending from outside.

Check the current values before changing anything

Before changing main.cf, save the current settings.

sudo cp /etc/postfix/main.cf /etc/postfix/main.cf.bak
sudo cp /etc/postfix/master.cf /etc/postfix/master.cf.bak

# Show only the settings changed from their defaults
postconf -n

# Default values of the related parameters
postconf -d anvil_rate_time_unit smtpd_client_connection_count_limit \
  smtpd_client_event_limit_exceptions default_destination_concurrency_limit \
  default_destination_rate_delay default_destination_recipient_limit

The parameters introduced here assume Postfix 3.x, which is widely used as of October 2026. Some of them are not available in every version, so check your version with postconf mail_version.

Inbound side: limit per source with anvil

All inbound limits are counted in the form "how many times per client (IP address) within anvil_rate_time_unit." Note that changing the time unit changes the meaning of every *_rate_limit.

# /etc/postfix/main.cf

# Time unit for rate calculation (default 60s)
anvil_rate_time_unit = 60s

# Maximum concurrent connections per client: 10
smtpd_client_connection_count_limit = 10

# Per client, up to 30 new connections per 60 seconds
smtpd_client_connection_rate_limit = 30

# Per client, up to 20 messages per 60 seconds
smtpd_client_message_rate_limit = 20

# Per client, up to 100 recipients per 60 seconds
smtpd_client_recipient_rate_limit = 100

# Per client, up to 10 AUTH commands per 60 seconds (Postfix 3.1 and later)
smtpd_client_auth_rate_limit = 10

# Clients exempt from the limits (default is mynetworks)
smtpd_client_event_limit_exceptions = $mynetworks

The *_rate_limit parameters, such as smtpd_client_connection_rate_limit, default to 0 (no limit). Only smtpd_client_connection_count_limit has a nonzero default of 50.

In main.cf, writing # in the middle of a line does not make a comment. If you write smtpd_client_message_rate_limit = 20 # 20 messages, everything after # is treated as part of the value, which cannot be interpreted as a number and causes an error. If you want to add a comment, put it on its own line.

How to choose values

Before choosing values, find out the current maximums. anvil periodically logs the maximum values it has observed for each service.

# Maximum connection rate and message rate recorded by anvil
grep "postfix/anvil" /var/log/mail.log | grep -E "max (connection|message) rate"

If you set the limits with some margin above the maximums of your normal users, you can stop only abnormal sending without getting in the way of regular sending. If some clients legitimately send in bulk, such as an office multifunction printer or several WordPress sites running on other servers, add their IPs to smtpd_client_event_limit_exceptions.

Use different values per service

Port 25 (receiving from outside servers) and port 587 (for authenticated users who send mail) need different limits. In master.cf, you can override values per service with -o.

# The submission service in /etc/postfix/master.cf (add -o to the existing lines)
submission inet n       -       y       -       -       smtpd
  -o smtpd_client_message_rate_limit=60
  -o smtpd_client_recipient_rate_limit=300

With this, only port 587 allows "60 messages and 300 recipients per 60 seconds." Match the values in the columns such as chroot to the existing lines in your environment.

Delivery side: limit how fast you send

On the delivery side, you tune, per destination (by default the recipient's domain, or the relay if you have set relayhost), how many deliveries run at the same time and how much time to leave between them.

# /etc/postfix/main.cf

# Maximum concurrent deliveries to the same destination (default 20)
default_destination_concurrency_limit = 10

# Interval between deliveries to the same destination: 1 second (= at most one delivery per second to the same destination)
default_destination_rate_delay = 1s

# Maximum number of recipients per delivery (default 50)
default_destination_recipient_limit = 50

Parameters that start with default_ are defaults for all transports. To apply a setting to the smtp transport only, use a name with the transport name at the front, such as smtp_destination_concurrency_limit, smtp_destination_rate_delay, and smtp_destination_recipient_limit.

How rate_delay and recipient_limit interact

If you set *_destination_rate_delay to anything other than 0, deliveries to the same destination happen one at a time at the specified interval. Whether "one" means one message or one recipient is determined by the corresponding *_destination_recipient_limit.

  • recipient_limit of 2 or more: recipients at the same domain are delivered together in one delivery, so one delivery contains multiple recipients
  • recipient_limit of 1: each delivery contains a single recipient, so the limit becomes "at most one recipient per second"

If your email delivery service's limit is defined as "recipients per second," unless you set recipient_limit to 1, you may send more than you intended.

When sending through relayhost

If you relay through relayhost to a delivery service such as Amazon SES, every message goes to the same relay destination. As a result, the smtp rate_delay effectively becomes the cap on the sending speed of the whole server.

# /etc/postfix/main.cf (example of a setup that sends via relayhost)
relayhost = [email-smtp.example.com]:587

# Deliver to the relay once per second, with one recipient per delivery (= at most one recipient per second)
smtp_destination_rate_delay = 1s
smtp_destination_recipient_limit = 1

The delivery service's limits (sends per second and sends per day) vary with your contract and account status. Check the current limits in the service's admin console and adjust your values so they are slightly lower.

Slow down only specific destination domains

Sometimes you want to slow delivery only to certain domains, such as large free-mail providers that temporarily refuse mail when a lot arrives in a short time. In that case, create a dedicated transport and apply the limits only to it. Here we assume a setup that delivers directly to the recipient's MX without using relayhost.

In master.cf, add a transport called slow that uses the same program as smtp.

# /etc/postfix/master.cf (add using the same column values as the existing smtp unix line)
slow      unix  -       -       -       -       -       smtp

In main.cf, specify the values dedicated to slow and load a table that maps destination domains to it.

# /etc/postfix/main.cf

transport_maps = hash:/etc/postfix/transport

# Concurrent deliveries for the slow transport: up to 2
slow_destination_concurrency_limit = 2

# Delivery interval for the slow transport: 2 seconds (at most one delivery every 2 seconds to the same destination)
slow_destination_rate_delay = 2s
# /etc/postfix/transport (destination domain → transport)
example.net    slow:

After changing the table, rebuild the database with postmap before applying it.

sudo postmap /etc/postfix/transport
sudo postfix check
sudo systemctl reload postfix

If you leave the part after the colon (the next hop) empty, as in slow:, mail is delivered to the destination domain's MX as usual.

Check that the limits are working

After applying the settings, check their behavior in the logs and the queue.

# Clients that hit the inbound limits
grep -i "rate limit exceeded" /var/log/mail.log | tail -20

# Number of messages waiting in the queue
postqueue -p | tail -1

# Backlog per destination domain (on systems with qshape installed)
sudo qshape deferred | head -20

Delivery-side limits do not log that they "limited" anything. To check, look at whether the timestamps of the postfix/smtp lines are spaced at the interval you set.

Be careful if you make the delivery-side limits too strict. If new mail arrives faster than you send it, the queue keeps growing. How long mail can stay in the queue is determined by maximal_queue_lifetime (default 5 days), and mail past that limit is returned to the sender as an error. If you generate a large volume of mail, such as bulk mailings to members, also consider splitting the sending on the WordPress side or raising the limits on the delivery service side.

When you need per-sender limits

Postfix's standard features have no per-sender limit such as "this email address may send at most so many messages per hour." If several sites share one mail server and you want separate limits per site, combine Postfix with a policy service such as postfwd. First consider whether the standard inbound and delivery limits are enough, and consider adding it only if they are not.

Summary

  • Postfix rate limiting has two systems: the inbound side (smtpd plus anvil) and the delivery side (queue manager)
  • Mail sent from WordPress through mail() does not pass through smtpd, and localhost is exempt by default, so inbound limits do not apply. To limit sending volume, configure the delivery side
  • On the inbound side, use smtpd_client_connection_rate_limit, smtpd_client_message_rate_limit, and similar parameters to specify counts per anvil_rate_time_unit
  • On the delivery side, use default_destination_rate_delay, smtp_destination_concurrency_limit, and similar parameters to set the interval and concurrency, and note that *_destination_recipient_limit changes what one delivery covers
  • To slow down only specific domains, use a dedicated transport and transport_maps
  • A trailing comment in a main.cf line becomes part of the value. Put comments on their own lines, and keep the value and the intent consistent

Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)

お気軽にご相談ください

お見積りへ お問い合わせへ
▲