Postfix “Relay access denied”: Fix relaying without creating an open relay

Was this helpful?

It always happens at the least convenient time: your app tries to email a password reset, and Postfix replies with Relay access denied. The developers say, “But it works on my laptop.” Your monitoring says the queue is growing. Your inbox says the business is angry.

The fix is rarely “turn relaying on.” The fix is “turn relaying on for the right things.” Postfix is doing its job: refusing to be used as a spam cannon. Your job is to teach it who is allowed to relay, under what conditions, with as little ambiguity as possible.

What “Relay access denied” actually means

“Relay access denied” is Postfix saying: “I received a message from a client, but I am not willing to forward it to that destination under the current policy.” It’s a policy failure, not a connectivity failure. The server can accept the SMTP conversation, but it refuses to be an intermediary.

This usually appears as one of these:

  • During RCPT TO: 554 5.7.1 <user@remote.tld>: Relay access denied (classic)
  • In Postfix logs: NOQUEUE: reject: RCPT from ...: 554 5.7.1 ... Relay access denied; from=<...> to=<...>
  • App stack trace: “SMTP 554 Relay access denied”

Postfix decides whether to relay based on a few concepts:

  • Local recipient domains: things this server delivers locally (via mydestination and virtual_mailbox_domains).
  • Relay domains: things this server is willing to relay for (via relay_domains).
  • Client trust: networks or identities allowed to relay (via mynetworks, SASL auth, TLS client certs, etc.).
  • Recipient restrictions: the rule chain that decides yes/no at RCPT time (usually in smtpd_recipient_restrictions or, newer style, smtpd_relay_restrictions).

Postfix’s default stance is conservative: it will accept mail to local domains, and it will reject relaying to arbitrary remote domains unless the client is trusted. The error is annoying, but it’s a good sign your MTA isn’t doing “community outreach” to spammers.

One quote worth keeping on a sticky note: “Hope is not a strategy.” — Gene Kranz. SMTP policy is the same: define it, don’t wish for it.

Fast diagnosis playbook (check these first)

This is the part you run during an incident call while someone asks, “Can we just open it up?” No. Here’s the fastest path to “what broke” without opening the relay floodgates.

1) Confirm where the rejection happens

  • If the rejection is at RCPT TO with 5.7.1, it’s policy (relay permissions).
  • If it’s at connect or MAIL FROM, you’re in a different failure mode (firewall, TLS requirements, sender restrictions).

2) Identify the client IP and the recipient domain

You need the client IP and the domain being relayed to. With those two, you can decide whether the client should be trusted (mynetworks / SASL) and whether the recipient is local/relayable.

3) Check three Postfix settings, in this order

  1. smtpd_relay_restrictions (or smtpd_recipient_restrictions on older configs): do you have permit_mynetworks and/or permit_sasl_authenticated before reject_unauth_destination?
  2. mynetworks: does it include the client?
  3. mydestination, virtual_mailbox_domains, relay_domains: is the recipient domain supposed to be local, virtual, or relayed?

4) If it’s a submission use-case, stop using port 25

For users/apps that should authenticate, use the submission service on 587 (or 465) with SASL auth and TLS. Port 25 is for server-to-server SMTP. You can use it internally, but you need to be intentional.

5) Validate you’re not accidentally an open relay

Before you declare victory, test relaying from an untrusted network. You’re aiming for: “trusted clients can relay; random internet clients cannot.”

Joke #1: An open relay is like leaving your office badge printer in the lobby—technically convenient, spiritually catastrophic.

Core principles: allow relaying without becoming an open relay

Principle A: “Trusted client” is a narrow category

mynetworks should contain only networks you control and that are allowed to send mail to the internet through you. Usually that’s:

  • Loopback (127.0.0.0/8, [::1]/128)
  • Private subnets where only servers live (not coffee shop Wi‑Fi, not the whole corp LAN unless you enjoy incident calls)
  • A VPN subnet dedicated to server traffic

Don’t put 0.0.0.0/0 in there. Don’t put “all RFC1918” in there unless you enjoy debugging the CFO’s malware-infected laptop.

Principle B: Authenticated users relay; unauthenticated strangers don’t

