Contents
- Why high-volume tuning matters
- The real bottleneck is not the MTA
- Hardware sizing benchmarks
- Linux kernel parameter tuning
- TCP stack optimization
- ulimit configuration for file descriptors
- PowerMTA throughput directives
- Spool I/O patterns and SSD
- Log rotation strategies
- Horizontal scaling patterns
- Tuning effectiveness monitoring
Why high-volume tuning matters
PowerMTA's default configuration assumes moderate sending volume and reasonable hardware. At scales of 1M+ messages per day, defaults become limits and tuning becomes substantive. The tuning falls into several categories: Linux kernel parameters that affect TCP behavior under high connection load, ulimit settings that gate file descriptor availability, PowerMTA-specific directives that control parallelism and resource use, spool I/O patterns that determine sustained throughput, log rotation strategies that prevent disk fill from slowing delivery, and the hardware foundation that supports all of the above. Each category has straightforward tuning when understood individually; the interaction between them produces the operational outcome.
This guide exists because high-volume PowerMTA tuning is one of the topics where bad advice on the internet most consistently produces worse outcomes than no tuning. Some tutorials recommend kernel parameters from 2010-era documentation that no longer match current Linux behavior; others recommend ulimit values without explaining why; many ignore the substantive reality that MTA throughput is rarely the actual bottleneck in production email delivery. The structure: hardware sizing, kernel and TCP tuning, ulimit configuration, PowerMTA directives, spool I/O, log management, horizontal scaling, monitoring patterns to verify tuning effectiveness.
The real bottleneck is not the MTA
The most important practitioner perspective on PowerMTA high-volume tuning: raw MTA throughput is rarely the actual bottleneck in email delivery. ISP rate limits, connection caps, and reputation-based throttling determine actual sending speed far more than how fast your MTA can push packets.
The practical implication: KumoMTA documentation reports 4-6 million messages per hour per node on 16-core, 32 GB RAM hardware. PowerMTA equivalent benchmarks show similar capability. Yet typical operators running this hardware deliver 5-50M messages per day, not the 100M+ that the per-hour rate would suggest if ISPs accepted at that pace. The reason: gmail.com throttles to a few thousand per hour per IP regardless of how fast PowerMTA can push; outlook.com throttles further; yahoo.com enforces per-IP limits; iCloud is conservative. Combined across destinations, the effective throughput PowerMTA achieves is a small fraction of theoretical capability.
The substantive operational point: tune PowerMTA properly because under-tuned PowerMTA cannot keep up with ISP throughput even if it cannot exceed it. But do not expect throughput tuning to multiply send volume by an order of magnitude; the ceiling is set by ISPs, not by the MTA.
Hardware sizing benchmarks
Hardware sizing recommendations for 2026 PowerMTA deployments:
| Daily volume target | vCPU | RAM | Storage | Network |
|---|---|---|---|---|
| Under 500K/day | 4 | 8 GB | SSD 100 GB | 1 Gbps |
| 500K-2M/day | 8 | 16 GB | NVMe 250 GB | 1 Gbps |
| 2M-10M/day | 16 | 32 GB | NVMe 500 GB | 1 Gbps |
| 10M-50M/day | 32 | 64 GB | NVMe 1 TB | 10 Gbps |
| 50M+/day | Multiple nodes | 64 GB+ per node | NVMe per node | 10 Gbps |
The substantive points: memory matters more than CPU at high volume because PowerMTA holds message queues and accounting buffers in RAM; storage must be SSD or NVMe (HDD destroys MTA performance even at modest volumes); network bandwidth rarely bottlenecks below 5M per day on standard message sizes; horizontal scaling above 50M per day provides redundancy beyond just additional throughput.
Cloud provider considerations: avoid Amazon EC2 default IPs (poor outbound reputation in many ranges) unless paired with SES; Hetzner, OVH, Vultr, DigitalOcean all work well; bare metal provides better predictability than cloud VMs at the largest volumes.
Linux kernel parameter tuning
Linux kernel parameters affect TCP behavior under high connection load. PowerMTA at scale opens and closes many connections per second, and default kernel settings frequently produce subtle bottlenecks.
Create /etc/sysctl.d/99-pmta-tuning.conf with the substantive parameters:
# TCP listen backlog - default 128 too small for high connection rate
net.core.somaxconn = 4096
# SYN backlog for connection establishment under load
net.ipv4.tcp_max_syn_backlog = 8192
# Ephemeral port range - default range is narrow for high outbound rate
net.ipv4.ip_local_port_range = 1024 65535
# Allow TIME_WAIT socket reuse - essential at high outbound rate
net.ipv4.tcp_tw_reuse = 1
# Reduce TIME_WAIT linger duration
net.ipv4.tcp_fin_timeout = 15
# Increase TCP buffer sizes for high throughput connections
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
# System-wide file descriptor limit
fs.file-max = 1000000
# Maximum TCP connections in different states
net.netfilter.nf_conntrack_max = 1000000
# Reduce kernel SYN_RECV state attacks
net.ipv4.tcp_syncookies = 1
# Disable TCP slow start after idle (faster recovery)
net.ipv4.tcp_slow_start_after_idle = 0
Apply the settings:
sysctl -p /etc/sysctl.d/99-pmta-tuning.conf
Verify with sysctl -a | grep tcp_tw_reuse (should return 1). The settings persist across reboots because they are in /etc/sysctl.d/.
Older PowerMTA tuning tutorials recommend tcp_tw_recycle=1 alongside tcp_tw_reuse. This parameter was removed in Linux kernel 4.12+ because it produced incorrect behavior behind NAT. Do not attempt to set tcp_tw_recycle on modern kernels (Ubuntu 18.04+, Debian 10+, RHEL 8+); the setting will not exist and tutorials recommending it are out of date.
TCP stack optimization
Beyond the basic kernel parameters, TCP stack optimization for substantial outbound rates:
Congestion control algorithm. The Linux default is cubic which works well; BBR (Bottleneck Bandwidth and RTT) provides better behavior under packet loss. Enable BBR:
# /etc/sysctl.d/99-pmta-tuning.conf addition
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
Verify with sysctl net.ipv4.tcp_congestion_control (should return bbr) and tcp_available_congestion_control showing bbr in the list.
TCP Fast Open. Reduces connection establishment latency for repeated connections to the same destination. Useful when PowerMTA reconnects frequently:
net.ipv4.tcp_fastopen = 3
SACK and DSACK. Selective acknowledgment improves throughput under packet loss. Modern kernels enable by default but verify:
net.ipv4.tcp_sack = 1
net.ipv4.tcp_dsack = 1
Disable IPv6 if not used. If sending exclusively IPv4, disabling IPv6 reduces kernel overhead for connection handling. Edit /etc/default/grub adding ipv6.disable=1 to GRUB_CMDLINE_LINUX, then update-grub and reboot. Only do this if confirmed IPv6 is not used for sending; many destinations support IPv6 and disabling cuts off potential delivery paths.
ulimit configuration for file descriptors
PowerMTA holds one file descriptor per open SMTP connection plus spool files plus logs. The Linux default of 1024 per process is far too low for high-volume PowerMTA.
Configure ulimit for the PowerMTA user. Edit /etc/security/limits.d/99-pmta.conf:
pmta soft nofile 100000
pmta hard nofile 100000
pmta soft nproc 32768
pmta hard nproc 32768
For systemd-managed PowerMTA service, also edit the service unit. Create /etc/systemd/system/pmta.service.d/override.conf:
[Service]
LimitNOFILE=100000
LimitNPROC=32768
Apply changes:
systemctl daemon-reload
systemctl restart pmta
Verify with:
# Check PowerMTA process limits
cat /proc/$(pgrep pmtad)/limits | grep -E "files|process"
Should show Max open files and Max processes at 100000 and 32768 respectively. If still showing 1024, the ulimit changes did not take effect; check that the limits.d file is read (the user must log out and back in for /etc/security/limits.d/ changes, or systemd service uses LimitNOFILE).
PowerMTA throughput directives
PowerMTA-specific directives that affect throughput:
| Directive | Effect | High-volume value |
|---|---|---|
max-smtp-out (per domain) | Max concurrent SMTP connections to destination | 5-20 (destination-specific) |
max-conn-rate (per domain) | New connections per minute to destination | 10-30/m (destination-specific) |
max-msg-rate (per domain) | Messages per hour to destination | 1000-10000/h (destination-specific) |
max-conn-msgs (per domain) | Messages per connection before disconnect | 50-500 |
conn-life-time (per domain) | Maximum connection lifetime | 10m-30m |
max-recipients-per-message (global) | Max RCPT TO per envelope | 1 (one per envelope standard for MailWizz) |
max-smtp-conn (global) | Max total concurrent SMTP connections | 5000-50000 |
dns-resolution-num-threads (global) | DNS resolver thread count | 16-32 |
queue-num-threads (global) | Queue processing thread count | 16-32 |
The destination-specific directives (max-smtp-out, max-conn-rate, max-msg-rate) should be set per domain to match ISP capacity. The global directives (max-smtp-conn, dns-resolution-num-threads, queue-num-threads) gate PowerMTA's overall parallelism and matter most at substantial scale.
Example global configuration for high-volume node:
# /etc/pmta/config
max-smtp-conn 20000
dns-resolution-num-threads 32
queue-num-threads 32
max-recipients-per-message 1
The substantive operational reality: increasing max-smtp-out per domain above what ISPs accept produces "exceeded the connection limit" errors that reduce throughput. The Hotmail throttling analysis indicates 1-5 concurrent connections is the appropriate range for Hotmail at moderate volumes; pushing to 20 produces immediate errors. Operators learn ISP-appropriate values through accounting log analysis rather than guessing.
Spool I/O patterns and SSD
The PowerMTA spool directory (/var/spool/pmta by default) holds in-flight message files. Spool I/O patterns at high volume:
- Each accepted message produces one write to spool (creation)
- Each delivery attempt produces metadata updates to the message file
- Each successful delivery removes the message file
- At 1M messages/day baseline: roughly 12 writes/second baseline, with bursts to 100+ writes/second during peak submission
Storage requirements:
| Storage type | Suitability | Why |
|---|---|---|
| HDD (spinning) | Never | Random write IOPS catastrophic for MTA |
| SATA SSD | OK to 5M/day | Reasonable IOPS, acceptable cost |
| NVMe SSD | Standard for 5M+/day | High IOPS, low latency, cost-effective |
| Tmpfs (RAM) | Risky | Faster but loses spool on crash |
| Network storage (NFS, iSCSI) | Avoid | Network latency unpredictable, complicates recovery |
The /var/spool/pmta directory should be on dedicated storage when possible. Mount with noatime and nodiratime to reduce unnecessary I/O:
# /etc/fstab
/dev/nvme1n1 /var/spool/pmta xfs defaults,noatime,nodiratime 0 2
XFS or ext4 are both fine for the filesystem. XFS handles many small files slightly better which matches spool patterns; ext4 is more familiar for most operators. Either works adequately.
Log rotation strategies
PowerMTA log files (accounting CSV, smtp logs, pmta.log) accumulate quickly at high volume. Bad rotation strategies bottleneck delivery; good rotation enables sustained throughput.
The accounting log rotation is configured in the PowerMTA acct-file directive:
<acct-file /var/log/pmta/accounting.csv>
records d,b,t,rb,f
max-size 500M
max-age 1d
move-to /var/log/pmta/archive
move-interval 1h
</acct-file>
The pattern: rotate at 500MB or 1 day whichever first; move rotated files to archive directory (not in-place rotation which can confuse downstream consumers); check rotation conditions every hour. The archive directory is consumed by downstream ingestion (ClickHouse, Postgres, or similar) which deletes files after successful ingestion.
For general PowerMTA logs (pmta.log), use logrotate. Create /etc/logrotate.d/pmta:
/var/log/pmta/pmta.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
create 644 pmta pmta
postrotate
/bin/kill -HUP `cat /var/run/pmta.pid 2>/dev/null` 2> /dev/null || true
endscript
}
The substantive points: compression with delaycompress prevents PowerMTA from writing to a file that just got compressed (corruption risk); postrotate HUP signal tells PowerMTA to reopen log files; daily rotation with 14-day retention provides reasonable history without disk fill.
For accounting archive cleanup: a separate cron job deletes ingested files after ingestion succeeds (or moves them to S3-class long-term storage):
0 2 * * * find /var/log/pmta/ingested -mtime +30 -delete
Horizontal scaling patterns
Above 50M messages/day or for redundancy requirements, horizontal scaling distributes load across multiple PowerMTA nodes.
The standard pattern: 2-3 PowerMTA nodes in active-active configuration, HAProxy or similar load balancer distributing incoming SMTP submissions from applications across nodes, each node holding its own VMTA pool with dedicated IPs, accounting flowing to centralized log aggregation.
HAProxy configuration for SMTP load balancing:
frontend smtp_in
bind *:25
mode tcp
default_backend pmta_nodes
backend pmta_nodes
mode tcp
balance leastconn
server pmta1 10.0.1.10:25 check
server pmta2 10.0.1.11:25 check
server pmta3 10.0.1.12:25 check
leastconn balance produces better distribution than roundrobin because SMTP connection durations vary substantially. Health checks (check) prevent routing to dead nodes.
Per-node configuration distinguishes which IPs belong to which node. Each node binds its smtp-source-host directives to local IPs, and the load balancer ensures incoming submissions distribute across nodes. The VMTA pool composition per node reflects the IPs that node actually has bound; cross-node IP sharing is not supported by standard PowerMTA (each IP belongs to one node).
Accounting log aggregation centralizes data from all nodes. The pattern: each node writes accounting locally, a cron job ships log files to a central ClickHouse cluster (or S3, BigQuery, similar), analytics queries against the centralized store rather than against individual nodes. This avoids the operational complexity of querying multiple nodes during incident investigation.
Tuning effectiveness monitoring
Verifying that high-volume tuning actually produces expected outcomes requires monitoring across several dimensions.
System-level metrics. CPU usage (should not saturate; saturation means more cores needed), memory usage (should not approach max; high usage means more RAM needed), disk I/O wait (high wait time means storage is the bottleneck), network throughput (should not saturate; saturation means more bandwidth needed). Standard Linux monitoring tools (Prometheus node_exporter, Datadog, Netdata) capture these.
PowerMTA metrics. Total messages sent per hour, total messages queued, accounting writes per second, queue depth per destination, retry rate per destination. These come from /api/queues JSON feed plus accounting log analysis.
Connection metrics. Outbound connections established per second, TCP connection states (ESTABLISHED, TIME_WAIT counts), ephemeral port usage. Monitor with ss -s output or Prometheus exporters for connection stats.
File descriptor usage. Verify ulimit settings are working. Check lsof -p $(pgrep pmtad) | wc -l showing actual FD usage versus the configured limit. If approaching the limit, increase ulimit further.
The tuning success signals:
- CPU usage stays under 70% sustained even at peak volume
- Memory usage stable, not approaching swap
- Disk I/O wait under 10% even during burst writes
- TCP TIME_WAIT count stable (no exhaustion)
- Queue depth correlates with ISP throttling rather than PowerMTA capacity
- Delivery rate per IP matches ISP-imposed limits not PowerMTA limits
The tuning failure signals:
- CPU pegged at 100% during peak
- Memory approaching limits, swap activity
- Disk I/O wait above 30%
- "Too many open files" errors in PowerMTA log
- Connection refused errors when establishing outbound
- Queue depth growing despite low ISP throttling
An operator we audited had implemented every kernel tuning recommendation they could find online and ran a 16-core, 64 GB RAM bare metal PowerMTA node for what should have been substantial volume. Actual throughput was approximately 800K messages per day. Investigation revealed the tuning was correctly applied at the OS level but the PowerMTA configuration had max-smtp-conn at the default 1024, which capped concurrent connections far below what the hardware and ISPs could support. Bumping max-smtp-conn to 20000 plus adjusting per-destination max-smtp-out values for Gmail (15), Microsoft (5), Yahoo (5), iCloud (5) increased throughput to 4.2M per day within 48 hours, limited then by actual ISP throttling rather than PowerMTA capacity. The lesson: kernel tuning matters but is necessary not sufficient. PowerMTA directive tuning is equally important and frequently more impactful for throughput than kernel parameters. Both layers need attention; tuning only one produces the frustrating outcome of tuning without performance improvement.
High-volume PowerMTA tuning combines hardware selection, Linux kernel parameter adjustment, TCP stack optimization, ulimit configuration, PowerMTA directive tuning, spool I/O patterns, and log rotation strategy. Each layer requires attention; missing any layer produces bottlenecks regardless of how well other layers are tuned. The combination produces PowerMTA that can sustain its share of substantial daily volume without falling behind submission rate or producing technical failures during peak periods. The honest practitioner perspective remains important throughout: tune properly so PowerMTA is not the limit, but recognize that ISPs ultimately set the ceiling on what gets delivered. Tuning is necessary to reach the ISP-set ceiling reliably; tuning will not push past that ceiling.