Basic iptables rules for a Minecraft server
In this guide you will find a basic iptables ruleset that can be useful for a Minecraft server.
- Full root access to your machine (this guide is not intended for panels such as Pterodactyl or Multicraft).
- The
iptablespackage. - The
iptables-persistentpackage. - The
ipsetandipset-persistentpackages (optional).
$ sudo apt update
$ sudo apt install iptables
$ sudo apt install iptables-persistent
$ sudo apt install ipset ipset-persistent
$ sudo systemctl enable netfilter-persistent
On modern Debian-family systems, nftables is the replacement framework for iptables, and iptables often works as the nft-compatible frontend. This guide still uses iptables syntax intentionally.
If you are reading this guide, you probably want a simple starting point for firewalling your server and protecting the services running on it.
It is highly recommended to bind internal services to 127.0.0.1 whenever possible, such as MySQL or backend servers. Avoid exposing unnecessary web services such as Apache or NGINX unless you actually need them.
If IPv6 is enabled on your host, use an equivalent ip6tables ruleset such as the example shown below. If you are not going to filter IPv6, disable it intentionally rather than leaving it unmanaged.
If you are applying firewall changes over SSH on a remote host, consider using iptables-apply. It can roll back the rules automatically if the new ruleset disconnects your session.
Use SSH keys instead of passwords. This helps reduce brute-force attempts and makes it easier to manage access for trusted users.
For additional protection, you can use ipset to create a list of trusted IPs that are allowed to connect to the SSH port.
Note that you can change your SSH port by editing sshd_config. If you do, replace 22 in the examples below with your custom port.
All externally accessible services, such as your Minecraft server, should run only under a non-sudo user.
Do not run externally accessible services as root or as any user with sudo privileges.
We aim to prevent your server from being bypassed by UUID spoofing.
Minecraft Servers
Set every Minecraft backend server (Spigot servers) to bind to 127.0.0.1 in server.properties (server-ip).
Set only the proxy server (Bungee/Waterfall/Velocity) to bind to 0.0.0.0 in config.yml (host: 0.0.0.0:25565).
In the proxy config.yml, set backend server addresses to 127.0.0.1.
For example:
servers:
hub:
motd: 'Hub Server'
address: 127.0.0.1:25566
restricted: falsePolicies
This guide uses a DROP policy on the INPUT and FORWARD chains.
By default, these chain policies are usually set to ACCEPT, which means incoming traffic is allowed unless a rule says otherwise.
After changing the policy to DROP, only traffic that matches the rules you add will be accepted.
Ports
I assume that you only want to expose SSH and the required service ports on your server, so we will open only the ports that are needed.
The order of the rules matters because iptables evaluates chains from top to bottom, so add them in the same order shown below.
$ sudo iptables -A INPUT -i lo -j ACCEPT
(The loopback interface is also used if your application server connects to a database or another local service through localhost, so it should usually be allowed.)
READ CAREFULLY
To allow return traffic for outgoing connections initiated by the server itself, we can add this rule, but if
you are having problems with a DDoS attack quickly filling the conntrack table,
you can switch to option number 2.
Option 1:
$ sudo iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
Option 2:
$ sudo cat /proc/sys/net/ipv4/ip_local_port_range
This will show the kernel port range that we have to allow to let the kernel work without problems.
Note: `A1` is the first number, and `A2` is the second number.
$ sudo iptables -A INPUT -p tcp -m tcp --dport A1:A2 -j ACCEPT
$ sudo iptables -A INPUT -p udp -m udp --dport A1:A2 -j ACCEPT
(Replace `A1` and `A2` with the port numbers displayed by the `cat` command above.)
$ sudo iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
(Sometimes it can be useful to log this type of packet, but dropping it is often fine.)
$ sudo iptables -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
(This rule allows traffic to SSH. Change 22 if you use a different SSH port.)
If you want to rate-limit new connection attempts per source IP, use hashlimit rather than connlimit.
$ sudo iptables -A INPUT -p tcp --syn --dport 25565 -m hashlimit --hashlimit-name input_rate --hashlimit-mode srcip --hashlimit-upto 3/second --hashlimit-burst 3 -j ACCEPT
$ sudo iptables -A INPUT -p tcp --syn --dport 25565 -j DROP
(This example allows up to 3 new connection attempts per second per source IP, with a burst of 3.)
$ sudo iptables -A INPUT -p icmp -m icmp --icmp-type 8 -m limit --limit 1/sec --limit-burst 1 -j ACCEPT
(OPTIONAL - This rule will allow ICMP echo requests with a limit of 1/sec.)
$ sudo iptables -P INPUT DROP
$ sudo iptables -P FORWARD DROP
(As stated above, we have now configured the firewall to accept connections only to the ports and services we want.
Changing the policy to DROP will drop all connections that are not explicitly allowed.)
IPv6 Example
If IPv6 is enabled on your host, apply an equivalent ip6tables ruleset as well.
This example keeps stateful traffic, SSH, the service port, and essential ICMPv6 traffic working:
$ sudo ip6tables -A INPUT -i lo -j ACCEPT
$ sudo ip6tables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
$ sudo ip6tables -A INPUT -m conntrack --ctstate INVALID -j DROP
$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type destination-unreachable -j ACCEPT
$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type packet-too-big -j ACCEPT
$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type time-exceeded -j ACCEPT
$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type parameter-problem -j ACCEPT
$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type router-solicitation -j ACCEPT
$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type router-advertisement -j ACCEPT
$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbour-solicitation -j ACCEPT
$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbour-advertisement -j ACCEPT
$ sudo ip6tables -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
$ sudo ip6tables -A INPUT -p tcp --syn --dport 25565 -m hashlimit --hashlimit-name input_rate_v6 --hashlimit-mode srcip --hashlimit-upto 3/second --hashlimit-burst 3 -j ACCEPT
$ sudo ip6tables -A INPUT -p tcp --syn --dport 25565 -j DROP
$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type echo-request -m limit --limit 1/sec --limit-burst 1 -j ACCEPT
$ sudo ip6tables -P INPUT DROP
$ sudo ip6tables -P FORWARD DROP(ICMPv6 is more important to IPv6 operation than ICMP is to IPv4, so do not treat it as optional in the same way.)
$ sudo netfilter-persistent save
(This command saves the current iptables and ip6tables rules and makes them persistent so they are restored after a reboot.)