Relaying for mobile users, CI runners, and random app containers is solved by SASL authentication on submission (587/465). You enable TLS, require auth, and permit relaying for authenticated identities.

Principle C: reject_unauth_destination is the safety catch

In the restriction chain, reject_unauth_destination is what prevents open relaying. Removing it is not “a workaround.” It’s a confession.

Postfix commonly evaluates relaying using smtpd_relay_restrictions (modern) and/or smtpd_recipient_restrictions (legacy style). The safe pattern looks like:

  • Permit trusted networks
  • Permit authenticated clients
  • Reject unauth destinations

Principle D: Decide what your Postfix is supposed to be

“Relay access denied” often happens because nobody decided whether the box is:

  • MX / inbound server (accept mail for your domains)
  • Outbound relay / smarthost (accept from internal clients and forward to internet)
  • Both (possible, but demands careful policy separation)

If it’s both, separate listener policy: port 25 for inbound MX behavior; port 587 for authenticated submission; different restrictions per service where necessary.

Principle E: Debug with the SMTP transaction in mind

Relaying decisions happen at RCPT time. If you don’t know what Postfix thinks the client is (IP, SASL username, TLS state) and what it thinks the recipient domain is (local/virtual/relay), you’re guessing. Guessing is how you ship an open relay.

Common real scenarios and the correct fix

Scenario 1: Internal app sends via port 25, gets relay denied

Symptom: App server at 10.0.2.15 connects to Postfix and tries to send to user@gmail.com; Postfix rejects relaying.

Correct fixes:

  • Preferred: Move the app to submission (587) with SASL auth; do not rely on IP trust.
  • Acceptable in controlled networks: Add only that server (or its subnet) to mynetworks.

Scenario 2: Your Postfix is an MX, and someone expects it to relay outbound

Symptom: MX receives inbound fine. Outbound from internal hosts fails with relay denied.

Fix: Decide: should the MX be your outbound relay? If yes, treat it like one: enable submission, SASL, and outbound policy. If no, deploy a separate relay (recommended) and keep the MX locked down.

Scenario 3: You set mydestination wrong, and Postfix thinks a remote domain is local

Symptom: mail to user@partner.tld gets accepted but then bounces locally, or lands in local delivery attempts.

Fix: Remove domains that aren’t yours from mydestination. Use relay_domains or transport maps when you really intend to relay specific domains.

Scenario 4: You’re using a cloud provider SMTP relayhost and see relay denied internally

Symptom: Postfix is configured with relayhost = [smtp.provider.tld]:587 and works when you test locally, but internal clients get relay denied.

Fix: Your Postfix is not relaying for those clients. Either add their network to mynetworks (narrowly) or require SASL auth for them via submission.

Scenario 5: Kubernetes/containers: source IP is not what you think

Symptom: You added the node subnet to mynetworks, but relay denied persists; logs show a different IP (NAT, overlay network).

Fix: Trust the IP you actually see in logs. Better: require auth on submission and stop playing “guess the overlay CIDR”.

Practical tasks: commands, outputs, decisions (12+)

These are the things you do on a live box. Each task includes: a command, what output means, and the decision you make next.

Task 1: Confirm the exact rejection in logs

cr0x@server:~$ sudo tail -n 50 /var/log/mail.log
Jan 03 10:14:22 mx1 postfix/smtpd[18422]: NOQUEUE: reject: RCPT from app1.internal[10.0.2.15]: 554 5.7.1 <user@gmail.com>: Relay access denied; from=<noreply@corp.example> to=<user@gmail.com> proto=ESMTP helo=<app1.internal>
Jan 03 10:14:22 mx1 postfix/smtpd[18422]: disconnect from app1.internal[10.0.2.15] ehlo=1 mail=1 rcpt=0/1 quit=1 commands=3/4

What it means: The client IP is 10.0.2.15. The rejection is at RCPT. Policy issue.

Decision: Determine whether 10.0.2.15 should be trusted via mynetworks or forced to authenticate (submission).

Task 2: See Postfix’s effective configuration for relay restrictions

cr0x@server:~$ sudo postconf -n | egrep '^(smtpd_relay_restrictions|smtpd_recipient_restrictions|mynetworks|mydestination|relay_domains|virtual_mailbox_domains|inet_interfaces|smtpd_tls_security_level|smtpd_sasl_auth_enable)'
inet_interfaces = all
mydestination = $myhostname, localhost.$mydomain, localhost
mynetworks = 127.0.0.0/8 [::1]/128
relay_domains = $mydestination
smtpd_recipient_restrictions = permit_mynetworks,reject_unauth_destination
smtpd_relay_restrictions =
smtpd_sasl_auth_enable = no
smtpd_tls_security_level = may
virtual_mailbox_domains =

What it means: Only localhost can relay (mynetworks is loopback). No SASL auth. Recipient restrictions include reject_unauth_destination (good).

Decision: If you want internal apps to relay, either expand mynetworks or enable submission + SASL and keep mynetworks narrow.

Task 3: Check whether the client IP is in mynetworks (without guessing)

cr0x@server:~$ postconf mynetworks
mynetworks = 127.0.0.0/8 [::1]/128

What it means: 10.0.2.15 is not trusted.

Decision: Don’t “just add 10.0.0.0/8” unless you can defend that choice. Prefer submission with auth for anything that isn’t a tightly controlled server subnet.

Task 4: Inspect the service-level configuration (submission vs smtp)

cr0x@server:~$ sudo postconf -M | egrep '^(smtp/inet|submission/inet|smtps/inet)'
smtp/inet = smtpd
submission/inet = submission smtpd
smtps/inet = smtps smtpd

What it means: Submission and smtps services exist. That’s good—now ensure they’re configured to require auth and allow relay for authenticated clients.

Decision: Configure submission with smtpd_tls_security_level=encrypt and smtpd_sasl_auth_enable=yes, plus restrictions.

Task 5: Verify port listeners (don’t assume systemd did it)

cr0x@server:~$ sudo ss -lntp | egrep ':(25|587|465)\s'
LISTEN 0      100          0.0.0.0:25        0.0.0.0:*    users:(("master",pid=812,fd=13))
LISTEN 0      100          0.0.0.0:587       0.0.0.0:*    users:(("master",pid=812,fd=18))
LISTEN 0      100          0.0.0.0:465       0.0.0.0:*    users:(("master",pid=812,fd=19))

What it means: Postfix is listening on 25, 587, 465. Exposure matters.

Decision: If this box is not meant to serve the internet on submission ports, restrict via firewall or inet_interfaces / service binding.

Task 6: Configure a safe baseline relay policy (modern Postfix)

Edit /etc/postfix/main.cf and set:

cr0x@server:~$ sudo postconf -e 'smtpd_relay_restrictions=permit_mynetworks,permit_sasl_authenticated,reject_unauth_destination'
cr0x@server:~$ sudo postconf -e 'smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject_unauth_destination'
...output...

What it means: You’ve put the two safe “allows” before the one crucial “deny.”

Decision: If you use both parameters, keep them consistent. Prefer using smtpd_relay_restrictions for relaying logic and keep recipient restrictions for additional checks, but in many real configs you’ll see both.

Task 7: Add a tightly scoped internal network to mynetworks

cr0x@server:~$ sudo postconf -e 'mynetworks=127.0.0.0/8 [::1]/128 10.0.2.0/24'
cr0x@server:~$ postconf mynetworks
mynetworks = 127.0.0.0/8 [::1]/128 10.0.2.0/24

What it means: Only the 10.0.2.0/24 subnet can relay without auth. Not the whole data center. Not the entire corp universe.

Decision: If you can’t confidently define a small subnet, don’t do IP-based trust. Use submission + auth.

Task 8: Enable SASL auth (server side) for submission

In practice this often uses Dovecot SASL. First verify Postfix thinks SASL is on:

cr0x@server:~$ sudo postconf -e 'smtpd_sasl_auth_enable=yes'
cr0x@server:~$ sudo postconf -e 'smtpd_sasl_type=dovecot'
cr0x@server:~$ sudo postconf -e 'smtpd_sasl_path=private/auth'
cr0x@server:~$ sudo postconf smtpd_sasl_auth_enable smtpd_sasl_type smtpd_sasl_path
smtpd_sasl_auth_enable = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth

What it means: Postfix will ask Dovecot to authenticate clients.

