Photo by Paul Gilmore on Unsplash

How to protect against DDoS with UFW

Bob Kfir
Bob Kfir
Feb 1, 2018 · 6 min read

Ever need to protect your server using UFW from DDoS attacks?

What is a DDoS attack?

Simply put, a DDoS, or Distributed Denial of Service, is when many devices from many separate IP addresses send large amounts of traffic to your server in an attempt to take it down. These are slightly different from DoS attacks, in which only one device is attacking. DoS attacks can be mitigated rather easily because of the fact that they’re only coming from one IP address. But, how do you defend against DDoS attacks?

The answer? iptables

The majority of tutorials on DDoS protection give you a bunch of iptables rules. You’re probably wondering, why not just use those rules along with UFW. While you _technically _can, UFW rules generally run before iptables rules, so if a port is open with UFW, then no other iptables rule will affect that traffic. I will be using JavaPipe’s rules. They work amazing(I’ve tested them with a small DoS attack, and my system practically didn’t even notice), but don’t work if UFW is installed.

Enter before.rules

More or less, the only iptables rules that affect UFW are what’s in /etc/ufw/before*.rules. These rules are added before the standard UFW rules(generated with the UFW command). So, add the following to /etc/ufw/before.rules and /etc/ufw/before6.rules:

#ANTI DDOS
*mangle
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A PREROUTING -m conntrack --ctstate INVALID -j DROP
-A PREROUTING -p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j DROP
-A PREROUTING -p tcp -m conntrack --ctstate NEW -m tcpmss ! --mss 536:65535 -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG NONE -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN FIN,SYN -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags SYN,RST SYN,RST -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,RST FIN,RST -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,ACK FIN -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags ACK,URG URG -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,ACK FIN -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags PSH,ACK PSH -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,SYN,RST,PSH,ACK,URG -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG NONE -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,PSH,URG -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,SYN,PSH,URG -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,SYN,RST,ACK,URG -j DROP
-A PREROUTING -m conntrack --ctstate INVALID -j DROP
-A PREROUTING -p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j DROP
-A PREROUTING -p tcp -m conntrack --ctstate NEW -m tcpmss ! --mss 536:65535 -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG NONE -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN FIN,SYN -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags SYN,RST SYN,RST -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,RST FIN,RST -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,ACK FIN -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags ACK,URG URG -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,ACK FIN -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags PSH,ACK PSH -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,SYN,RST,PSH,ACK,URG -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG NONE -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,PSH,URG -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,SYN,PSH,URG -j DROP
-A PREROUTING -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,SYN,RST,ACK,URG -j DROP
-A PREROUTING -p tcp -m state --state NEW -m recent --set --name DEFAULT --rsource
-A PREROUTING -p tcp -m state --state NEW -m recent --update --seconds 10 --hitcount 25 --name DEFAULT --rsource -j DROP
-A PREROUTING -p icmp -m limit --limit 2/sec -j ACCEPT
-A PREROUTING -p icmp -j DROP
COMMIT
# Completed on Sun Jan 14 21:47:45 2018
# Generated by ip6tables-save v1.6.0 on Sun Jan 14 21:47:45 2018
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -p tcp -m connlimit --connlimit-above 25 --connlimit-mask 128 --connlimit-saddr -j REJECT --reject-with tcp-reset
-A INPUT -p tcp -m tcp --tcp-flags RST RST -j DROP
-A INPUT -p tcp -m conntrack --ctstate NEW -j DROP
COMMIT

In case you’re wondering, these are essentially an iptables-save dump from a server after I added all the relevant rules.

A bit more…

Now just run

sudo ufw reload

and the settings should take affect. Not everything on JavaPipe is on here, because I use a VPN, so the internal IPs must work, so I didn’t use the anti-false IP rules. I also left ICMP unblocked, because I use ping every now and then to see if my server has an SSH problem, or a network problem. Also, they recommend you add the following to /etc/sysctl.conf:

kernel.printk = 4 4 1 7 
kernel.panic = 10
kernel.sysrq = 0
kernel.shmmax = 4294967296
kernel.shmall = 4194304
kernel.core_uses_pid = 1
kernel.msgmnb = 65536
kernel.msgmax = 65536
vm.dirty_ratio = 80
vm.dirty_background_ratio = 5
fs.file-max = 2097152
net.core.netdev_max_backlog = 262144
net.core.rmem_default = 31457280
net.core.rmem_max = 67108864
net.core.wmem_default = 31457280
net.core.wmem_max = 67108864
net.core.somaxconn = 65535
net.core.optmem_max = 25165824
net.ipv4.neigh.default.gc_thresh1 = 4096
net.ipv4.neigh.default.gc_thresh2 = 8192
net.ipv4.neigh.default.gc_thresh3 = 16384
net.ipv4.neigh.default.gc_interval = 5
net.ipv4.neigh.default.gc_stale_time = 120
net.netfilter.nf_conntrack_max = 10000000
net.netfilter.nf_conntrack_tcp_loose = 0
net.netfilter.nf_conntrack_tcp_timeout_established = 1800
net.netfilter.nf_conntrack_tcp_timeout_close = 10
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 10
net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 20
net.netfilter.nf_conntrack_tcp_timeout_last_ack = 20
net.netfilter.nf_conntrack_tcp_timeout_syn_recv = 20
net.netfilter.nf_conntrack_tcp_timeout_syn_sent = 20
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 10
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.ip_local_port_range = 1024 65000
net.ipv4.ip_no_pmtu_disc = 1
net.ipv4.route.flush = 1
net.ipv4.route.max_size = 8048576
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.tcp_congestion_control = htcp
net.ipv4.tcp_mem = 65536 131072 262144
net.ipv4.udp_mem = 65536 131072 262144
net.ipv4.tcp_rmem = 4096 87380 33554432
net.ipv4.udp_rmem_min = 16384
net.ipv4.tcp_wmem = 4096 87380 33554432
net.ipv4.udp_wmem_min = 16384
net.ipv4.tcp_max_tw_buckets = 1440000
net.ipv4.tcp_tw_recycle = 0
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_max_orphans = 400000
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_rfc1337 = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_synack_retries = 1
net.ipv4.tcp_syn_retries = 2
net.ipv4.tcp_max_syn_backlog = 16384
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_sack = 1
net.ipv4.tcp_fack = 1
net.ipv4.tcp_ecn = 2
net.ipv4.tcp_fin_timeout = 10
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 60
net.ipv4.tcp_keepalive_probes = 10
net.ipv4.tcp_no_metrics_save = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.all.rp_filter = 1

I just put what I use, which is like three lines less than the original. To be honest, I have no idea what most of them do(I understand many of the networking ones), but from my testing with ab on a more powerful server, these rules are effective. Now reboot, or because you’re on Linux :) , just run

sudo sysctl -a

Additional tips

If you’re using UFW, there are a few more steps you can take to prevent your server from going down. You’re probably familiar with

ufw allow 22/tcp

This allows connections to TCP port 22, which is most commonly used for SSH. For extra protection, however, use “limit” like so:

ufw limit 22/tcp

This will overwrite “allow” rules if they exist for the same port. What this does is rate limit that port to 6 new connection per ip per 30 seconds. This works on many other services as well. However, remember that sometimes receiving more than 6 connections in 30 seconds is normal. For example, refreshing the page, or clicking on a link more than once within 30 seconds isn’t exactly rare. Rate limiting on TCP 80 and/or 443 could result in browsers not being able to access your website. To resolve this, simply don’t use rate limiting on those ports, or allow certain IP addresses(i.e. Cloudflare’s) unmetered access(i.e. ufw insert 1 allow from IP_ADDR to any port 443), and limit everyone else.

ATTRIBUTION:

99% of this post is from the amazing JavaPipe blog, I just converted the rules into iptables-restore compatible format to work with UFW and ip6tables. Some UFW examples are from DigitalOcean. I also got some UFW help from cyberciti.

Note: Some of the rules can’t be put in before.rules or else they would just let all ports through, even ones blocked with UFW. The rules in this post (should) respect UFW port blocking.

Leave any other tips in the comments!!!

Bob Kfir’s Tech Blog

A technology blog with an emphasis on cybersecurity and privacy.

Bob Kfir

Written by

Bob Kfir

I’m a writer and a programmer. Most of what I write is about technology (often privacy and cybersecurity) and/or writing. You can learn more at www.bobkfir.com

Bob Kfir’s Tech Blog

A technology blog with an emphasis on cybersecurity and privacy.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade