SSD Nodes Learn Hosting plans →
Guides Matt ConnorBy Matt Connor

Fix Postfix "Relay access denied" errors

Why Postfix answers 454 4.7.1 Relay access denied, the three causes behind it, and the exact postconf fixes that do not turn your VPS into an open relay.

What "Relay access denied" means in Postfix

Postfix returns "Relay access denied" when a client it does not trust asks it to deliver mail to a domain it does not treat as its own. Both halves must be true at the same time. A trusted client may send to any domain, and any client may send to a domain Postfix hosts. The fix is to make one of those halves true for the mail you want, and never for everyone.

The refusal comes in an SMTP (simple mail transfer protocol) session, right after the RCPT TO command. You will see one of these two replies:

454 4.7.1 <someone@example.com>: Relay access denied
554 5.7.1 <someone@example.com>: Relay access denied

The text after the address is the same in both, because Postfix builds both replies in the same function in its source (smtpd_check.c). Only the numbers differ. This guide shows which one Ubuntu's shipped configuration gives, explains why the other one exists, and then fixes the three causes readers actually hit:

  1. An app server or a Docker network sends mail, but its address is not in mynetworks.
  2. A mail client sends on port 25 without logging in, instead of authenticating on the submission port 587.
  3. Incoming mail for your own domain is refused, because that domain is not in mydestination, virtual_mailbox_domains or relay_domains.

The commands are written for Ubuntu 26.04, which ships Postfix 3.10.6 in main. Check your version with apt-cache policy postfix.

Reproduce the error without sending any mail

You can trigger the error on purpose with no outbound mail at all. The test client talks to Postfix on 127.0.0.1. Postfix refuses the external recipient at RCPT TO, so nothing leaves the box. Do this on a test server, or with port 25 closed in your firewall: the default "Internet Site" install makes Postfix listen on all interfaces.

sudo apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y postfix swaks
sudo postfix status && echo running

postfix status exits with zero when Postfix is running, so you should see running. If not, start it with sudo systemctl start postfix. On a machine with no systemd, use sudo postfix start.

Now look at the settings that decide relaying:

postconf smtpd_relay_restrictions smtpd_recipient_restrictions
postconf mynetworks mydestination
postconf -n

Postfix's built-in default for smtpd_relay_restrictions is permit_mynetworks, permit_sasl_authenticated, defer_unauth_destination, and Ubuntu's installer writes the same three words into main.cf. Read the list from left to right. Postfix permits a client inside mynetworks. It permits a client that logged in with SASL (simple authentication and security layer). Everything else that is addressed to a foreign domain hits defer_unauth_destination.

Ubuntu's template sets mynetworks = 127.0.0.0/8, and the installer may add IPv6 loopback to it. That means 127.0.0.1 is trusted, and a test from it would relay. To see the error, replace mynetworks with a range that does not contain 127.0.0.1. The range 192.0.2.0/24 is reserved for documentation, so no real client uses it.

sudo postconf -e 'mynetworks = 192.0.2.0/24'
sudo postfix reload
postconf mynetworks

postconf -e only edits the file. postfix reload makes the running daemons read it again. Now send to an external recipient:

swaks --server 127.0.0.1 --to someone@example.com
echo "swaks exit: $?"

swaks connects to port 25 by default. Lines starting with -> are what it sent, and lines starting with <- are what the server answered. Read the answer to RCPT TO. With Ubuntu's shipped configuration, Postfix's source gives 454 4.7.1 <someone@example.com>: Relay access denied, because the restriction list ends in defer_unauth_destination. If your output shows 554 5.7.1 instead, something in your main.cf already uses reject_unauth_destination, and postconf -n shows where.

Now send to a local recipient. Check that localhost appears in postconf -h mydestination first, and pick another listed domain if it does not.

swaks --server 127.0.0.1 --to root@localhost
echo "swaks exit: $?"

The client is the same and just as untrusted, but this recipient should be accepted at RCPT TO, because localhost is a domain Postfix delivers itself. Compare the two exit codes. The swaks reference defines 0 as "no errors occurred" and 24 as "no RCPTs accepted". This one test shows both halves of the rule: an untrusted client was allowed to reach a local domain, and refused for a foreign one.

Put loopback back when you are done: sudo postconf -e 'mynetworks = 127.0.0.0/8 [::1]/128' and sudo postfix reload.

Where Postfix logs the rejection

Do not assume /var/log/mail.log exists. Ubuntu's postfix package does not depend on any syslog daemon, so a minimal image may have no rsyslog, and then that file never appears. Check what you have:

ls /var/log/mail.log 2>/dev/null || echo "no mail.log"
sudo grep "Relay access denied" /var/log/mail.log
sudo journalctl --since "15 min ago" | grep "Relay access denied"

If neither search finds anything, let Postfix write its own log file. Postfix 3.4 and later can log without syslog through its postlogd service. The parameter is maillog_file. An empty value, the default, means syslog. By default the file name must start with /var or be /dev/stdout.

grep -n '^postlog' /etc/postfix/master.cf
sudo postconf -e 'maillog_file = /var/log/postfix.log'
sudo systemctl restart postfix

