An IP keeps hammering your server — failed SSH logins, scans, junk requests. Here is how to block it with the firewall you already have: ufw on Ubuntu and Debian, firewalld on RHEL, Rocky and AlmaLinux, or plain iptables anywhere. Every example uses 203.0.113.45; swap in the address you want gone.
Before you start: don't block yourself
If you're connected over SSH, check your own address first so you never type it by mistake:
echo $SSH_CLIENT
The first field is the IP you're connecting from. Keep a second SSH session open while you change firewall rules — if something goes wrong, that session usually stays connected.
With ufw (Ubuntu, Debian)
ufw checks rules in order and stops at the first match. A plain ufw deny is added at the end, after your allow OpenSSH rule — so it would never be reached. Insert it at the top instead:
sudo ufw insert 1 deny from 203.0.113.45
Block a whole range (here, 256 addresses) the same way:
sudo ufw insert 1 deny from 203.0.113.0/24
Check it worked, and remove it later:
sudo ufw status numbered sudo ufw delete deny from 203.0.113.45
ufw rules survive a reboot on their own.
With firewalld (RHEL, Rocky, AlmaLinux, Fedora)
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.45" drop' sudo firewall-cmd --reload
List and remove:
sudo firewall-cmd --list-rich-rules sudo firewall-cmd --permanent --remove-rich-rule='rule family="ipv4" source address="203.0.113.45" drop' sudo firewall-cmd --reload
With iptables (any distribution)
-I puts the rule at the top of the chain, so it wins over any earlier ACCEPT:
sudo iptables -I INPUT -s 203.0.113.45 -j DROP
Only cut off SSH and leave your websites reachable from that IP:
sudo iptables -I INPUT -s 203.0.113.45 -p tcp --dport 22 -j DROP
See your rules with line numbers, and delete one:
sudo iptables -L INPUT -n --line-numbers sudo iptables -D INPUT -s 203.0.113.45 -j DROP
iptables rules are lost on reboot unless you save them. On Ubuntu and Debian:
sudo apt install iptables-persistent sudo netfilter-persistent save
For IPv6 addresses, use ip6tables with the same options.
With nftables (newer Debian, Ubuntu and RHEL)
On Debian 10+, Ubuntu 21.10+ and RHEL 8+, the iptables command is usually a front end for nftables, so the commands above keep working. To use nftables directly, keep blocked addresses in a set in a table of their own — one rule then covers every address in it:
sudo nft add table inet blocklist
sudo nft add set inet blocklist ips '{ type ipv4_addr; flags interval; }'
sudo nft add chain inet blocklist input '{ type filter hook input priority -10; policy accept; }'
sudo nft add rule inet blocklist input ip saddr @ips drop
sudo nft add element inet blocklist ips '{ 203.0.113.45, 198.51.100.0/24 }'Check the list, and remove an address:
sudo nft list set inet blocklist ips
sudo nft delete element inet blocklist ips '{ 203.0.113.45 }'Rules made this way are lost on reboot. To keep them, add the table to the file the nftables service loads at boot:
sudo sh -c 'nft list table inet blocklist >> /etc/nftables.conf' sudo systemctl enable nftables
Blocking many addresses with iptables: ipset
One iptables rule per address gets slow once the list reaches thousands. An ipset keeps the addresses in a fast lookup table that a single rule checks:
sudo apt install ipset # RHEL family: sudo dnf install ipset sudo ipset create blocklist hash:net sudo ipset add blocklist 203.0.113.45 sudo ipset add blocklist 198.51.100.0/24 sudo iptables -I INPUT -m set --match-set blocklist src -j DROP
Add or remove addresses later with ipset add and ipset del — the iptables rule never changes. On Ubuntu and Debian, sudo apt install ipset-persistent saves the set along with your iptables rules when you run netfilter-persistent save.
DROP or REJECT?
DROP silently ignores the traffic, so the other side waits for a timeout. REJECT answers "no" straight away. For attackers, DROP is the usual choice — it tells them nothing and slows their tools down.
Blocking doesn't work for Docker containers?
Ports published by Docker (-p 8080:80) are routed before your INPUT rules and ufw ever see the traffic. To block an IP from reaching containers, add the rule to Docker's own chain:
sudo iptables -I DOCKER-USER -s 203.0.113.45 -j DROP
Don't forget your cloud firewall
AWS security groups, Azure network security groups and similar sit in front of your server. A server firewall rule is enough to block an IP, but if you want to stop traffic before it reaches the machine at all, deny it there too.
When one IP becomes hundreds
Blocking by hand works for one stubborn address. On a server facing the internet, SSH attacks come from thousands of IPs a week, and new ones appear every hour. That's where automation earns its keep:
- First, make guessing pointless — SSH keys only, no passwords. See how to stop SSH brute-force attacks.
- Then block repeat offenders automatically. ThreatGuard watches SSH logins on all your servers, blocks an IP at the firewall once it crosses your rule (say, 5 failures in an hour), and lets you unblock it with one click — from one dashboard instead of a terminal on every server.