Error Medic

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.

Last updated:
Last verified:
2,388 words
Key Takeaways
  • 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
Fix Approaches Compared
MethodWhen to UseTimeRisk
Restart Postfix serviceService is stopped or has crashed< 1 minLow — no config changes required
Set inet_interfaces = all in main.cfPostfix runs but binds only to 127.0.0.12–5 minLow — requires postfix reload; configure relay restrictions first
Open OS firewall port 25/587External hosts refused; local telnet succeeds2–5 minMedium — exposes SMTP port to internet
Resolve port 25 conflictpostfix/master: fatal: bind port 25: Address already in use5–15 minMedium — stopping the conflicting process may affect other services
Adjust SELinux/AppArmor policyAVC denial log entries appear alongside connection errors10–20 minHigh — incorrect policy changes reduce security posture
Request ISP/cloud port unblockAll OS checks pass but external delivery still failsHours–daysNone — 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.com followed by /var/log/mail.log showing connection 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 ECONNREFUSED when connecting to localhost:25 or localhost:587
  • Manual test: telnet localhost 25 returns Trying 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 = all on a public IP, ensure smtpd_relay_restrictions and mynetworks are 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:

  1. 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.conf and consider adding a local caching resolver.

  2. 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 -50

  3. Large queue backlog: postqueue -p | wc -l reveals queue size. Force immediate retry of all queued messages: postqueue -f

  4. smtp_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.

  5. 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

bash
#!/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. ==="
E

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

Related Articles in Postfix

Explore More Linux Sysadmin Guides