Postfix Email Forwarding with Cloudflare DNS: Step-by-Step Guide

Postfix Email Forwarding with Cloudflare DNS: Step-by-Step Guide

Codeable.io

Postfix email forwarding lets you receive mail at addresses like hello@example.com and deliver it to an existing inbox, such as Gmail. You don’t need a full mailbox server. In this guide, you will configure Postfix, add the right records in Cloudflare, and test the whole chain.

The setup is simple. However, one detail trips up many people: the DNS records must live where your nameservers actually point. We will cover that first, because it saves hours of debugging.

What you need before you start

Before you begin, make sure you have the following:

  • A Linux server with Postfix installed (Debian or Ubuntu in this guide)
  • A domain managed in Cloudflare
  • A public IP address for the server, shown here as 203.0.113.10
  • Port 25 open to the internet (some hosting providers block it by default)
  • A destination inbox, such as you@gmail.com

Throughout this tutorial, replace example.com with your domain and mail.example.com with your mail hostname.

How Postfix email forwarding works

When someone sends a message to hello@example.com, their mail server looks up the MX record for example.com. That record points to mail.example.com. The sender then connects to your server on port 25.

Next, Postfix checks its virtual alias map. If it finds a match, it rewrites the recipient to your external address. Finally, Postfix delivers the message to that address.

So the chain has four links: DNS, network, Postfix, and the destination. If one link breaks, nothing arrives. For that reason, we test them in order.

Step 1: Confirm where your DNS is hosted

Many people edit the wrong DNS zone. Check which nameservers the public internet uses:

dig NS example.com +short @8.8.8.8

If the answer shows Cloudflare nameservers (for example name.ns.cloudflare.com), then Cloudflare is your only source of truth. Any zone file on your own BIND server is ignored. Therefore, make all changes in the Cloudflare dashboard.

Step 2: Add the DNS records in Cloudflare

Open the Cloudflare dashboard, select your domain, and go to DNS → Records. Then add these four records:

TypeNameContentProxy statusPriority
Amail203.0.113.10DNS only (grey cloud)n/a
MX@mail.example.comn/a10
TXT@v=spf1 ip4:203.0.113.10 ~alln/an/a
TXT_dmarcv=DMARC1; p=none; rua=mailto:you@gmail.comn/an/a

Pay attention to the proxy setting. Cloudflare’s proxy only handles web traffic, so it cannot carry SMTP. If the mail A record is orange (proxied), senders get Cloudflare’s IP addresses and never reach your server. Always switch it to grey (DNS only). See Cloudflare’s documentation on proxy status for details.

Also, remove any old MX records. Templates and domain connectors often add entries such as _dc-mx.xxxx.example.com. If they stay, mail keeps going to the wrong place. Likewise, keep exactly one SPF record, because two v=spf1 records invalidate each other. The SPF specification (RFC 7208) explains why.

Finally, keep your website records (@ and www) proxied if you want Cloudflare’s protection for the site. Only the mail hostname needs to be unproxied.

Step 3: Configure Postfix for forwarding

Now move to the server. Postfix handles forwarding domains through virtual aliases. The official Postfix virtual domain documentation covers every option, but a minimal setup needs only a few lines.

Edit /etc/postfix/main.cf:

myhostname = mail.example.com
mydestination = $myhostname, localhost
virtual_alias_domains = example.com
virtual_alias_maps = hash:/etc/postfix/virtual

Here is the key rule: a domain must appear in either mydestination or virtual_alias_domains, never both. A domain listed in both causes errors such as “mail loops back to myself”. If your forwarding domain is currently in mydestination, remove it.

Next, create the alias map in /etc/postfix/virtual:

# Specific addresses
hello@example.com      you@gmail.com
contact@example.com    you@gmail.com

# Catch-all for everything else
@example.com           you@gmail.com

Specific entries always win over the catch-all. As a result, you can route some addresses elsewhere while the rest fall through.

Then compile the map and reload Postfix:

sudo postmap /etc/postfix/virtual
sudo postfix check
sudo systemctl reload postfix

Remember to run postmap after every edit. Postfix reads the compiled .db file, not the text file.

Step 4: Test Postfix email forwarding locally

Before involving the internet, verify the lookup:

postmap -q "anything@example.com" hash:/etc/postfix/virtual

The command should print your destination address. Next, run a dry-run delivery check:

sendmail -bv anything@example.com

You should see the recipient expand to your external address, with a status such as deliverable. Nothing is actually sent in this mode.

Then confirm Postfix listens on all interfaces:

postconf inet_interfaces
ss -ltnp | grep ':25'