The grep must print a postlog unix-dgram line, because file logging needs that service in master.cf. If it prints nothing, add the line postlog unix-dgram n - n - 1 postlogd to the end of /etc/postfix/master.cf first. Where systemd is not running, use sudo postfix stop and then sudo postfix start instead of systemctl. Run the swaks test again, then search the file with sudo grep "Relay access denied" /var/log/postfix.log.

The line comes from postfix/smtpd. It starts with NOQUEUE, because the message was refused before Postfix gave it a queue ID. It carries the client address, the reply you saw in swaks, and the from= and to= addresses. The client address is the most useful field: it is the exact address Postfix compared against mynetworks. Copy the address from your own log line, not from a guess.

Why Ubuntu returns 454 when the other code is 554

The two restrictions do the same check. postconf(5) describes defer_unauth_destination as rejecting "the same requests as reject_unauth_destination, with a non-permanent error code". The source code shows what that means in numbers. reject_unauth_destination replies with the value of relay_domains_reject_code, which defaults to 554, and the enhanced status 5.7.1. defer_unauth_destination replies with that same value minus 100, and 4.7.1. So the defaults are 554 and 454. The manual page never prints "454". That number is arithmetic from the source, which also means that changing relay_domains_reject_code changes both replies.

Do not confuse this with reject_code, which also defaults to 554. That parameter belongs to the plain reject restriction, not to relaying.

A 4xx reply tells the sending server to try again later. A 5xx reply tells it to give up and bounce the message. With Postfix's default, a mistake in your relay setup makes senders queue and retry, which gives you time to fix it. Once your configuration is stable, you can switch to the permanent version:

sudo postconf -e 'smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated reject_unauth_destination'
sudo postfix reload
swaks --server 127.0.0.1 --to someone@example.com

With loopback removed from mynetworks again, the reply to RCPT TO should now start with 554 5.7.1. Keep one of these restrictions in the list. Postfix refuses to receive mail at all when neither smtpd_relay_restrictions nor smtpd_recipient_restrictions contains a rule that stops relaying.

Cause 1: an app server or Docker network outside mynetworks

The symptom: a web app, a monitoring tool or a container sends mail through your Postfix server and gets "Relay access denied". The log line shows a client address that is not in mynetworks. The fix is to add that one address, and nothing wider.

postconf mynetworks
sudo postconf -e 'mynetworks = 127.0.0.0/8 [::1]/128 203.0.113.25/32'
sudo postfix reload
postconf -n | grep '^mynetworks'
sudo postfix check

postconf -e replaces the whole value, so repeat the loopback entries in the new list. postfix check warns about bad ownership or permissions on Postfix's files and directories. It is not a syntax checker for main.cf, so the swaks test is still the proof. An app on another server also needs port 25 reachable from that server only. When the app can log in with a username and password, that is better than a trusted address, and the guide to sending mail from your self-hosted apps covers the outbound half of that setup, including how your apps reach a relay on the host.

When a Docker bridge subnet belongs in mynetworks

Containers on Docker's default bridge network often get addresses in 172.17.0.0/16, but do not copy that range. Each user-defined network, such as the one Docker Compose creates for a project, gets its own subnet from Docker's address pools, and it can differ from one host to the next. Read the real one:

docker network ls
docker network inspect bridge | grep -E '"(Subnet|Gateway)"'

Add that exact subnet to mynetworks only when two things are true. Postfix runs on the host and is meant to relay for those containers. Postfix's port 25 is not published to the internet through Docker. If Postfix itself runs in a container with port 25 published, test from an outside machine and read the client address in the log. If that address is a bridge address and not the real outside address, a bridge subnet in mynetworks would trust every client on the internet.

The hard rule: never widen mynetworks to the internet

Setting mynetworks = 0.0.0.0/0 makes the error disappear, and it is always wrong. postconf(5) is direct about it: "trusted" SMTP clients are allowed to relay mail through Postfix. Trust every address and your server delivers mail for anyone who connects. That is an open relay. The spam it sends carries your server's IP address, and other mail servers judge your own mail by that address, on top of what your reverse DNS PTR record already tells them.

The same applies to mynetworks_style = class. postconf(5) warns that it may trust your entire provider's network. Keep explicit addresses in mynetworks, and test from a machine outside your network after every change:

swaks --server mail.example.com --to someone@example.net

That reply must be a refusal. Many home connections block outgoing port 25, so run it from a second VPS.

Cause 2: a mail client sending on port 25 without logging in

The symptom: Thunderbird or a phone mail app can receive mail, but sending to an outside address fails with "Relay access denied". The client connects to port 25 with no login. To Postfix it is a stranger, so the rule from the first section applies. Port 25 is for servers talking to servers. Postfix's SASL documentation describes port 587 as reserved for email client applications.

permit_sasl_authenticated is already in the default relay list, but it only matches a client that has logged in. That needs smtpd_sasl_auth_enable = yes and a SASL backend, and Ubuntu's postfix package installs no backend on its own. List the backend types your build supports with postconf -a. Most self-hosted setups use Dovecot, with Postfix pointed at Dovecot's socket:

