Configure Postfix on RHEL 8 to send outbound mail through Gmail's SMTP relay (port 587), instead of trying to deliver directly over port 25 — which most ISPs/home networks block.
By default, Postfix tries to deliver mail directly to the recipient's mail server over port 25 (standard SMTP). On a home network (broadband/WiFi), the ISP blocks outbound port 25 to prevent spam — this is an almost universal residential ISP policy in India (Airtel, Jio, BSNL, etc.) and elsewhere.
Symptom in logs (journalctl -u postfix):
connect to alt4.gmail-smtp-in.l.google.com[142.250.101.27]:25: Connection refused
status=deferred (connect to alt4.gmail-smtp-in.l.google.com[142.250.101.27]:25: Connection refused)
This is not a Postfix config bug — it's the network blocking the connection. The fix is to stop trying to deliver mail directly, and instead route (relay) all outbound mail through an authenticated service like Gmail on port 587 (Submission port, used for authenticated mail clients — generally not blocked).
[Your App/Script/Cron]
│
│ (local, via "mail" command or sendmail)
▼
[Postfix - localhost]
│
│ authenticates using Gmail App Password
│ connects on port 587 (TLS encrypted)
▼
[smtp.gmail.com:587]
│
▼
[Recipient's Inbox]
- Postfix = Mail Transfer Agent (MTA) running locally on your RHEL box. It accepts mail from local users/scripts (e.g.
mailcommand, cron job alerts, application notifications). - Relay / Smarthost = Instead of Postfix resolving the recipient's mail server itself, it forwards everything to Gmail's SMTP server, which then handles final delivery.
- SASL Authentication = Postfix logs into your Gmail account (using an App Password, not your real password) before Gmail accepts mail from it.
- TLS Encryption = The connection between Postfix and Gmail is encrypted (
smtp_tls_security_level = encrypt), required for port 587.
This relay pattern is exactly what's used in production environments — servers/apps rarely send mail directly to the internet; they go through a relay (Gmail, AWS SES, SendGrid, Mailgun, etc.).
| File | Purpose | Edited By You? |
|---|---|---|
/etc/postfix/main.cf |
Main Postfix config — relay host, TLS, SASL settings | ✅ Yes |
/etc/postfix/sasl_passwd |
Stores Gmail address + App Password (plaintext, root-only) | ✅ Yes (create new) |
/etc/postfix/sasl_passwd.db |
Hashed/indexed version of the above, auto-generated | ⚙️ Auto (via postmap) |
- RHEL 8 with Postfix installed (
postfixpackage) - A Gmail account with 2-Step Verification enabled
- A Gmail App Password (16 characters) — generate at: https://myaccount.google.com/apppasswords
Your normal Gmail login password will not work here. Google requires a separate App Password for SMTP apps.
Postfix needs this to authenticate with Gmail.
sudo dnf install cyrus-sasl-plain -ysudo nano /etc/postfix/sasl_passwdAdd this single line (replace with your Gmail and App Password, no spaces in the password):
[smtp.gmail.com]:587 youraccount@gmail.com:your16charapppassword
sudo postmap /etc/postfix/sasl_passwd
sudo chmod 600 /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db
sudo chown root:root /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.dbpostmapconverts the plain text file into a fast-lookup.dbfile that Postfix actually reads.chmod 600ensures onlyrootcan read/write it — important since it holds a credential in plaintext.
sudo nano /etc/postfix/main.cfAdd at the end of the file:
relayhost = [smtp.gmail.com]:587
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_tls_security_level = encrypt
smtp_tls_CAfile = /etc/pki/tls/certs/ca-bundle.crt
⚠️ Before adding: check ifsmtp_tls_security_levelalready exists earlier in the file:grep -n "smtp_tls_security_level" /etc/postfix/main.cfIf it does (commonly set to
mayby default), comment out the old line instead of having two conflicting entries (see Troubleshooting below).
sudo systemctl restart postfixecho "Test mail from RHEL via Gmail relay" | mail -s "Postfix Relay Test" youraccount@gmail.comsudo journalctl -u postfix -n 20 --no-pager✅ Success looks like:
status=sent (250 2.0.0 OK ... gsmtp)
❌ Failure (still blocked/misconfigured) looks like:
status=deferred (connect to ... Connection refused)
This means smtp_tls_security_level is defined twice in main.cf — once by default (may) and once by you (encrypt). Postfix uses the later value, but logs a warning every restart.
Fix — find both entries:
grep -n "smtp_tls_security_level" /etc/postfix/main.cfComment out the older/default one (replace <LINE_NUMBER> with the line number of the may entry):
sudo sed -i '<LINE_NUMBER>s/^smtp_tls_security_level = may$/#&/' /etc/postfix/main.cf
sudo systemctl restart postfixThis comments the line out (prefixes #) rather than deleting it — safer, reversible.
- Confirm you're using port
587inrelayhost, not25. - Confirm
sasl_passwd.dbexists — re-runpostmapif you editedsasl_passwdafter the first setup. - Check the mail queue:
mailq - Force a retry:
sudo postqueue -f
- Check Gmail Spam/Junk folder — first-time senders from a new server often land there.
- Confirm the App Password wasn't regenerated/revoked in your Google Account.
| Concept | Why It Matters |
|---|---|
| Port 25 vs Port 587 | 25 = direct server-to-server SMTP (often ISP-blocked); 587 = authenticated client submission (standard for app/relay use) |
| Smarthost / Relay pattern | Production systems rarely deliver mail directly — they relay through a trusted provider (Gmail, SES, SendGrid, Mailgun) |
| SASL Authentication | How Postfix proves its identity to an external mail provider |
| TLS encryption in mail transport | Protects credentials and mail content in transit |
Reading Postfix logs (journalctl -u postfix) |
Core sysadmin/production-support troubleshooting skill |
Config file conflict debugging (grep -n) |
Common pattern for resolving "duplicate setting" issues in any .conf/.cf file |
postfix-gmail-relay/
├── README.md ← this file
├── main.cf.snippet ← the exact lines to add to main.cf
└── sasl_passwd.example ← template for the credentials file (no real password)
- Never commit your real
sasl_passwdfile (with actual App Password) to GitHub. - This repo includes only an
.exampletemplate — always.gitignorethe real file. - Treat the App Password like any other secret/credential.