The bounce message “mail for example.com loops back to myself” is a routing error, not a delivery timeout. It appears in logs as status=bounced (mail for example.com loops back to myself) with DSN 5.4.6. Postfix resolved the destination domain’s MX record, found the best-preference MX points at this host, but found no matching entry in mydestination, virtual_mailbox_domains, virtual_alias_domains, or relay_domains. Instead of looping, Postfix bounces the message.

This typically surfaces after a DNS or MX change, a relay-host migration, adding a virtual domain without updating delivery maps, or converting a null client to a full internet site. The fix is to align the domain ownership tables with the MX record. Raising retry limits will not help because the routing decision itself is wrong.

The error often hits only some domains while others deliver normally. That narrow scope points to a per-domain configuration gap, not a system-wide failure. Start there.

What this means

Postfix’s smtp(8) delivery agent performs MX resolution for each outbound domain. When the best-preference record resolves to an IP that belongs to the local machine, Postfix checks whether it “owns” that domain. Ownership is determined by four parameters:

  • mydestination: domains delivered locally to UNIX accounts via the local delivery agent.
  • virtual_mailbox_domains: domains delivered to virtual mailboxes via the virtual delivery agent.
  • virtual_alias_domains: domains handled purely by alias rewriting.
  • relay_domains: domains this host accepts and forwards to another destination.

If the MX points at this host but the domain appears in none of these lists, Postfix detects a loop and bounces the message rather than connecting to itself repeatedly.

The error can also fire when Postfix does not recognize its own IP addresses. If the server sits behind NAT or a proxy and the MX record points to a public IP that Postfix does not know about, the loop detection logic fails. The proxy_interfaces parameter exists for this scenario.

Common causes

CauseWhat it looks likeFirst thing to check
Domain missing from mydestinationMail for a local domain bounces; MX points herepostconf -h mydestination
Domain missing from virtual_mailbox_domainsVirtual mailbox domain bounces; SQL query returns nothingpostmap -q domain mysql:/etc/postfix/mysql-virtual_domains.cf
Domain missing from relay_domainsBackup MX or gateway loops for a relayed domainpostconf -h relay_domains
Server behind NAT without proxy_interfacesMX resolves to public IP Postfix does not recognize as its ownpostconf -h proxy_interfaces
relay_domains inherits empty mydestinationGateway or null-client: mydestination empty, relay_domains also empty because it defaults to $mydestinationpostconf -h mydestination relay_domains
Inactive or typo’d domain in SQL tableDomain exists in database but query returns no rows (inactive flag, typo)Run the actual SQL query or postmap -q lookup
transport_maps missing routeDomain in relay_domains but no transport entry routes it onwardpostconf -h transport_maps and test lookup

Quick checks

Run these read-only commands to narrow the problem. None modify configuration.

# Show all domain ownership parameters at once
postconf -h mydestination virtual_mailbox_domains virtual_alias_domains relay_domains

# Check NAT/proxy awareness
postconf -h proxy_interfaces inet_interfaces

# Check transport maps and relay transport
postconf -h transport_maps relay_transport

# Check what hostname Postfix identifies as
postconf -h myhostname

# Resolve the MX for the affected domain
dig +short MX example.com

# Find all affected domains in the bounce logs
grep 'loops back to myself' /var/log/mail.log | tail -20

# Test a virtual domain lookup (adjust map type and path to your setup)
postmap -q example.com mysql:/etc/postfix/mysql-virtual_domains.cf

# Verify local and virtual recipient maps are configured
postconf -h local_recipient_maps virtual_mailbox_maps

# Confirm Postfix is listening and the greeting matches myhostname
echo QUIT | nc -w 5 localhost 25 | head -1

If your system logs to /var/log/maillog (RHEL and derivatives) instead of /var/log/mail.log (Debian and Ubuntu), adjust the grep commands accordingly.

How to diagnose it

The diagnostic flow is a sequence of elimination. Work through it methodically.

flowchart TD
    A["Bounce: loops back to myself"] --> B{"Domain in mydestination?"}
    B -->|No| C{"Domain in virtual_mailbox_domains?"}
    B -->|Yes| D["Verify local delivery agent handles it"]
    C -->|No| E{"Domain in relay_domains?"}
    C -->|Yes| F["Verify virtual delivery agent handles it"]
    E -->|No| G{"Does MX point to this host?"}
    E -->|Yes| H["Verify transport_maps routes onward"]
    G -->|Yes| I["Add domain to correct table"]
    G -->|No| J{"Is server behind NAT/proxy?"}
    J -->|Yes| K["Set proxy_interfaces to public IP"]
    J -->|No| L["Check DNS and MX records"]
  1. Identify affected domains. Extract them from the bounce logs. A single domain points to a missing table entry. Multiple domains suggest a broader routing change or a NAT/proxy issue.

  2. Determine which table should own each domain. Is the domain for local UNIX accounts, virtual mailboxes, alias-only rewriting, or relay to another host? A null client or outbound-only relay typically has an empty mydestination. An internet site with virtual hosting uses virtual_mailbox_domains. A backup MX uses relay_domains.

  3. Verify the domain is present in that table. Use postmap -q to test the actual lookup, not just grep on the source file. For SQL-backed maps, the database might have the domain but with an inactive flag or a typo. The lookup query is what Postfix evaluates.

  4. Resolve the MX and confirm it points to this host. Run dig +short MX example.com. Compare the resolved hostname or IP against myhostname, inet_interfaces, and proxy_interfaces. If the MX resolves to a public IP and this server is behind NAT, Postfix may not recognize the address as its own.

  5. Check proxy_interfaces if behind NAT. This parameter must list the public IP address(es) that the MX record resolves to. Without it, Postfix sees only its private IP and cannot match it against the MX-resolved address.

  6. Check transport_maps for relayed domains. If the domain is in relay_domains but transport_maps has no entry for it, Postfix uses the default relay transport. If that transport points back at this host or is misconfigured, the loop persists.

  7. Check the relay_domains default trap. The relay_domains parameter defaults to $mydestination. If mydestination is empty (common on gateway or null-client setups), relay_domains is also empty. Any domain whose MX points to this host will loop. Set relay_domains explicitly.

