Postfix 'Connection Refused' Error: Complete Troubleshooting Guide
Fix Postfix 'connection refused' errors step by step: check service status, inet_interfaces, firewall rules, and port conflicts to restore mail delivery.
- Postfix 'connection refused' means the TCP handshake was actively rejected — the service is stopped, bound only to loopback, or blocked by a firewall
- The inet_interfaces = loopback-only setting silently prevents all external SMTP connections even when Postfix is fully running
- Cloud provider security groups and ISP-level port 25 blocks are independent of OS-level firewall rules and are frequently overlooked
- Postfix slow delivery is usually caused by DNS lookup delays, greylisting retries, or a swollen mail queue — not a connection issue
- Quick triage sequence: systemctl status postfix → ss -tlnp | grep master → postconf inet_interfaces → firewall check → telnet localhost 25
| Method | When to Use | Time | Risk |
|---|---|---|---|
| Restart Postfix service | Service is stopped or has crashed | < 1 min | Low — no config changes required |
| Set inet_interfaces = all in main.cf | Postfix runs but binds only to 127.0.0.1 | 2–5 min | Low — requires postfix reload; configure relay restrictions first |
| Open OS firewall port 25/587 | External hosts refused; local telnet succeeds | 2–5 min | Medium — exposes SMTP port to internet |
| Resolve port 25 conflict | postfix/master: fatal: bind port 25: Address already in use | 5–15 min | Medium — stopping the conflicting process may affect other services |
| Adjust SELinux/AppArmor policy | AVC denial log entries appear alongside connection errors | 10–20 min | High — incorrect policy changes reduce security posture |
| Request ISP/cloud port unblock | All OS checks pass but external delivery still fails | Hours–days | None — administrative/policy change only |
Understanding the Postfix 'Connection Refused' Error
When Postfix or a mail client reports connect to mail.example.com[203.0.113.10]:25: Connection refused, the TCP three-way handshake was actively terminated — the destination host replied with a RST packet rather than a SYN-ACK. This is categorically different from a timeout (firewall silently drops packets) or a DNS failure (NXDOMAIN or servfail). A RST means something is listening on the network stack but actively denying access, or nothing is listening at all.
This error surfaces in several contexts:
- Sending mail via CLI:
echo 'test' | mail -s 'test' user@example.comfollowed by/var/log/mail.logshowingconnection refused - Remote MTA delivery log entry:
postfix/smtp[14221]: connect to smtp.example.com[93.184.216.34]:25: Connection refused - Application SMTP integration: Your app's SMTP client throws
ECONNREFUSEDwhen connecting tolocalhost:25orlocalhost:587 - Manual test:
telnet localhost 25returnsTrying 127.0.0.1... telnet: connect to address 127.0.0.1: Connection refused
Step 1: Confirm the Postfix Service Is Running
This is always the first check. A stopped or crashed Postfix master process produces an identical 'connection refused' error regardless of all other configuration.
systemctl status postfix
A healthy response shows Active: active (running). If you see inactive (dead), failed, or activating (auto-restart), start or restart the service:
systemctl start postfix
# or if running but misbehaving:
systemctl restart postfix
Also verify that Postfix is enabled to survive reboots:
systemctl is-enabled postfix
# If 'disabled': systemctl enable postfix
After starting, immediately validate with a banner check:
telnet localhost 25
# Expected: 220 hostname.example.com ESMTP Postfix
Step 2: Verify Postfix Is Listening on the Correct Interface
Postfix can be running but bound only to 127.0.0.1 (loopback). All external connections will be refused while local connections succeed — a common source of confusion. Inspect the bound addresses:
ss -tlnp | grep master
Healthy output for a server accepting external SMTP:
LISTEN 0 100 0.0.0.0:25 0.0.0.0:* users:(("master",pid=1234,fd=13))
If you see only 127.0.0.1:25 or [::1]:25, Postfix is restricting itself to loopback. The cause is almost always the inet_interfaces directive in /etc/postfix/main.cf:
postconf inet_interfaces
# Returns: inet_interfaces = loopback-only
Change it to accept connections on all interfaces:
postconf -e 'inet_interfaces = all'
postfix reload
Security note: Before setting
inet_interfaces = allon a public IP, ensuresmtpd_relay_restrictionsandmynetworksare configured to prevent your server from becoming an open relay. At minimum:postconf -e 'smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination'
Step 3: Check for Port 25 Conflicts
If Postfix fails to start and /var/log/mail.log contains fatal: bind port 25: Address already in use, another process owns the port. Identify it:
ss -tlnp 'sport = :25'
# or
lsof -i TCP:25 -n -P
Common culprits: a stale Postfix instance with a locked PID file, sendmail, exim4, or a testing SMTP daemon. Stop the conflicting service, remove any stale PID file at /var/spool/postfix/pid/master.pid, then start Postfix.
On Debian/Ubuntu systems, sendmail and postfix may both be installed:
dpkg -l | grep -E 'sendmail|exim|postfix'
systemctl stop sendmail exim4 2>/dev/null; systemctl start postfix
Step 4: Inspect OS Firewall Rules
Even when Postfix binds to 0.0.0.0:25, the OS firewall can silently drop or actively reject inbound connections.
iptables/nftables:
iptables -L INPUT -n -v | grep -E '25|smtp'
# Add a rule if missing:
iptables -I INPUT -p tcp --dport 25 -j ACCEPT
iptables-save > /etc/iptables/rules.v4
firewalld (RHEL/CentOS/Fedora):
firewall-cmd --list-services
firewall-cmd --permanent --add-service=smtp
firewall-cmd --permanent --add-service=smtps
firewall-cmd --reload
ufw (Ubuntu/Debian):
ufw allow 25/tcp
ufw allow 587/tcp
ufw status numbered
Cloud provider layer: AWS EC2, GCP Compute Engine, Azure VMs, and DigitalOcean Droplets all have a second, separate firewall (security groups, VPC firewall rules, NSGs) that operates independently of the OS firewall. A perfectly configured OS firewall will not help if the cloud security group blocks port 25. Check your cloud console and ensure inbound TCP 25 (and 587 for submission) are permitted from the appropriate source ranges.
Note: Many cloud providers block outbound port 25 by default to prevent spam. AWS requires submitting a form to lift this restriction; GCP blocks it by default on all projects.
Step 5: Check SELinux and AppArmor Denials
On SELinux-enabled systems, a type enforcement violation can silently prevent Postfix from binding to a non-standard port or making outbound connections:
audit2why < /var/log/audit/audit.log 2>/dev/null | grep -A5 postfix
# or for recent denials:
audit2allow -a | grep postfix
For AppArmor (Ubuntu/Debian):
journalctl -k | grep 'apparmor.*postfix'
dmesg | grep -i apparmor | grep postfix
If denials appear, either generate a custom policy module with audit2allow or temporarily set SELinux to permissive to confirm it is the root cause:
setenforce 0 # Permissive mode — confirm the cause, then re-enable
Step 6: Read the Postfix Logs
Postfix writes to /var/log/mail.log (Debian/Ubuntu) or /var/log/maillog (RHEL/CentOS/Fedora). Always read the logs after any configuration change:
tail -100 /var/log/mail.log | grep -iE 'fatal|error|refused|denied'
# Live monitoring:
journalctl -u postfix -f
Key log patterns and their meanings:
| Log Message | Most Likely Cause |
|---|---|
fatal: bind port 25: Address already in use |
Port 25 conflict with another process |
warning: inet_protocols: disabling IPv6 |
IPv6 configured but unavailable in this environment |
connect to X.X.X.X:25: Connection refused |
Remote MTA is down or blocking your IP |
lost connection after CONNECT from |
TLS negotiation failure or banner timeout |
NOQUEUE: reject: RCPT ... Connection refused |
Downstream filter (Amavis, Spamassassin) not running |
fatal: open /etc/postfix/main.cf: Permission denied |
File permission problem after update or restore |
Step 7: Validate Configuration Before Restarting
After editing /etc/postfix/main.cf or any lookup table, always run the built-in checker before applying changes:
postfix check
# Dump all non-default active settings:
postconf -n
postfix check catches missing files, bad permissions on queue directories, and unknown parameter names. A clean run produces no output. Any output indicates a problem that must be fixed before restarting.
Diagnosing Postfix Slow Delivery
Slow delivery (Postfix running, not connection-refused, but mail is delayed) has distinct causes:
DNS lookup delays: Postfix performs MX and A record lookups per delivery attempt. Test:
dig +time=2 MX destination.com— if slow, check/etc/resolv.confand consider adding a local caching resolver.Greylisting at destination: The receiving server temporarily rejects with
450 4.1.1 Please try again later. Postfix retries on its default schedule (first retry: 5 minutes). Inspect the queue:mailq | head -50Large queue backlog:
postqueue -p | wc -lreveals queue size. Force immediate retry of all queued messages:postqueue -fsmtp_connect_timeout too low: On high-latency paths the default 30-second timeout may be inadequate. Increase:
postconf -e 'smtp_connect_timeout = 60s'then reload.Content filter bottleneck: If Postfix passes mail through Amavis or Spamassassin, a slow or backed-up filter appears as Postfix slowness. Check:
systemctl status amavis spamassassin
Frequently Asked Questions
#!/usr/bin/env bash
# postfix-diagnose.sh — Connection Refused / Not Working Diagnostic
# Run as root on the mail server. Safe, read-only checks only.
set -uo pipefail
HR='------------------------------------------------------------'
echo "$HR"
echo '1. POSTFIX SERVICE STATUS'
echo "$HR"
systemctl status postfix --no-pager -l 2>&1 | head -20
echo ""
echo "$HR"
echo '2. LISTENING PORTS (postfix master process)'
echo "$HR"
ss -tlnp | grep master || echo 'WARNING: postfix master not found in ss output — service may be down'
echo ""
echo "$HR"
echo '3. INET_INTERFACES AND INET_PROTOCOLS'
echo "$HR"
postconf inet_interfaces
postconf inet_protocols
echo ""
echo "$HR"
echo '4. POSTFIX CONFIGURATION CHECK'
echo "$HR"
postfix check 2>&1 && echo 'OK: postfix check passed with no errors'
echo ""
echo "$HR"
echo '5. PORT CONFLICT CHECK (who owns :25)'
echo "$HR"
ss -tlnp 'sport = :25' 2>/dev/null || echo 'Nothing found on port 25'
ss -tlnp 'sport = :587' 2>/dev/null || echo 'Nothing found on port 587 (submission)'
echo ""
echo "$HR"
echo '6. LOCAL SMTP BANNER TEST (localhost:25 and :587)'
echo "$HR"
for PORT in 25 587; do
if timeout 5 bash -c "</dev/tcp/localhost/$PORT" 2>/dev/null; then
echo "OK: localhost:$PORT is open"
else
echo "FAIL: localhost:$PORT is not reachable"
fi
done
echo ""
echo "$HR"
echo '7. OS FIREWALL CHECK'
echo "$HR"
if systemctl is-active --quiet firewalld 2>/dev/null; then
echo '--- firewalld ---'
firewall-cmd --list-services 2>&1
firewall-cmd --list-ports 2>&1
elif command -v ufw &>/dev/null && ufw status | grep -q 'Status: active'; then
echo '--- ufw ---'
ufw status verbose 2>&1 | grep -E '25|587|465|smtp|SMTP|Postfix' || echo 'No explicit SMTP rules in ufw'
else
echo '--- iptables ---'
iptables -L INPUT -n -v 2>&1 | grep -E '25|587|smtp|SMTP' || echo 'No explicit SMTP rules in iptables INPUT chain'
fi
echo ""
echo "$HR"
echo '8. SELINUX STATUS AND RECENT POSTFIX DENIALS'
echo "$HR"
if command -v getenforce &>/dev/null; then
getenforce
command -v ausearch &>/dev/null && ausearch -m AVC -ts recent 2>/dev/null | grep postfix | tail -10 || true
else
echo 'SELinux not present on this system'
fi
echo ""
echo "$HR"
echo '9. RECENT MAIL LOG ERRORS (last 50 error lines)'
echo "$HR"
for LOGFILE in /var/log/mail.log /var/log/maillog; do
if [ -f "$LOGFILE" ]; then
grep -iE 'fatal|error|refused|denied|rejected|warning' "$LOGFILE" | tail -50
break
fi
done
if [ ! -f /var/log/mail.log ] && [ ! -f /var/log/maillog ]; then
journalctl -u postfix --since '2 hours ago' | grep -iE 'fatal|error|refused|denied' | tail -50
fi
echo ""
echo "$HR"
echo '10. MAIL QUEUE STATUS'
echo "$HR"
mailq 2>&1 | tail -10
echo ""
echo "$HR"
echo '11. NON-DEFAULT POSTFIX SETTINGS (postconf -n)'
echo "$HR"
postconf -n
echo ""
echo "$HR"
echo '12. DNS MX SELF-CHECK'
echo "$HR"
MYDOMAIN=$(postconf -h mydomain 2>/dev/null)
echo "mydomain = $MYDOMAIN"
dig +short +time=5 MX "$MYDOMAIN" 2>/dev/null || echo 'dig not available or MX lookup failed'
echo ""
echo "=== Diagnostic complete. Review any FAIL or WARNING lines above. ==="Error Medic Editorial
The Error Medic Editorial team is composed of senior Linux systems engineers and SREs with over a decade of experience managing production mail infrastructure. Contributors hold RHCE, LFCS, and AWS certifications and have debugged Postfix deployments across cloud-native, on-premises bare metal, and hybrid environments handling millions of messages per day.
Sources
- http://www.postfix.org/BASIC_CONFIGURATION_README.html
- http://www.postfix.org/DEBUG_README.html
- https://serverfault.com/questions/58141/postfix-connection-refused
- https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-postfix-as-a-send-only-smtp-server-on-ubuntu-22-04
- https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html/deploying_mail_servers/configuring-postfix_deploying-mail-servers
- https://wiki.debian.org/Postfix