Rate limiting in Postfix prevents abuse, protects your IP reputation, and ensures fair resource usage. Whether you need to limit outbound sending rates to avoid being flagged as a spammer, or restrict inbound connections to prevent denial-of-service, Postfix provides multiple throttling mechanisms. This guide covers all rate limiting options with practical configurations.
Why Rate Limit?
- IP reputation — sending too fast triggers spam filters at receiving servers
- Abuse prevention — prevent compromised accounts from sending bulk spam
- Resource protection — avoid overwhelming your server or remote servers
- Warm-up — gradually increase sending volume for new IP addresses
Outbound Rate Limiting
Per-Destination Rate Delay
# /etc/postfix/main.cf
# Delay between messages to the same destination
default_destination_rate_delay = 1s
# Per-transport overrides
smtp_destination_rate_delay = 1s
# This means Postfix waits 1 second between delivering messages
# to the same remote domain — effective for not overwhelming recipients
Per-Destination Concurrency
# Maximum simultaneous connections to any single destination
default_destination_concurrency_limit = 20
smtp_destination_concurrency_limit = 20
# For sensitive destinations (e.g., known rate-limiters)
# Use transport maps with per-destination settings
# /etc/postfix/main.cf
transport_maps = hash:/etc/postfix/transport
# /etc/postfix/transport
gmail.com slow-smtp:
outlook.com slow-smtp:
yahoo.com slow-smtp:
# /etc/postfix/master.cf
slow-smtp unix - - n - 5 smtp
-o smtp_destination_concurrency_limit=5
-o smtp_destination_rate_delay=2s
-o smtp_extra_recipient_limit=5
Global Sending Rate
# Maximum messages per second (across all destinations)
# Controlled via active queue size
qmgr_message_active_limit = 500
# Limit recipients per delivery
default_extra_recipient_limit = 50
smtp_extra_recipient_limit = 50
# Maximum recipients per message
smtpd_recipient_limit = 100
Inbound Rate Limiting
Connection Rate Limiting
# /etc/postfix/main.cf
# Rate limit client connections
smtpd_client_connection_rate_limit = 30 # Max connections per minute per client
smtpd_client_connection_count_limit = 10 # Max simultaneous connections per client
smtpd_client_message_rate_limit = 60 # Max messages per minute per client
smtpd_client_recipient_rate_limit = 100 # Max recipients per minute per client
smtpd_client_new_tls_session_rate_limit = 30
# Rate limit cache
anvil_rate_time_unit = 60s # Time window for rate counting
anvil_status_update_time = 600s # How often to log anvil stats
# Exempt trusted clients
smtpd_client_event_limit_exceptions = $mynetworks
Per-User Sending Limits (SASL)
# Limit authenticated users
# Requires Postfix 3.1+ with smtpd_relay_restrictions
# Using policy service for per-user limits
smtpd_sender_restrictions =
check_policy_service unix:private/policy-spf,
permit_sasl_authenticated,
reject
# For more granular per-user limits, use a policy daemon
# Example: postfix-policyd-spf-python or custom policy script
Policy Daemon for Advanced Rate Limiting
#!/usr/bin/env python3
# /usr/local/bin/rate-limit-policy.py
# Simple per-sender rate limiting policy daemon
import sys
import time
from collections import defaultdict
RATE_LIMIT = 100 # messages per hour
WINDOW = 3600 # 1 hour in seconds
counters = defaultdict(list)
def check_rate(sender):
now = time.time()
# Clean old entries
counters[sender] = [t for t in counters[sender] if now - t < WINDOW]
if len(counters[sender]) >= RATE_LIMIT:
return "DEFER_IF_PERMIT Rate limit exceeded. Try again later."
counters[sender].append(now)
return "DUNNO"
while True:
attrs = {}
for line in sys.stdin:
line = line.strip()
if not line:
break
key, _, value = line.partition("=")
attrs[key] = value
sender = attrs.get("sasl_username", attrs.get("sender", ""))
action = check_rate(sender)
sys.stdout.write(f"action={action}\n\n")
sys.stdout.flush()
# /etc/postfix/master.cf
policy-ratelimit unix - n n - 0 spawn
user=nobody argv=/usr/bin/python3 /usr/local/bin/rate-limit-policy.py
# /etc/postfix/main.cf
smtpd_data_restrictions = check_policy_service unix:private/policy-ratelimit
IP Warm-Up Schedule
# For new IP addresses, gradually increase sending volume
# Week 1: 50 messages/hour
# Week 2: 200 messages/hour
# Week 3: 500 messages/hour
# Week 4: 1000 messages/hour
# Week 5+: Full speed
# Adjust these settings weekly during warm-up:
# /etc/postfix/main.cf
default_destination_rate_delay = 60s # Week 1: very slow
# Gradually reduce to 0 over weeks
Monitoring Rate Limits
# Check anvil statistics
postconf -d anvil_status_update_time
# Anvil logs to mail.log every 600s by default
# View current rate data
postfix check
# Check for rate-limited connections in logs
grep "rate limit" /var/log/mail.log
grep "anvil" /var/log/mail.log
# Monitor with postfix_exporter (Prometheus)
# Tracks connections, message rates, and rejections per client
Handling Legitimate Bulk Senders
# Whitelist known bulk senders from rate limits
smtpd_client_event_limit_exceptions =
$mynetworks,
hash:/etc/postfix/rate-limit-exceptions
# /etc/postfix/rate-limit-exceptions
# IP addresses of trusted bulk senders
10.0.0.50 OK
10.0.0.51 OK
Best Practices
- Start with conservative limits and increase based on observed delivery success
- Use per-destination rate delays for outbound mail to major providers (Gmail, Outlook, Yahoo)
- Exempt
$mynetworksfrom inbound rate limits to avoid blocking trusted sources - Monitor anvil statistics and mail logs for signs of rate limiting hitting legitimate traffic
- Implement per-user rate limits for shared hosting environments to prevent one user from consuming all resources
- Warm up new IP addresses gradually over 4-6 weeks
- Log rate limit events for debugging and abuse investigation