You want inet_interfaces = all and a listener on 0.0.0.0:25.

Step 5: Verify DNS from the outside

Now check that the public internet sees your records. Query Cloudflare directly first, because that answer is never cached:

dig MX example.com +short @name.ns.cloudflare.com
dig A mail.example.com +short @name.ns.cloudflare.com

Use one of your two assigned Cloudflare nameservers in place of name.ns.cloudflare.com. Expect 10 mail.example.com. and your server IP.

After that, query public resolvers:

dig MX example.com +short @1.1.1.1
dig MX example.com +short @8.8.8.8

If these still show old data, they are serving cached answers. The next section explains how to clear them.

How to flush DNS caches

Public resolvers keep answers until the record’s TTL expires. Fortunately, the two largest providers offer manual purge tools:

You can also flush your own machine’s cache. On Ubuntu with systemd-resolved, run:

sudo resolvectl flush-caches

On macOS, use sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder. On Windows, use ipconfig /flushdns.

Keep in mind that you cannot flush every resolver on the internet. Lower the TTL before a migration, and most senders will pick up the change within minutes.

Step 6: Send a real test and watch the log

Send a message from a different account to test1@example.com. A non-Gmail address gives the clearest result, because Gmail-to-Gmail forwarding can be treated as a loop or a duplicate.

Meanwhile, watch the mail log on the server:

sudo tail -f /var/log/mail.log | grep -E 'example.com|status=|NOQUEUE'

A successful forward looks like this:

smtpd[...]: connect from mail-xxx.google.com[...]
... orig_to=<test1@example.com>, to=<you@gmail.com> ... status=sent

Expect background noise as well. Port 25 attracts bots, so lines like connect from unknown[...] followed by lost connection after AUTH are normal. They are harmless when relaying is restricted.

Step 7: Add SRS so forwarded mail is not rejected

Basic Postfix email forwarding keeps the original sender address. When Gmail checks that sender’s SPF record, your server’s IP is not authorised, so the check fails. Strict senders (those with p=reject DMARC policies) may then have their mail rejected or sent to spam.

The fix is the Sender Rewriting Scheme (SRS). It rewrites the envelope sender so that SPF passes for your domain. Install PostSRSd:

sudo apt install postsrsd

Then add these lines to main.cf:

sender_canonical_maps = tcp:127.0.0.1:10001
sender_canonical_classes = envelope_sender
recipient_canonical_maps = tcp:127.0.0.1:10002
recipient_canonical_classes = envelope_recipient,header_recipient

Finally, enable the service and restart Postfix:

sudo systemctl enable --now postsrsd
sudo systemctl restart postfix

Check /etc/default/postsrsd first, because the port numbers can differ between versions. Set up SRS only after the basic forwarding test works.

Troubleshooting common errors

SymptomLikely causeFix
No smtpd lines when a test is sentMX missing or pointing elsewhereCheck Cloudflare records and flush caches
dig A mail... shows Cloudflare IPsmail record is proxiedSet it to DNS only (grey cloud)
Mail loops back to myselfDomain in both mydestination and virtual_alias_domainsRemove it from mydestination
Relay access deniedDomain missing from virtual_alias_domainsAdd it and reload Postfix
User unknown in virtual alias tableNo matching entry and no catch-allAdd the address or an @example.com line
unknown user: "localuser"Alias target is a Unix account that does not existUse a real account or an external address
Mail arrives in spamMissing SRS, SPF, or reverse DNSAdd SRS and ask your host to set a PTR record
Connection timeout on port 25Firewall or provider blockOpen port 25 or contact your host

Extra steps for sending mail

Everything above covers receiving and forwarding. If your server also sends mail as @example.com, add DKIM signing with OpenDKIM. In addition, ask your hosting provider to set the reverse DNS (PTR) of your IP to mail.example.com. Without both, outgoing mail will often land in spam.

For related setup advice, read our guide on configuring SMTP for WordPress.

Final checklist

Use this list before you call the job done:

  • Cloudflare is confirmed as the DNS host
  • The mail A record is grey cloud
  • One MX record exists, pointing to your mail host
  • One SPF record exists
  • The domain is only in virtual_alias_domains
  • postmap was run after the last edit
  • A real test from another account shows status=sent
  • SRS is installed if strict senders matter to you

Conclusion

Postfix email forwarding takes only a few lines of configuration. Most failures come from DNS, not Postfix itself. Check where your nameservers point, keep the mail record unproxied, and test each link in the chain in order. With SRS in place, you get a reliable setup that forwards your domain’s mail to any inbox.

Leave a Reply

Your email address will not be published. Required fields are marked *