
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:
| Type | Name | Content | Proxy status | Priority |
|---|---|---|---|---|
| A | mail | 203.0.113.10 | DNS only (grey cloud) | n/a |
| MX | @ | mail.example.com | n/a | 10 |
| TXT | @ | v=spf1 ip4:203.0.113.10 ~all | n/a | n/a |
| TXT | _dmarc | v=DMARC1; p=none; rua=mailto:you@gmail.com | n/a | n/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:
- Google Public DNS (8.8.8.8): use the Google cache flush tool. Enter the domain and record type (MX, then A for the mail host).
- Cloudflare DNS (1.1.1.1): use the Cloudflare purge cache page and repeat for each record.
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
| Symptom | Likely cause | Fix |
|---|---|---|
No smtpd lines when a test is sent | MX missing or pointing elsewhere | Check Cloudflare records and flush caches |
dig A mail... shows Cloudflare IPs | mail record is proxied | Set it to DNS only (grey cloud) |
Mail loops back to myself | Domain in both mydestination and virtual_alias_domains | Remove it from mydestination |
Relay access denied | Domain missing from virtual_alias_domains | Add it and reload Postfix |
User unknown in virtual alias table | No matching entry and no catch-all | Add the address or an @example.com line |
unknown user: "localuser" | Alias target is a Unix account that does not exist | Use a real account or an external address |
| Mail arrives in spam | Missing SRS, SPF, or reverse DNS | Add SRS and ask your host to set a PTR record |
| Connection timeout on port 25 | Firewall or provider block | Open 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
mailA 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 postmapwas 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.