Metrics and signals to monitor

SignalWhy it mattersWarning sign
Bounce rate (status=bounced)Routing errors produce bounces, not deferralsSudden spike with “loops back to myself” as the reason
Delivery rate by domainIsolates which domains are affectedDelivery to one domain drops to zero while others are normal
Deferred queue growthLooped mail may defer before bouncingQueue grows for specific domains before bounces appear
DNS resolver responsivenessMX resolution must be correct for routingLookups returning unexpected or stale records
Postfix configuration stateConfig changes cause the mismatchChanges to mydestination, relay_domains, or virtual maps correlate with onset

Fixes

Fix the routing decision. Each cause has a specific remedy.

The commands below modify main.cf or map files. Review the current value before applying, and run postfix reload after changes to pick up the new configuration.

Domain belongs in mydestination

If the domain is delivered to local UNIX accounts, add it to mydestination:

# Review current value
postconf -h mydestination

# Add the domain (this replaces the entire value; include existing entries)
postconf -e 'mydestination = $myhostname, localhost.$mydomain, example.com'

# Reload to apply
postfix reload

Do not list the same domain in both mydestination and virtual_mailbox_domains. Postfix logs a warning about overlapping domains and delivery becomes unpredictable.

Domain belongs in virtual_mailbox_domains

If the domain uses virtual mailboxes, ensure it appears in the virtual domains map. For file-backed maps:

# Add to the source file, then rebuild the indexed map
echo "example.com OK" >> /etc/postfix/virtual_domains
postmap /etc/postfix/virtual_domains

# Verify the lookup returns a result
postmap -q example.com hash:/etc/postfix/virtual_domains

For SQL-backed maps, verify the domain is active in the database. Check for an active column or similar flag. A common failure is the domain row existing but marked inactive, so the lookup query returns no rows. Run the exact query from your map file against the database to confirm.

Domain belongs in relay_domains

For a relay or gateway host that forwards mail to another internal server, add the domain to relay_domains and configure transport_maps to route it onward:

# Add the domain (this replaces the entire value; include existing entries)
postconf -e 'relay_domains = example.com'

# Add a transport entry to route onward
echo "example.com smtp:[internal-mail.example.lan]" >> /etc/postfix/transport
postmap /etc/postfix/transport

postfix reload

The square brackets around [internal-mail.example.lan] suppress MX lookup and force direct A-record resolution. Use this for internal relays that should not go through public DNS.

If relay_domains was inheriting an empty mydestination, set it explicitly rather than relying on the default.

Backup MX without relay_domains

A backup MX that queues mail for a primary must list the backed-up domain in relay_domains. Do not list it in mydestination (that would attempt local delivery). The domain belongs in relay_domains only, and Postfix will queue and retry delivery to the primary MX.

NAT or proxy without proxy_interfaces

If the server is behind NAT or a proxy and the MX record points to a public IP, set proxy_interfaces to that public address:

postconf -e 'proxy_interfaces = 203.0.113.10'
postfix reload

This tells Postfix the public IP belongs to this machine, so the loop detection logic recognizes the MX target as itself.

transport_maps missing or wrong

If the domain is in relay_domains but no transport entry exists, Postfix uses the default relay transport. Verify the transport map:

# Check configured transport maps
postconf -h transport_maps

# Test a transport lookup
postmap -q example.com hash:/etc/postfix/transport

Add the entry if missing, or correct the destination if it points back at this host.

Prevention

Audit domain tables after every DNS or MX change. When you add, remove, or change an MX record, verify that the affected domain exists in the correct Postfix parameter. A domain whose MX moves to this host without a corresponding mydestination or relay_domains entry will loop.

Test with postmap -q before announcing the domain. For SQL-backed virtual domains, run the actual lookup query. Do not assume the database row is correct. Check active flags and spelling.

Version-control main.cf and master.cf. Configuration drift causes this error more often than DNS changes. Knowing what changed and when collapses diagnosis from minutes to seconds. See postfix check warnings: configuration drift and permission problems.

Run postfix check after changes. It catches overlapping domain declarations, permission problems, and syntax errors before they cause bounces.

Monitor bounce rate by domain. A routing misconfiguration for one domain produces a spike in bounces for that domain while overall mail flow looks healthy. Alert on per-domain bounce rates, not just aggregate counts. See Postfix monitoring checklist: the signals every production mail server needs.

How Netdata helps

  • Per-second bounce detection. Netdata surfaces the bounce rate spike from a routing error within seconds, often before users report missing mail. Correlating the bounce timestamp with configuration changes narrows the cause.
  • Delivery rate by domain. Delivery to one domain dropping to zero while others remain healthy confirms a per-domain routing issue rather than a system-wide failure.
  • Deferred queue growth tracking. Looped mail may enter the deferred queue before bouncing. Queue depth metrics show the growth pattern that precedes the bounces.
  • DNS resolver health. MX resolution is central to this error. Monitoring DNS latency and failure rate independently confirms whether the resolver is contributing.
  • Configuration change correlation. When bounce rate spikes, correlating the timestamp against recent system-level changes helps identify the triggering edit.