You see lines like this filling your mail log:
NOQUEUE: reject: RCPT from unknown[203.0.113.45]: 504 5.5.2 <desktop-abc123>: Helo command rejected: need fully-qualified hostname; from=<user@example.com> to=<recipient@example.org> proto=ESMTP helo=<desktop-abc123>
Postfix is rejecting the connection because the client sent a bare hostname in its HELO or EHLO command instead of a fully-qualified domain name. The restriction responsible is reject_non_fqdn_helo_hostname in your smtpd_helo_restrictions.
Most rejections are legitimate: bots and spam scripts routinely send garbage or bare names in HELO. The problem starts when a legitimate client gets caught – a desktop mail client sending its machine name, an internal monitoring server using a short hostname, or an application hardcoded with localhost as its HELO string. All trigger the same 504 rejection.
What this means
The reject_non_fqdn_helo_hostname restriction rejects any HELO or EHLO command where the hostname argument is not a fully-qualified domain form or a valid address literal enclosed in brackets (such as [192.0.2.1]). The response code is 504 by default, controlled by the non_fqdn_reject_code parameter.
In Postfix versions before 2.3, this restriction was called reject_non_fqdn_hostname. The old name still works as a deprecated alias.
Postfix evaluates HELO restrictions at the RCPT TO stage, not at HELO time. This is because smtpd_delay_reject defaults to yes, which defers restriction evaluation until after the recipient address is known. That is why the rejection appears in logs as RCPT from rather than at the HELO stage.
Two companion restrictions are often configured alongside the non-FQDN check:
reject_invalid_helo_hostname: rejects malformed hostnames. Response code 501, controlled byinvalid_hostname_reject_code.reject_unknown_helo_hostname: rejects hostnames with no DNS A or MX record. Response code 450 by default (a temporary failure), controlled byunknown_hostname_reject_code. The log message readsHelo command rejected: Host not found.
For HELO restrictions to be fully enforced, smtpd_helo_required must be set to yes. Without it, a client can skip the HELO or EHLO command entirely and bypass all HELO checks.
Common causes
| Cause | What it looks like | First thing to check |
|---|---|---|
| Spam or bot traffic | High volume of 504 rejects from many rotating IPs with random or garbage HELO names | Reject rate by IP over time |
| Misconfigured MUA | Authenticated user on port 587 rejected; mail client sends its machine name (e.g., DESKTOP-ABC123) | Whether submission port has a HELO restriction override |
| Internal host with bare name | Monitoring server, cron job, or hypervisor notification rejected; hostname is a single-label name with no dot | Whether the sender is in mynetworks or needs a check_helo_access exception |
| Your own application | App-generated mail rejected; application sends localhost or a bare hostname in HELO | What HELO name the application’s SMTP library sends |
Quick checks
# Show current HELO restrictions
postconf -h smtpd_helo_restrictions
# Check if HELO is required
postconf -h smtpd_helo_required
# Check the reject code for non-FQDN HELO
postconf -h non_fqdn_reject_code
# Check delayed reject behavior (explains why rejection is at RCPT, not HELO)
postconf -h smtpd_delay_reject
# Find recent HELO rejections (Debian/Ubuntu: /var/log/mail.log, RHEL/CentOS: /var/log/maillog)
grep 'Helo command rejected' /var/log/mail.log | tail -20
# Count HELO rejections by client IP
grep 'Helo command rejected' /var/log/mail.log | grep -oE 'from [^[]+\[[^]]+\]' | sort | uniq -c | sort -rn | head -20
# Show the actual HELO names being rejected
grep 'Helo command rejected' /var/log/mail.log | grep -oE 'helo=<[^>]*>' | sort | uniq -c | sort -rn | head -20
# Check for submission port overrides in master.cf
postconf -M submission/inet
# Check if reject_unknown_helo_hostname is also firing (different root cause)
grep 'Host not found' /var/log/mail.log | tail -20
How to diagnose it
flowchart TD
A["504 HELO reject in logs"] --> B{"High volume
from many IPs?"}
B -->|"Yes"| C["Likely spam or bot traffic
Restrictions working as intended"]
B -->|"No"| D{"On port 587?"}
D -->|"Yes"| E["Authenticated MUA rejected
Add submission port override"]
D -->|"No, port 25"| F{"Internal host?"}
F -->|"Yes"| G["Uses bare name
Add to mynetworks or helo_access"]
F -->|"No"| H["Remote client misconfig
Whitelist or notify sender"]Determine whether this is attack traffic or a false positive. Extract the rejected HELO names and client IPs using the quick checks above. A high reject rate from many rotating IPs with random strings is bot traffic. A small number of IPs with consistent, human-readable machine names (such as
DESKTOP-ABC123orproxmox-host01) are legitimate clients with misconfigured HELO.Check whether authenticated users are being rejected. Look for
sasl_method=in the rejected log lines. If an authenticated user on port 587 is being rejected by HELO restrictions, your submission service needs an override (see Fixes below).Identify which port the rejections are on. Port 25 is server-to-server traffic where strict HELO is appropriate. Port 587 is for authenticated mail user agents where strict HELO will break clients that send machine names instead of FQDNs.
Extract the exact HELO name being rejected. The
helo=<...>field in the log line shows what the client sent. A bare name likelocalhostorworkstationconfirms the restriction is firing correctly against a non-FQDN. A name that looks like an FQDN but is still being rejected may indicatereject_unknown_helo_hostnameis also active, which is a different restriction with a different response code.Read reject categories together. Check sender and recipient reject counts alongside HELO rejects. If HELO rejects spike while sender and recipient rejects stay flat, you are seeing bot traffic that fails at the HELO stage. If all three spike together, a broader configuration or policy change may be involved.
Fixes
Separate submission port restrictions (recommended for MUAs)
The standard pattern is to keep strict HELO restrictions on port 25 and relax them on port 587 where authenticated users submit mail. Add this -o line to your existing submission service definition in master.cf:
submission inet n - n - - smtpd
-o smtpd_helo_restrictions=permit_sasl_authenticated,permit
This lets authenticated users send whatever HELO name their mail client chooses. The trailing permit effectively disables HELO checks for all port 588 connections; unauthenticated connections are still rejected by recipient or relay restrictions later in the SMTP transaction.
Whitelist specific hosts via check_helo_access
For a known internal host that sends a bare hostname, create a HELO access map. check_helo_access matches on the HELO string, not the source IP, so entries apply globally to all senders – including external bots:
# /etc/postfix/helo_access
internal-monitor-01 OK
Build the map and reference it before the reject:
postmap /etc/postfix/helo_access
postconf -e 'smtpd_helo_restrictions = permit_mynetworks, check_helo_access hash:/etc/postfix/helo_access, reject_non_fqdn_helo_hostname'
postfix reload
Place check_helo_access before the reject rules so exceptions take effect first.
Add internal hosts to mynetworks
If the sender is an internal host, placing it in mynetworks and using permit_mynetworks early in the restriction list is the cleanest fix. permit_mynetworks is IP-based, so it will not accidentally whitelist external clients sending the same HELO string.
# WARNING: this replaces mynetworks entirely. Include all existing entries.
postconf -e 'mynetworks = 127.0.0.0/8, 10.0.0.0/8, 192.168.1.0/24'
postconf -e 'smtpd_helo_restrictions = permit_mynetworks, reject_non_fqdn_helo_hostname'
postfix reload
Note that reject_unknown_helo_hostname will also reject names with no public DNS A or MX record. If you use both restrictions, permit_mynetworks must precede both.
Fix the client-side HELO name
The correct long-term fix for your own applications and servers is to configure them to send a proper FQDN in HELO. For Postfix itself, this is controlled by myhostname in main.cf. For applications, check the SMTP library configuration. Many libraries default to localhost or the machine’s short hostname.
Do not just remove the restriction
Removing reject_non_fqdn_helo_hostname stops the rejections but weakens your anti-spam posture on port 25. Legitimate mail servers send valid FQDNs in HELO. Keep the restriction on port 25 and handle false positives with targeted exceptions.
Prevention
- Keep HELO restrictions on port 25. This is where server-to-server mail arrives and where bot traffic concentrates.
- Override HELO restrictions on the submission port. Port 587 authenticated users should not be subject to FQDN HELO checks.
- Place
permit_mynetworksbefore reject rules. Internal hosts using short names should be exempted before the reject fires. - Monitor reject categories together. Read HELO, sender, and recipient reject counts as a group. A spike in HELO rejects alone tells a different story than a spike across all three categories.
- Watch for configuration drift. Package updates, automation runs, or manual edits can reorder restriction lists, placing a reject before an allow.
- Verify fail2ban coverage if you rely on it. The default fail2ban Postfix filter may not detect the
RCPT from unknownvariant of HELO rejection lines. If you depend on fail2ban to rate-limit rejected connections, verify your filter catches these lines or add a custom rule.
Metrics and signals to monitor
| Signal | Why it matters | Warning sign |
|---|---|---|
| Reject rate by category (HELO, sender, recipient) | Distinguishes attack patterns from config drift | Spike in HELO rejects alone suggests bot traffic; spike across all categories suggests broader policy issue |
| Reject rate by client IP | Identifies repeat offenders or compromised hosts | Single IP generating disproportionate rejects |
| Authenticated vs unauthenticated rejection ratio | Catches false positives on legitimate users | Any authenticated user being rejected by HELO rules on port 587 |
| Mail flow velocity (injected vs delivered) | Confirms whether rejections affect legitimate mail | Injection rate dropping while rejects rise could indicate legitimate mail being blocked |
| SASL auth success and failure rates | Correlates with submission port issues | Auth failures rising alongside HELO rejects on port 587 |
smtpd_helo_required setting | Ensures HELO checks cannot be bypassed | Setting changed to no after config edit |
How Netdata helps
- Per-second log parsing reveals reject rate spikes within seconds rather than minutes, which matters during botnet bursts that can generate thousands of rejected connections per minute.
- Reject categorization across HELO, sender, and recipient restrictions lets you correlate whether a HELO reject spike is isolated or part of a broader attack hitting multiple restriction layers.
- Mail flow velocity correlation (injection vs delivery rate) confirms whether rejections are noise from bots or are actually blocking legitimate mail.
- Queue depth tracking catches secondary effects. If HELO rejections are blocking legitimate clients, mail may accumulate in upstream or local retry queues.
- SASL authentication metrics on submission ports help you distinguish a bot hitting port 25 from an authenticated user being rejected on port 587, which requires a different fix.
Related guides
- Postfix active queue saturation: hitting qmgr_message_active_limit
- Postfix backscatter storm: bounces to forged senders and blocklisting
- Postfix IP blocklisted: deliverability collapse and sender reputation
- Postfix bounce rate spike: 5xx failures, bad address lists, and reputation risk
- Postfix check warnings: configuration drift and permission problems
- Postfix connection refused: blocked port 25 and rejected outbound delivery
- Postfix connection timed out: delivery deferrals to unreachable destinations
- Postfix content_filter backpressure: incoming queue growth when Amavis or Rspamd slows
- Postfix deferred queue growing: why mail piles up and how to drain it
- Postfix destination concurrency limit: tuning per-destination delivery
- Postfix DNS resolver failure: when a broken resolver defers mail to everyone
- Postfix double-bounce loop: MAILER-DAEMON mail multiplying in the queue






