ULTIMATE GUIDE: Setting up your server for reliable email delivery. PART I: Firewall

Firewall is software (or a hardware-software appliance) that controls which connections to the server are allowed and which are blocked. In the vast majority of modern Linux server distributions, some form of firewall is available out of the box.
Reliable email delivery to the recipient depends not only on the mail server itself, but also on correct DNS records and firewall configuration. If something is wrong with those, your messages are very likely to land in the Spam folder - or not be delivered at all.
This article outlines the key steps that will allow you to achieve nearly 100% reliable delivery of messages sent from your server. In Part 1, we will walk through the possible firewall-related issues in detail; in Part 2, you will find instructions for configuring DNS records.
The information in this article applies only to mail servers running on Linux distributions. Debian 12 and Rocky Linux 8.10 with the FASTPANEL control panel are used as examples.
Pre-requisites
Step 1. Install diagnostic tools
To check records and ports, you will need:
-
dig - for analyzing DNS records
-
lsof - for checking the mail server state
-
netcat - for checking port availability
-
whois - for checking the current DNS provider
Installation on Debian/Ubuntu:
sudo apt update && sudo apt install -y bind9-dnsutils netcat-openbsd lsof whois
CentOS/AlmaLinux/Rocky Linux:
sudo yum install -y bind-utils nmap-ncat lsof whois
Mail port availability
A port is a numerical identifier used to address different services on a server. Each service or application listens on its own port to exchange data over the network (for example, HTTP uses port 80, and SMTP uses port 25).
To perform all the following steps, connect to your server over SSH using the root user credentials or use sudo when running commands, as shown in the examples. You can learn how to connect to a server via SSH in our SSH article.
Step 2. Check mail server status
Before checking whether the ports are reachable over the network, first make sure that the mail servers are running and working correctly. Outgoing mail is usually handled by Exim or Postfix, and incoming mail by Dovecot.
To check that they are running, use the commands:
sudo lsof -i:25
sudo lsof -i:143
If the services are running, you will get an output similar to the following:
Port 25:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
exim 839 exim 4u IPv6 778199358 0t0 TCP *:smtp (LISTEN)
exim 839 exim 5u IPv4 778199359 0t0 TCP *:smtp (LISTEN)
If your server uses a different SMTP server, for example Postfix, you will see its exact name instead of exim in the first column. If needed, use that name in subsequent commands.
Port 143:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
dovecot 859 root 39u IPv4 778204692 0t0 TCP *:imap (LISTEN)
dovecot 859 root 40u IPv6 778204693 0t0 TCP *:imap (LISTEN)
This means everything is fine and you can proceed to the next step.
If the mail services are not running, you will get an empty output:
~ sudo lsof -i:25
~
~ sudo lsof -i:143
~
In this case, something is wrong and the mail services are not available. You can try starting them manually and then check their status:
Debian/Ubuntu:
systemctl restart exim4 dovecot
systemctl status exim4
systemctl status dovecot
CentOS/AlmaLinux/Rocky Linux:
systemctl restart exim dovecot
systemctl status exim
systemctl status dovecot
In the running state, the service status will look similar to:
Exim:
● exim.service - Exim Mail Transport Agent
Loaded: loaded (/usr/lib/systemd/system/exim.service; enabled; vendor preset: disabled)
Active: active (running) since Sun 2025-11-02 16:38:57 UTC; 57min ago
Main PID: 839 (exim)
Tasks: 1
Memory: 11.0M
CGroup: /system.slice/exim.service
└─839 /usr/sbin/exim -bd -q1h
Warning: Journal has been rotated since unit was started. Log output is incomplete or unavailable.
Dovecot:
● dovecot.service - Dovecot IMAP/POP3 email server
Loaded: loaded (/usr/lib/systemd/system/dovecot.service; enabled; vendor preset: disabled)
Active: active (running) since Sun 2025-11-02 16:38:58 UTC; 58min ago
Docs: man:dovecot(1)
https://doc.dovecot.org/
Main PID: 859 (dovecot)
Tasks: 5
Memory: 9.5M
CGroup: /system.slice/dovecot.service
├─ 859 /usr/sbin/dovecot -F
├─ 880 dovecot/anvil
├─ 881 dovecot/log
├─ 882 dovecot/config
└─1729 dovecot/stats
In this case, everything is fine and you can move on to the next step.
If something is wrong with the services, you will see output similar to this:
Exim:
● exim.service - Exim Mail Transport Agent
Loaded: loaded (/usr/lib/systemd/system/exim.service; enabled; vendor preset: disabled)
Active: inactive (dead) since Sun 2025-11-02 17:38:44 UTC; 3s ago
Process: 839 ExecStart=/usr/sbin/exim -bd -q${QUEUE} (code=exited, status=0/SUCCESS)
Main PID: 839 (code=exited, status=0/SUCCESS)
Dovecot:
● dovecot.service - Dovecot IMAP/POP3 email server
Loaded: loaded (/usr/lib/systemd/system/dovecot.service; enabled; vendor preset: disabled)
Active: inactive (dead) since Sun 2025-11-02 17:39:32 UTC; 3s ago
Docs: man:dovecot(1)
https://doc.dovecot.org/
Process: 2278 ExecStop=/usr/bin/doveadm stop (code=exited, status=0/SUCCESS)
Process: 859 ExecStart=/usr/sbin/dovecot -F (code=exited, status=0/SUCCESS)
Main PID: 859 (code=exited, status=0/SUCCESS)
There are many possible reasons why the services might be unavailable (errors in configuration files, accidentally deleted log files, lack of free disk space on the server, and much more). These aspects are outside the scope of this article.
If you run into this issue and want to investigate it yourself, we recommend our article on working with the system journal. Or contact your hosting provider’s support team for assistance. At kodu.cloud we work 24/7 and respond to requests within a few minutes.
Step 3. Checking the availability of mail ports from the global network
For email to work correctly, the following TCP ports must be accessible over the network:
-
25, 465, 587 - for sending mail (SMTP)
-
143, 993 - for receiving mail (IMAP)
You can check their availability using netcat:
nc -vz 1.2.3.4 25
Replace 1.2.3.4 with the actual IP address of your server.
If the port is open, you will get the following response:
Debian/Ubuntu:
Connection to 1.2.3.4 25 port [tcp/smtp] succeeded!
CentOS/AlmaLinux/Rocky Linux:
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Connected to 1.2.3.4:25.
Ncat: 0 bytes sent, 0 bytes received in 0.01 seconds.
If the port is closed, the response will be as follows:
Debian/Ubuntu:
nc: connect to 1.2.3.4 port 25 (tcp) failed: Connection refused
CentOS/AlmaLinux/Rocky Linux:
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Connection refused.
Run this command for all ports listed at the beginning of this section. If all of them are available, proceed to the second part of this article (coming soon!). If not, go to Step 4 to fix the issue.
Step 4. Opening access (optional)
Most often, servers use iptables or UFW as a firewall to protect against unwanted connections. Run the command:
sudo ufw status
If you receive the following response:
-bash: ufw: command not found
it means that iptables is used. If, however, the output starts with:
Status: active
It means that UFW is used.
Go to the relevant subsection of this step according to the firewall in use.
There is other software for managing the system firewall in Linux (nftables, firewalld, and others). These will not be covered in this article in order not to increase its size.
Iptables
In this section, all examples apply only to IPv4. If your server has an IPv6 address and you also need the mail ports to be available over IPv6, additionally repeat all commands using ip6tables instead of iptables.
To display all current rules, run:
sudo iptables -L -v -n --line-numbers
The output may differ depending on the set of rules added on the server. By default, there are no rules, but depending on the hosting configuration or operating system, some configuration may have already been performed. Let us look at two main cases that are often encountered when configuring a mail server.
- “Everything that is not forbidden is allowed” policy (policy ACCEPT)
In this case, the firewall, by default, allows all traffic until a rule explicitly blocking it is created. This policy is usually used by default.
Below is an example of a ruleset where SSH and mail ports are blocked:
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 51 5637 DROP 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp multiport dports 22
2 0 0 DROP 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp multiport dports 25,143,993,587,465
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 0 0 DROP 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp multiport dports 25,143,993,587,465
In this example:
-
Policy ACCEPT defines the default behavior: all connections are allowed unless stated otherwise.
-
Chain INPUT (incoming connections): new connections to port 22 (SSH) and to all standard mail ports (SMTP: 25, 465, 587; IMAP: 143; IMAPS: 993) are blocked.
-
Chain OUTPUT (outgoing connections): outgoing packets to the same ports are blocked.
The rule number is shown at the beginning of the line (num). If you need to delete, for example, the rule that blocks the mail ports in the INPUT chain (rule number 2), the command will be:
sudo iptables -D INPUT 2
Here:
-
-D - is the command to delete a rule.
-
INPUT is the chain from which the rule is being deleted (in this case, incoming traffic).
-
2 is the line number where the rule you want to delete is located.
Similarly, to remove the block on outgoing ports from this example, you need to run:
sudo iptables -D OUTPUT 1
The line numbers in the output of iptables -L -v -n --line-numbers may change if you add or delete other rules. Therefore, if you add new rules, the line numbers may shift. To avoid mistakes, always check the current line numbers before deleting a rule.
After this, to make sure everything is correct, run the command again:
iptables -L -v -n --line-numbers
The output should not contain rules that block the operation of the mail ports:
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 51 5637 DROP 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp multiport dports 22
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
If the rules are still present, check the correctness of the commands you entered and the line numbers.
After changing the firewall rules, be sure to save the changes; otherwise they will be lost after a reboot. To save the current iptables rules, run:
Debian/Ubuntu:
iptables-save > /etc/iptables/rules.v4
CentOS/AlmaLinux/Rocky Linux:
iptables-save > /etc/sysconfig/iptables
After that, test the availability of the mail ports again, as described in Step 3. If they are still unavailable, proceed to Step 5. If everything is in order, proceed to the second part of this article (coming soon!).
2. “Block everything that is not explicitly allowed” policy (policy DROP)
This type of configuration is much stricter. All ports and protocols are blocked by default, and only explicit allow rules for specific ports let traffic through. This is a more secure and recommended approach for servers, especially if you want to minimize risks.
An example of rules for such a configuration:
Chain INPUT (policy DROP 306 packets, 17962 bytes)
num pkts bytes target prot opt in out source destination
1 92 12474 ACCEPT 0 -- * * 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
2 466 32947 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
Chain OUTPUT (policy DROP 197 packets, 29192 bytes)
num pkts bytes target prot opt in out source destination
1 44 10332 ACCEPT 0 -- * * 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
In this example:
- Chain INPUT (incoming connections): the policy is DROP, that is, all packets are blocked by default, except for those for which there are allow rules.
Rule 1: ACCEPT state RELATED,ESTABLISHED - allows incoming packets that relate to existing connections or are associated with them. This is important for the server to correctly receive responses to outgoing connections (for example, SSH, mail, DNS queries).
Rule 2: ACCEPT tcp dpt:22 - allows new incoming connections on port 22 (SSH).
- Chain OUTPUT (outgoing connections): the policy is DROP, that is, all outgoing packets are blocked by default.
Rule 1: ACCEPT state RELATED,ESTABLISHED - allows outgoing packets that relate to existing connections. This enables the server to respond to client requests (for example, SSH, mail connections) and maintain a stable connection.
So we see that there are no allow rules for the mail ports. To add them, run the following commands:
sudo iptables -A INPUT -p tcp -m multiport --dports 25,465,587,143,993 -m state --state NEW,ESTABLISHED -j ACCEPT
sudo iptables -A OUTPUT -p tcp -m multiport --dports 25,465,587,143,993 -m state --state NEW,ESTABLISHED -j ACCEPT
After that, save the rules:
Debian/Ubuntu:
iptables-save > /etc/iptables/rules.v4
CentOS/AlmaLinux/Rocky Linux:
iptables-save > /etc/sysconfig/iptables
Then test the availability of the mail ports again, as described in Step 3. If they are still unavailable, proceed to Step 5. If everything is in order, proceed to the second part of this article (coming soon!).
UFW (Uncomplicated Firewall)
On some servers, UFW is used instead of iptables - a simplified interface for configuring the firewall. It is suitable for basic mail server configuration and is often found on Ubuntu and Debian.
To see whether UFW is enabled and which rules are active, run:
sudo ufw status verbose
Example output:
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing)
New profiles: skip
Here:
-
Default: deny (incoming) - by default, all incoming connections are blocked.
-
Default: allow (outgoing) - by default, all outgoing connections are allowed.
For the mail server to work correctly, you need to open the standard ports. Run the following commands in sequence:
sudo ufw allow 25/tcp
sudo ufw allow 465/tcp
sudo ufw allow 587/tcp
sudo ufw allow 143/tcp
sudo ufw allow 993/tcp
If you also have outgoing restrictions, you need to allow SMTP ports for outgoing mail:
sudo ufw allow out 25/tcp
sudo ufw allow out 465/tcp
sudo ufw allow out 587/tcp
After adding the rules, you can check them again:
sudo ufw status numbered
You will see a list of rules with numbers. If you need to delete a rule, use the number from the output:
sudo ufw delete 3
In UFW, rules are saved automatically and will remain in effect after a reboot.
Then test the availability of the mail ports again, as described in Step 3. If they are still unavailable, proceed to Step 5. If everything is in order, proceed to the second part of this article (coming soon!).
Step 5. The firewall is configured correctly, but the mail ports are still unavailable (optional)
Such behavior indicates that the mail ports are blocked not at the level of your server, but on your hosting provider’s equipment. In this case, create a support ticket and ask them to unblock these ports for your server. If this is not possible, the only solution will be to change the hosting provider.
At kodu.cloud we do not restrict the operation of mail ports in any way. Our rules prohibit bulk mailings, but for bona fide customers email is available in full. You do not even need to configure a mail server, because all our clients are provided free access to the FASTPANEL control panel with an extended license. Order a server with the control panel and start using mail on your own domain in just a few minutes.
The following steps for proper DNS record configuration will be covered in the second part of this article (coming soon!).