Decision: If you don’t have Dovecot, don’t cargo-cult these values. Use Cyrus SASL or your platform’s supported auth stack. The decision is: “auth exists and works” before you depend on it.

Task 9: Require encryption and auth on submission service

Edit /etc/postfix/master.cf for the submission service override (example):

cr0x@server:~$ sudo postconf -P 'submission/inet/smtpd_tls_security_level=encrypt'
cr0x@server:~$ sudo postconf -P 'submission/inet/smtpd_sasl_auth_enable=yes'
cr0x@server:~$ sudo postconf -P 'submission/inet/smtpd_client_restrictions=permit_sasl_authenticated,reject'
...output...

What it means: Submission requires TLS and requires auth (or rejects). This sharply reduces abuse.

Decision: If your clients can’t do TLS, fix the clients. Don’t downgrade the server to accommodate outdated libraries unless you like living dangerously.

Task 10: Reload Postfix safely and watch for config errors

cr0x@server:~$ sudo postfix check
postfix/postfix-script: warning: /etc/postfix/main.cf: unused parameter: smtpd_relay_restrictions
cr0x@server:~$ sudo systemctl reload postfix
...output...

What it means: The warning suggests your Postfix version or config path may not be using that parameter as expected, or it’s overridden by service settings.

Decision: Don’t ignore warnings. Confirm Postfix version and confirm which restriction set is actually enforced. If needed, use the parameter your version honors (smtpd_recipient_restrictions on older builds).

Task 11: Confirm Postfix version and feature expectations

cr0x@server:~$ postconf mail_version
mail_version = 3.5.23

What it means: This is new enough to use smtpd_relay_restrictions normally. If you saw “unused parameter,” something else is off (typo, wrong file, chroot, packaged defaults).

Decision: Validate the live config file path and ensure you’re editing the right instance (containers, multiple Postfix installs, etc.).

Task 12: Test SMTP policy from a trusted host (positive test)

cr0x@server:~$ swaks --to user@gmail.com --from noreply@corp.example --server mx1 --port 25
=== Trying mx1:25...
=== Connected to mx1.
<-  220 mx1 ESMTP Postfix
 -> EHLO app1.internal
<-  250-mx1
<-  250 PIPELINING
 -> MAIL FROM:<noreply@corp.example>
<-  250 2.1.0 Ok
 -> RCPT TO:<user@gmail.com>
<-  250 2.1.5 Ok
...output...

What it means: Relaying now works from the tested client environment.

Decision: Do the negative test next from an untrusted network. Success without that is not success.

Task 13: Test that you are not an open relay (negative test)

cr0x@server:~$ swaks --to victim@remote.tld --from attacker@remote.tld --server mx1 --port 25 --ehlo evil.remote.tld
=== Trying mx1:25...
=== Connected to mx1.
<-  220 mx1 ESMTP Postfix
 -> EHLO evil.remote.tld
<-  250-mx1
 -> MAIL FROM:<attacker@remote.tld>
<-  250 2.1.0 Ok
 -> RCPT TO:<victim@remote.tld>
<-  554 5.7.1 <victim@remote.tld>: Relay access denied
...output...

What it means: Untrusted client can’t relay to a remote domain. That’s what you want.

Decision: If this test succeeds (i.e., it relays), you have an incident, not a feature.

Task 14: Confirm recipient domain classification (local vs relay)

cr0x@server:~$ postconf mydestination relay_domains virtual_mailbox_domains
mydestination = $myhostname, localhost.$mydomain, localhost
relay_domains = $mydestination
virtual_mailbox_domains =

What it means: This server only considers its hostname/localhost as local. It does not relay for specific domains by policy.

Decision: If you expect it to accept mail for corp.example, you must add it to mydestination (local delivery) or configure virtual domains. If you expect it to relay for corp.example to another backend, use relay_domains plus a transport.

Task 15: Inspect the queue to distinguish “rejecting” from “accepting but failing later”

cr0x@server:~$ mailq
-Queue ID-  --Size-- ----Arrival Time---- -Sender/Recipient-------
B3C2A1D4F9      1249 Fri Jan  3 10:18:01  noreply@corp.example
                                         user@gmail.com

-- 1 Kbytes in 1 Request.

What it means: This message was accepted and queued; the problem might now be outbound delivery (DNS, blocked port 25, relayhost auth), not relay permission.