sudo postconf -e 'smtpd_sasl_type = dovecot' 'smtpd_sasl_path = private/auth'

Dovecot must then listen on /var/spool/postfix/private/auth. Configure that from Dovecot's own documentation for the version you install. Next, turn on the submission service in /etc/postfix/master.cf. Upstream's file ships it commented out, so check yours with grep -n 'submission' /etc/postfix/master.cf. Remove the # from the submission inet line and from these option lines under it:

  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_tls_auth_only=yes
  -o smtpd_relay_restrictions=
  -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject

smtpd_tls_security_level=encrypt requires TLS (transport layer security) on this port, and smtpd_tls_auth_only=yes offers the login only after TLS starts, so passwords never cross the network in clear text. TLS needs a certificate, so check postconf smtpd_tls_cert_file before you restart. Then restart and test:

sudo systemctl restart postfix
ss -ltn | grep ':587'
swaks --server mail.example.com --port 587 --tls --auth --auth-user you@example.com --to someone@example.net

ss should list a socket on port 587, and swaks should log in and get past RCPT TO. Then set the mail client to port 587, STARTTLS and password login. The submission log lines carry the tag postfix/submission, so you can tell them apart from port 25 traffic.

Cause 3: incoming mail for your own domain is refused

The symptom: people tell you their mail to you@example.com bounced or is delayed. Your log shows a "Relay access denied" line with an outside mail server as the client and your own address in to=. The client is a stranger, which is normal for incoming mail. The problem is the other half: Postfix does not treat example.com as its own.

Postfix sorts domains into address classes. Local domains are listed in mydestination. Virtual mailbox domains are listed in virtual_mailbox_domains. Domains you forward to another server are listed in relay_domains. There is also a virtual alias class. A domain in none of these lists falls into the default class, which has no domain table and only forwards mail for authorized clients. An outside server is not authorized, so it gets the relay refusal. Check all three lists:

postconf mydestination virtual_mailbox_domains relay_domains

Put the domain in the one list that matches where the mail ends up. For mailboxes owned by system users, add it to mydestination:

sudo postconf -e 'mydestination = $myhostname, localhost.$mydomain, localhost, example.com'
sudo postfix reload
swaks --server 127.0.0.1 --to you@example.com

The single quotes stop the shell from expanding $myhostname, so Postfix receives it as written. For mailboxes stored by Dovecot that do not map to system users, use virtual_mailbox_domains together with virtual_mailbox_maps. Use relay_domains when this server accepts mail and passes it to another one. Set relay_recipient_maps with it, because with that map empty Postfix accepts every recipient at that domain. Stacks such as a mailcow mail server on a VPS write these settings for you, so check their admin panel before you edit main.cf inside one.

With Ubuntu's default defer_unauth_destination, the outside servers got a 4xx reply. Most of them will retry, so mail sent while the domain was missing may still arrive after the fix. If you are weighing whether to run inbound mail at all, read whether self-hosting email is still worth it first.

How to confirm any Relay access denied fix

Use the same four steps after every change. Run postconf -n and read the value you changed, plus any warning lines postconf prints. Run sudo postfix check for file permission problems. Run sudo postfix reload. Repeat the swaks test that failed, then find the matching log line. A fix is done when the mail you want gets past RCPT TO and the outside test from a second server is still refused.

FAQ

What does "454 4.7.1 Relay access denied" mean in Postfix?

An untrusted client asked Postfix to deliver to a domain Postfix does not host, and the defer_unauth_destination restriction refused it with a temporary error. That restriction ends Postfix's default smtpd_relay_restrictions, which Ubuntu also uses. Its permanent twin, reject_unauth_destination, gives 554 5.7.1 with the same text. Both numbers come from relay_domains_reject_code, default 554, minus 100 for the defer version.

Can I fix Relay access denied by adding 0.0.0.0/0 to mynetworks?

No. postconf(5) says trusted clients in mynetworks may relay mail through Postfix, so trusting every address turns the server into an open relay for anyone on the internet. Add only the exact address or subnet of the app server or Docker network that sends through Postfix, and use SASL login for everything else.

Why does my email client get Relay access denied on port 25?

The client connects without logging in, so Postfix treats it as a stranger and refuses to forward its mail to other domains. Turn on the submission service on port 587 with TLS and SASL in master.cf, give Postfix a SASL backend such as Dovecot, and set the client to port 587 with STARTTLS and password login.

Why is incoming mail for my own domain getting Relay access denied?

The domain is missing from every Postfix address class, so Postfix only accepts it from authorized clients, and outside mail servers are not authorized. Add the domain to mydestination for system-user mailboxes, virtual_mailbox_domains for virtual mailboxes, or relay_domains when you forward it to another server, then run postfix reload.

Where does Postfix log when there is no /var/log/mail.log?

Without a syslog daemon such as rsyslog, that file is never created. Set maillog_file = /var/log/postfix.log with postconf -e, check that master.cf has the postlog unix-dgram service line, and restart Postfix. This works in Postfix 3.4 and later. By default the file name must start with /var or /dev/stdout, the two prefixes that maillog_file_prefixes allows.

Was this article useful?