Error when using specified source IP addresses

Your question

Why does setting specific Source IP Addresses result in inability to bind?

Node 1 is 10.0.1.100
Node 2 is 10.0.2.100
Node 3 is 10.0.3.100

Settings → MTA → Outbound → Connection Strategies → default → Source IP Addresses

Add Source IP = 10.0.1.100 + EHLO Hostname = mx1.example.com
Add Source IP = 10.0.2.100 + EHLO Hostname = mx2.example.com
Add Source IP = 10.0.3.100 + EHLO Hostname = mx3.example.com

Mail delivery log, which is clearly indicating the IP it is attempting to use is not available for binding

queueId = 310172228782129153, queueName = "remote", from = "[email protected]", to = ["[email protected]"], size = 418, total = 1, domain = "srv1.mail-tester.com", hostname = "reception.mail-tester.com", localIp = 10.0.1.100, remoteIp = 134.98.158.159, remotePort = 25, causedBy = SMTP error occurred (smtp.error) { details = "I/O Error", reason = "Cannot assign requested address (os error 99)" }, elapsed = 0ms

If I remove these settings, mail flows correctly, but then the PTR + EHLO don’t match.

Is Stalwart trying to use an IP from the wrong node, I don’t see a way to explicitly define a node + IP binding.

I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.

on

The root cause is clear. Here’s the explanation:

The sourceIps list in a connection strategy is shared across all nodes in the cluster, and Stalwart randomly picks one from the list for each delivery attempt. This means Node 2 or Node 3 may randomly try to bind to 10.0.1.100 (which only exists on Node 1), causing OS error 99 (“Cannot assign requested address”).

There is no built-in concept of “this source IP is only valid on this node” inside a single connection strategy. The workaround is to use separate connection strategies per node and select the correct one using the connection expression on your MtaOutboundStrategy, keyed on a variable that identifies the current node. The local_ip variable is available in the MtaQueueHostVariable expression context for the connection field, but it reflects the IP of the inbound received connection – not a reliable node identifier.

The practical approach is:

  1. Create three connection strategies, e.g. node1-conn, node2-conn, node3-conn, each with only their respective node’s source IP and EHLO hostname.
  2. Use the connection expression on your MtaOutboundStrategy to select the right strategy based on received_from_ip or another variable that reliably reflects which node is processing the message – OR, configure your cluster so each node runs its own outbound strategy object pointing to the correct connection strategy.

The MtaQueueHostVariable context (used by the connection expression) does include local_ip, which would be the node’s own address during delivery – this is likely the cleanest discriminator. For example, your connection expression could be:

if local_ip == '10.0.1.100' → 'node1-conn'
if local_ip == '10.0.2.100' → 'node2-conn'
if local_ip == '10.0.3.100' → 'node3-conn'
else → 'default'

This ensures each node only ever tries to bind to its own IP.

Relevant docs:

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.

I understand the concept now. I’ve created separate appropriate profiles, but when adjusting the Strategy:Connection to pick one, I’m not seeing a way to identify a node or node source from the variable list at MtaQueueHostVariable | Stalwart

The bot has conflicted itself by saying use local_ip, after saying you can’t use local_ip as it reflects the original inbound IP of the message. (I tried it anyways, didn’t work, fails back to default).

Suggestions on how to identify the node correctly would be appreciated.

Resolved. For future posterity Functions | Stalwart mentions variables for node_id and node_hostname.

Create enough MTA:Outbound:Connection Strategies to cover your node requirements, and then alter your MTA:Strategy:Connection to reference your nodes. Example:

if system('node_hostname') == 'mx1.example.com'
then 'node1'

… repeat as required.

This sounds really good, but I’m having trouble with it.

I created two connection strategies, but I’m getting this to the strategy called ‘home’:

2026-05-31T12:35:29Z WARN Strategy not found (smtp.id-not-found) queueId = 310275587551790594, queueName = “remote”, from = “[email protected]”, to = [“[email protected]”], size = 965, total = 1, id = “myhomedomaineu”, details = “Connection strategy not found”

It worked later - I had to restart Stalwart for these strategies to be picked.