Decision: If mail is queuing, you’re past the “relay access denied” stage. Switch troubleshooting to outbound delivery logs and network egress.

Task 16: Trace one queued message end-to-end

cr0x@server:~$ sudo postcat -q B3C2A1D4F9 | sed -n '1,40p'
*** ENVELOPE RECORDS ***
message_size:            1249            1249
message_arrival_time:    Fri Jan  3 10:18:01 2026
sender: noreply@corp.example
*** MESSAGE CONTENTS ***
Received: from app1.internal (app1.internal [10.0.2.15])
        by mx1 (Postfix) with ESMTP id B3C2A1D4F9
        for <user@gmail.com>; Fri,  3 Jan 2026 10:18:01 +0000 (UTC)
Subject: test
...output...

What it means: Confirms the client identity and recipient, useful for audits and for proving which host is sending.

Decision: If you see an unexpected sender host, your trust boundary is wrong. Fix mynetworks and firewalling, then investigate that host.

Three mini-stories from corporate mail trenches

Mini-story 1: The incident caused by a wrong assumption

They had a “mail relay” VM that everyone treated like plumbing: it existed, therefore it worked. An app team rolled out a new service in a different subnet after a network reorganization. Same data center, same company, different CIDR.

The rollout was clean until the first password reset email. Postfix replied with “Relay access denied,” and the service degraded in a way that looked like an auth outage. The on-call engineer for the app stared at their code because “SMTP is just another HTTP call, right?” It is not.

The wrong assumption was simple: that mynetworks included “all internal networks.” It did not. It included exactly one /24 from years ago, and the only documentation was a comment in a config file that nobody read.

They fixed it by adding the new subnet to mynetworks. The second-order fix—the one that actually mattered—was moving apps to authenticated submission on 587 and shrinking mynetworks to only a few infrastructure boxes. Fewer IP-based exceptions, fewer future surprises.

Mini-story 2: The optimization that backfired

A different company wanted to “reduce complexity” by using one Postfix instance for everything: inbound MX, outbound relay, and internal notifications. One box, one IP, one place to patch. The diagram looked terrific.

They optimized the policy too. Someone decided the restriction chain was “too strict” for internal apps and sprinkled permit rules around until emails started flowing. It shipped on a Friday, because of course it did.

Over the weekend, the box began sending a lot of mail. Not their mail. Some of it was passing through from odd source IPs that were “internal” thanks to a VPN client pool and an overbroad mynetworks entry. Once spammers find an open relay, they don’t politely keep it secret.

Monday morning was a parade: IP reputation tanked, legitimate mail got deferred, and outbound queues exploded. They “optimized” themselves into a reputation incident. The fix was unglamorous: separate inbound and outbound roles, tighten mynetworks, require SASL auth on submission, and keep reject_unauth_destination exactly where it belongs.

Mini-story 3: The boring but correct practice that saved the day

A large enterprise had a mail relay tier that almost never made headlines. That was intentional. They treated it like any other production system: change control, config management, and a dull but effective runbook.

One day a security team pushed a network ACL change that unintentionally forced a NAT path for certain subnets. Suddenly, Postfix logs showed client IPs coming from a different range. Relaying failed for a handful of services, but the blast radius stayed small because nothing relied on “the entire internal network” being trusted.

The on-call followed the runbook: check logs for the client IP, verify whether it’s in mynetworks, confirm submission auth works, then decide whether to temporarily add a narrow CIDR. They added a /28 as a stopgap and filed a ticket to fix the NAT issue.

Because they had a negative relay test in the runbook, they also verified the box still rejected relaying from the outside. No open relay, no reputation damage, no panic. Boring won again.

