Knowledge base

PMTA knowledge base›Block Global›Directive§ 3.3.1.50

max-memory-usage-percent

Global

Stop accepting mail above a memory threshold
in the configuration this is a linemax-memory-usage-percent 100inside the block Global
An emergency valve: refuse politely instead of crashing

The engine tracks how much physical memory its own process has taken. As soon as the share goes over the set one, receiving new mail stops — right at the MAIL FROM stage, with the response 452 4.3.2 Server resources temporarily exhausted. When memory is freed, receiving resumes by itself: neither a restart nor a command is needed for that.

The refusal is temporary, and that is the whole point. 452 is not a rejection of the message but a request to retry: the submitting system must try again, not throw the message away. So the valve loses nothing — it only holds back the input.

The input is shut off, not the output. Everything already in the queue keeps going out: the directive does not touch delivery. That is by design — the queue drains, memory is freed, receiving opens back up. This is also how to recognize this 452: it reaches your sending platform at submission time, not from the recipient’s mailbox, and it has to be looked for in the submission log, not among delivery bounces.

What happens without the valve. A node with a growing queue runs into the operating system’s memory, and from there the system decides: on Linux the kernel kills the most memory-hungry process, and that is the engine itself, pmtad. The watchdog pmtawatch will bring it back up, but whatever was received and not written is lost, and after startup the node also rereads the whole spool, loading the queue into memory again — minutes during which it does not send.

The main trap. The default changed between editions. In the PowerMTA 5.0 manual the default was 90 — the valve worked for everyone, even those who did not know about it. In 6.0 the default is 100, and a hundred, as the manual says directly, turns off the memory check. That is, upgrading from version five to version six silently removes this protection from the node if the line is not in the configuration. The config itself has not changed and looks exactly the same.
What the Hub does. It does not write it — a node set up by the Hub lives with the 100 default, that is, without the valve. The Hub has the data to calculate it: when a server is connected, it records the amount of memory and stores it with the node, and the preflight check rejects a node with less than a gigabyte and warns at two. All that is missing is a line in the configuration template — and it has to be added exactly there, not on the node: the Hub rewrites the canonical config in full, and a manual line will not survive the next apply.
Recommended values

Count not the percentage but what is left. The threshold is the reserve you leave for everything else: the kernel, logs, the disk cache, and on a shared server the neighbors too. The same percentage on different nodes leaves a different reserve, so the less memory there is, the lower the threshold should be.

As a guide, leave the system between half a gigabyte and one and a half: with 2 GB that is 70, with 4 GB — 80, with 8 GB — 85, with 16 GB or more — 90, the very default of version five.

Do not go below 60. A threshold that triggers under normal load turns an emergency refusal into a permanent one: the platform gets 452 all day and sooner or later considers the node dead. The valve should trigger rarely — it is the last line, not a way to control the rate. The rate is controlled by max-msg-rate and total-max-smtp-out, the queue volume by spool-max-recipients, and those should be dealt with first.

A hundred makes sense only where memory is limited from outside — by a container or cgroup limit — and you consciously hand the decision over there. In other cases it is not “without limits” but “without a safety net.”

The percentages and the reserve in gigabytes are our recommendation. From the manual here come the 1–100 range, the 452 4.3.2 response and the defaults: 100 in edition 6.0, 90 in edition 5.0.
Example configuration
# node-level directives are written at the root of the file, without an enclosing block
total-max-smtp-in 1000
total-max-smtp-out 5000

# emergency valve: on a 4 GB node the system keeps about 800 MB
max-memory-usage-percent 80
The neighbors are not chosen at random: these are also limits for the whole instance. The memory valve comes last in this row — it triggers when the others have already failed to hold.
yellow — — directive names, blue — blocks. They are highlighted the same way in the text of the card.
▲

Common mistakes and problems

what people get wrong and how it ends3 mistakes▶

Upgrading from version five without noticing the valve is gone

§ 3.3.1.50

What it costsIn 5.0 the default was 90, in 6.0 it is 100, that is, the check is off. The configuration did not change, the behavior did: a node that for years refused on the threshold by itself now accepts mail until it hits the operating system’s memory limit.
How to spot itAfter the upgrade the node started restarting under load, even though before the upgrade it only slowed down receiving under the same load.
How to do it rightSet the threshold explicitly — do not rely on the default either way. If the line is in the configuration, the valve is there and you know its value.

Setting the threshold low “to be on the safe side”

our experience

What it costsA value like 50 triggers under normal working load, and the temporary refusal stops being an emergency: the platform gets 452 constantly. Formally everything is right — messages are not lost — but submission goes in jerks, and the queue on the platform side grows.
How to spot it452s in the submission log come in a steady stream rather than in bursts; meanwhile the node’s memory is far from exhausted.
How to do it rightRaise the threshold so that it leaves the system between half a gigabyte and one and a half, and deal with the rate through queue limits, not through the valve.

Looking for the cause of a 452 on the recipient's side

our experience

What it costsThe refusal looks like an ordinary temporary SMTP refusal, and people start explaining it by reputation, provider limits or a blocked address. The real cause is your own node at the receiving stage, and time goes in the wrong direction.
How to spot itThe 452 comes in response to MAIL FROM, things never get to RCPT, and the response text talks about server resources, not about the recipient or the sender.
How to do it rightRead the whole response. 452 4.3.2 Server resources temporarily exhausted is your node’s response to the submitting system; look at the node’s memory, not at deliverability.
Compatibility
PowerMTA 6.0
described in the manual of this version · platform: any
The vendor does not state the first version for every directive — there is no note here. The name also appears in the 5.0 edition of the manual.
Reference data
Syntax
max-memory-usage-percent N
Values
an integer from 1 to 100; 100 turns the check off
Default
100 § 3.3.1.50
Applying
reload, no restart needed
Queue
will not touch what is already queued
Where to write it
Global
How many times
once in the block
Everything here comes from the manual, with the section.
Inheritance
There is no inheritance: the directive is written once at the root of the file and applies to the whole instance. It cannot be set in a sending address or recipient domain block — the node has one memory, and one threshold for it. It is picked up by pmta reload; a service restart is not needed.
How to check on the node
There is no dedicated command: pmta show settings shows the queue and sending address settings, and node-level directives never appear in its output. So we look at the config itself — together with included files — and at the process’s memory usage:
grep -rn max-memory-usage-percent /etc/pmta/
ps -o pid,rss,%mem,comm -C pmtad
An empty result from the first command means there is no valve: the node runs with the default of 100. In the second, the %MEM column is exactly the share the threshold is compared with; next to pmtad you will usually see pmtawatch, the watchdog, which uses almost no memory. The Hub does not collect the node’s current memory usage yet: monitoring shows the machine’s memory size recorded at connection, not how much is in use.
In the Hub
Cost of a mistake
reversible: harms only while it is set
Scenarios
Capacity planning · Untangle a stuck queue