Okay, I guess we can't figure this out in one go. So I'll edit my answer as we go. The data so far indicates no reason why iptables
would not work. Looking at the (potentially) relevant lines of the lsof
output I asked you for and you provided, you should only see the ports you mentioned:
Only select ports such as 22, 25 & 80 are open
open and others closed. However, you should see open 21, 22, 80 and 9312. There are no services listening on the other ports (trimmed down version of the sudo lsof -i -s tcp:listen
output):
sshd 960 root 3u IPv4 564 0t0 TCP *:ssh (LISTEN)
sshd 960 root 4u IPv6 566 0t0 TCP *:ssh (LISTEN)
exim4 2001 Debian-exim 5u IPv4 11968 0t0 TCP mysite.com:smtp (LISTEN)
searchd 2025 root 7u IPv4 14510 0t0 TCP *:9312 (LISTEN)
proftpd 2041 proftpd 2u IPv6 11974 0t0 TCP *:ftp (LISTEN)
apache2 2103 root 4u IPv6 11992 0t0 TCP *:http (LISTEN)
apache2 ???? www-data 4u IPv6 11992 0t0 TCP *:http (LISTEN)
Alright, having that established (i.e. they are open, but nothing is listening). We should look into how you are trying to determine whether the port is open or not. The normal way is to do an nmap
run from the outside world. Nmap will also be able to distinguish - depending on the circumstances - between open and filtered and closed.
One thing that came to mind is that if you use tcpwrappers, /etc/hosts.deny
and /etc/hosts.allow
will also play a role without iptables
interfering.
Simplification possible
Furthermore your rules can be simplified.
Replace:
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
by:
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
unless you intend to make use of connection tracking down the road.
All OUTPUT
(filter
) rules can be removed, since they don't do anything as long as the policy of that chain is set to ACCEPT
.
You probably want to add a rule for DNS on UDP, as UDP is the default. It would look like this:
-A INPUT -p udp -m udp --dport 53 -j ACCEPT
Now all the port names can be put into a more readable form by using the names from /etc/services
and thus
-A INPUT -p tcp -m tcp --dport 20 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 21 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 25 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 53 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 143 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 993 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 3306 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 4444 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 5900 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 9312 -j ACCEPT
-A INPUT -p udp -m udp --dport 53 -j ACCEPT # <-- added this one
-A INPUT -p udp -m udp --dport 5900 -j ACCEPT
can be condensed down to the more readable two-liner:
-A INPUT -p tcp -m multiport --dports ftp-data,ftp,ssh,smtp,domain,www,imap2,https,imaps,mysql,4444,5900,9312
-A INPUT -p udp -m multiport --dports domain,5900
with help of the multiport
module
All of the rules in a format suitable for iptables-restore
then become:
*filter
:INPUT ACCEPT
:FORWARD ACCEPT
:OUTPUT ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH
-A INPUT -m recent --update --seconds 90 --hitcount 4 --rttl --name SSH -j DROP
-A INPUT -p tcp -m multiport --dports ftp-data,ftp,ssh,smtp,domain,www,imap2,https,imaps,mysql,4444,5900,9312
-A INPUT -p udp -m multiport --dports domain,5900
-A INPUT -p icmp -m limit --limit 10/sec -j ACCEPT
-A INPUT -j DROP
-A FORWARD -p icmp -m limit --limit 10/sec -j ACCEPT
COMMIT
Piping those rules into an invocation of iptables-restore --test
will show you any syntax errors, by the way. This is generally very useful for all cases where you type your rules into a format suitable for iptables-restore
or iptables-apply
.
Bonus: Variations
You can use ipset(8)
(apt-get install ipset
) to maintain the port list as well. This can also be used for other things such as locking out whole geographical areas if you have a set of (CIDR) net specifications for those areas. See this.
Of course you can also match against IP sets as source or destination address and address/port combinations. For more information turn to the manual (man ipset
) and check out the set
module and the SET
target (way down from the one before) descriptions in man iptables-extensions
.
Last but not least: if you have IPv6 connectivity, you might want to add prefix -6
and -4
respectively before lines on which protocol specific rules are given. This way you can use a single file to store your rules for both ip6tables-restore
and iptables-restore
. Rules shared between both should not carry the prefix.
For example the condensed rules at the time of this writing could be used for both IPv4 and IPv6. Usually only addresses, net masks and such stuff will require that you split the rules between IPv4 and IPv6. Also: IP sets can only be either IPv4 (default) or IPv6 (ipset create foo family inet6
), which means that such sets, due to the different names, will require split rules as well.
You also asked implicitly:
:OUTPUT ACCEPT [19969:30829963]# <--- not sure where this came from?
the numbers between the brackets are the counters for the chain. When using iptables-restore
(or its IPv6 counterpart) these are optional. Before the colon is the number of packets, after it the number of bytes. If you run iptables-save
with the -c
switch you will get the corresponding counters per rule.
Best Answer
The easy way is to use
iptables-persistent
.Install
iptables-persistent
:After it's installed, you can save/reload iptables rules anytime:
Ubuntu 16.04 Server
The installation as described above works without a problem, but the two commands for saving and reloading above do not seem to work with a 16.04 server. The following commands work with that version: