Docs / Email Servers / Postfix Rate Limiting and Throttling

Postfix Rate Limiting and Throttling

By Admin · Mar 15, 2026 · Updated Apr 23, 2026 · 481 views · 4 min read

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 $mynetworks from 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

Was this article helpful?