Interesting facts & historical context (8 points)

  1. Open relays used to be common. Early internet mail culture assumed good behavior; abuse forced MTAs to harden by default.
  2. Postfix was designed as a safer Sendmail alternative. Wietse Venema built it with a security-first architecture: multiple processes, least privilege, and containment boundaries.
  3. The industry’s stance shifted around the early 2000s. Major providers began aggressively blocking or listing open relays, pushing admins to adopt strict relay controls.
  4. reject_unauth_destination became a centerpiece. It’s effectively “do not relay unless explicitly allowed,” which is the only sane default on today’s internet.
  5. Port 587 exists because port 25 got messy. Submission was standardized so end users could authenticate and submit mail without mixing policy with server-to-server SMTP.
  6. RBLs changed operational behavior. Realtime blocklists made it easy for receivers to punish bad sending behavior quickly—sometimes too quickly.
  7. NAT and containers complicated “IP trust.” Modern infrastructure makes “this IP is that host” less reliable; auth-based submission scales better.
  8. “Relay denied” is often a success signal. It means your MTA refused to be weaponized—until you misconfigure it.

Common mistakes: symptom → root cause → fix

This section is deliberately specific. If you recognize your symptom, you get a fix that doesn’t involve turning your mail server into a community service.

Mistake 1: “It works locally but not from other servers”

  • Symptom: sendmail from the Postfix host works; remote app servers get Relay access denied.
  • Root cause: mynetworks only contains loopback, or restrictions don’t permit your internal CIDR.
  • Fix: Add a narrow CIDR to mynetworks or require submission auth. Do not widen to “everything private.”

Mistake 2: “We removed reject_unauth_destination and it fixed it”

  • Symptom: Mail flows, and then reputation collapses. Or your monitoring shows unusual outbound volume.
  • Root cause: You created an open relay (or close enough).
  • Fix: Put reject_unauth_destination back. Add permit_mynetworks and permit_sasl_authenticated before it. Test negative relay from untrusted networks.

Mistake 3: “We put our domain into mydestination so it would relay”

  • Symptom: Mail to a domain gets accepted but delivered locally, bounced, or fed into the wrong mailbox mechanism.
  • Root cause: Confusing “local delivery” with “relay for a domain.”
  • Fix: Use relay_domains and a transport map if you are relaying to another mail system; use mydestination only when you deliver locally.

Mistake 4: “We trusted the whole VPN subnet”

  • Symptom: Random endpoints can send outbound mail without auth; security gets nervous for good reasons.
  • Root cause: VPN client pools are not “servers.” IP trust is wrong boundary.
  • Fix: Require SASL auth for remote users; remove VPN client ranges from mynetworks.

Mistake 5: “We enabled submission, but relaying still denied”

  • Symptom: Client authenticates (or thinks it does) on 587, but RCPT still gets relay denied.
  • Root cause: Missing permit_sasl_authenticated in the relay restriction chain for that service, or submission service isn’t actually requiring auth.
  • Fix: Add permit_sasl_authenticated to smtpd_relay_restrictions and/or submission service overrides; confirm SASL works in logs (sasl_username= entries).

Mistake 6: “We added the subnet, but Postfix still says relay denied”

  • Symptom: You added 10.0.2.0/24 but logs show the client as 10.244.3.18 or some NAT IP.
  • Root cause: NAT, proxy, overlay networks, or the app is not coming from where you think.
  • Fix: Trust what Postfix logs show. Adjust mynetworks accordingly—or stop trusting IPs and move to auth-based submission.

Mistake 7: “We used relayhost and assumed clients can relay”

  • Symptom: Postfix can send outbound when run locally, but internal clients get relay denied.
  • Root cause: relayhost config affects outbound delivery from Postfix itself; it doesn’t automatically authorize inbound clients to relay through it.
  • Fix: Configure inbound client authorization (mynetworks or submission auth) separately.

Joke #2: SMTP is simple until humans get involved, at which point it becomes an interpretive dance with logs.

Checklists / step-by-step plan

Step-by-step: Fix relay denied for internal servers (IP-trust model)

  1. Identify the client IP from logs (tail -n 50 /var/log/mail.log).
  2. Decide if the client should be trusted (is it a server you control, patched, and monitored?).
  3. Add a narrow CIDR to mynetworks (prefer /32 or /28–/24, not /8).
  4. Ensure restrictions include: permit_mynetworks, then reject_unauth_destination.
  5. Reload and test from a trusted host (positive test).
  6. Test from an untrusted host (negative test) to confirm not open relay.
  7. Document the CIDR and owner (future you will ask why it exists).

Step-by-step: Fix relay denied for users/apps (auth model on submission)

  1. Enable submission (587) and ensure it listens only where intended (firewall/bind).
  2. Require TLS on submission (smtpd_tls_security_level=encrypt for that service).
  3. Enable SASL auth (Dovecot or Cyrus) and verify authentication in logs.
  4. Permit authenticated relaying (permit_sasl_authenticated before reject_unauth_destination).
  5. Optionally restrict sender identities per user/app to reduce abuse potential.
  6. Test with a real client that authenticates and sends to a remote domain.
  7. Run the negative open-relay test on port 25 from untrusted networks.

Step-by-step: Split inbound MX and outbound relay roles (best practice)

  1. Keep MX strict: port 25 exposed, no general relaying, accepts only your inbound domains.
  2. Deploy outbound relay: accepts from internal networks and/or submission auth, forwards outbound.
  3. Separate IP reputations: outbound IP can be rate-limited and monitored independently.
  4. Use explicit routing: internal clients target the relay; MX does not become a catch-all.
  5. Audit both: logs, queue behavior, and relay denial rates become clear signals.

FAQ (the questions people actually ask)

1) What does reject_unauth_destination actually do?

It rejects recipients that are not in your local/virtual/relay domains unless the client is authorized to relay. It’s the main open-relay prevention rule in many Postfix setups.

2) Is adding my app subnet to mynetworks safe?

It can be safe if the subnet contains only controlled servers (not user endpoints) and you have strong host security. It’s risky when networks are shared, dynamic, or include VPN clients. Auth on 587 scales better.

3) Why not just allow relaying from all RFC1918 ranges?

Because RFC1918 ranges show up in places you don’t expect: VPN pools, misconfigured NAT, lab networks bridged into prod, and occasionally an attacker with internal foothold. Broad trust boundaries age poorly.

4) Should I fix this by setting relay_domains = *?

No. That’s a classic path to “we became an open relay in spirit.” Relay domains are about which recipient domains you’ll relay for as a service; they don’t replace client authorization.

5) My users are remote. Do I need to open port 25 inbound?

For user submission, no. Use port 587 (or 465) with TLS and auth. Port 25 is for server-to-server mail. If you run an MX, you need 25 inbound for internet delivery.

6) I enabled SASL but it still says relay denied. What should I look for in logs?

Look for sasl_username= in Postfix smtpd logs during the session. If it’s missing, auth isn’t happening. If it’s present, ensure permit_sasl_authenticated is in the relay restriction chain for that service.

7) Can I allow relaying only to certain external domains?

Yes. That’s a policy choice: use restriction checks (recipient access maps) to permit only approved recipient domains for unauthenticated clients, and require auth for everything else. Be careful: you’re building a ruleset that will be maintained under pressure.

8) How do I know if I accidentally created an open relay?

Test from an untrusted network: attempt to send from a non-local sender to a non-local recipient through your server. The correct outcome is rejection at RCPT with Relay access denied (or similar). Also watch logs for unusual relay volume and unknown client IPs.

9) Why does Postfix sometimes reject with “Relay access denied” even for my own domain?

If the domain isn’t listed in mydestination or virtual_mailbox_domains (or properly defined as a relay domain), Postfix treats it as non-local and applies relay rules. Fix the domain classification first.

10) Should I run inbound and outbound on the same Postfix instance?

You can, but you’re signing up for policy complexity. If you have the option, split roles: MX inbound and outbound relay are different risk profiles and operational concerns.

Conclusion: practical next steps

“Relay access denied” is not Postfix being difficult. It’s Postfix refusing to become someone else’s spam problem. Your fix is to explicitly define who is allowed to relay and how: narrow IP trust for controlled servers, authenticated submission for everything else, and reject_unauth_destination staying right where it is.

Next steps you can execute today:

  1. Pull one real log line and identify client IP + recipient domain.
  2. Decide whether that client should be trusted by IP. If you hesitate, require auth.
  3. Implement the safe restriction chain and reload Postfix with postfix check first.
  4. Run both tests: “trusted client can relay” and “untrusted client cannot.”
  5. Write down the policy in one paragraph and keep it with the config. Future you will be tired and grateful.
← Previous
Proxmox VLAN not working: configure a VLAN-aware bridge correctly
Next →
Ubuntu 24.04: systemd-resolved breaks Docker DNS — fix without disabling everything

Leave a comment