Security

Linux Firewall: Using UFW to Block Specific Source IP Ranges Automatically

Block a source IP range in UFW with one command, then keep a blocklist file and a cron job that re-applies it so new ranges are dropped without manual work.

IntermediateLinux server6 min read

You can block a source IP range in UFW with a single command: sudo ufw deny from 203.0.113.0/24. This guide shows that command, then builds a small script and cron job that read a blocklist file and keep those ranges blocked automatically, so you are not editing rules by hand every time a feed changes.

The approach works on Ubuntu 22.04/24.04, Debian 12 and AlmaLinux/Rocky 8–9. It is aimed at ranges you already know are hostile or unwanted — scanners, abusive subnets, a country block you decided on. It is not a replacement for rate limiting or Fail2ban, which handle hosts that misbehave after connecting.

Before you start

  • A Linux VPS with root or sudo access and UFW installed. On Ubuntu and Debian: sudo apt install ufw. On AlmaLinux and Rocky, UFW comes from EPEL: sudo dnf install epel-release && sudo dnf install ufw. Check your distribution's documentation if the package name differs.
  • Your own admin IP written down. You need it to avoid blocking yourself.
  • SSH access tested, plus the browser console in your VPS provider's control panel open in a second tab. The console is your way back in if a rule goes wrong.

Warning: A deny rule that matches your own IP or your office range will lock you out of SSH immediately. Keep your VPS provider's console open until you have confirmed the rule does what you expect.

Warning: sudo ufw reload and sudo ufw reset flush and rebuild the rule set. Established SSH sessions can drop during a reload. Prefer adding rules with the ufw command, which applies them without a full flush.

Block a source IP range with UFW

Start by looking at what is already loaded.

Bash
sudo ufw status verbose
sudo ufw status numbered

Then deny the range. deny drops packets silently; reject sends a refusal back, which tells the sender the host exists.

Bash
sudo ufw deny from 203.0.113.0/24

To block the range only on one service, scope the rule to a port:

Bash
sudo ufw deny from 203.0.113.0/24 to any port 22 proto tcp

UFW evaluates rules in order, so a rule appended at the end sits below any existing allow. If you already allow SSH from Anywhere, a later deny will never be reached. Insert the block at the top instead:

Bash
sudo ufw insert 1 deny from 203.0.113.0/24

Preview what a rule will do before you commit it:

Bash
sudo ufw --dry-run deny from 203.0.113.0/24

Remove a rule by number from ufw status numbered, or by repeating the rule text:

Bash
sudo ufw delete 3
sudo ufw delete deny from 203.0.113.0/24

Keep a blocklist file

Typing rules one at a time does not scale. Put the ranges in a file, one CIDR per line, with # for comments.

Bash
sudo install -m 600 /dev/null /etc/ufw/blocklist.txt
sudo nano /etc/ufw/blocklist.txt
# Known hostile ranges - review monthly
203.0.113.0/24
198.51.100.0/25
192.0.2.128/26

Keep the file mode at 600 and owned by root. Anyone who can write to it can change your firewall.

Tip: Only add ranges you can justify. Blocking a large cloud provider range will also block legitimate users and monitoring services that live there.

Automate the blocklist with a script and cron

The script below reads the file, skips ranges that are already blocked, and inserts new ones at the top of the rule set.

Bash
sudo nano /usr/local/sbin/ufw-blocklist.sh
Bash
#!/usr/bin/env bash
set -euo pipefail

LIST="/etc/ufw/blocklist.txt"
UFW="/usr/sbin/ufw"

[[ -f "$LIST" ]] || { echo "blocklist: $LIST not found"; exit 1; }

while read -r line; do
  cidr="${line%%#*}"
  cidr="$(echo "$cidr" | tr -d '[:space:]')"
  [[ -z "$cidr" ]] && continue

  if "$UFW" status | grep -qF "$cidr"; then
    continue
  fi

  echo "blocklist: adding deny for $cidr"
  if "$UFW" status numbered | grep -q '^\['; then
    "$UFW" insert 1 deny from "$cidr" >/dev/null
  else
    "$UFW" deny from "$cidr" >/dev/null
  fi
done < "$LIST"

Make it executable and run it once by hand to confirm it behaves.

Bash
sudo chmod 700 /usr/local/sbin/ufw-blocklist.sh
sudo /usr/local/sbin/ufw-blocklist.sh

Then schedule it. Use the root crontab so the script has the privileges it needs.

Bash
sudo crontab -e
17 4 * * * /usr/local/sbin/ufw-blocklist.sh >> /var/log/ufw-blocklist.log 2>&1

If you pull ranges from a third-party feed, download to a temporary file, validate that every line looks like a CIDR, and only then move it into place. Never pipe a remote list straight into ufw.

Warning: A bad feed can contain 0.0.0.0/0 or your own subnet. Validate the file before the script reads it, and keep a copy of the last known good list.

Check it works

Confirm the rules are present and in the order you expect.

Bash
sudo ufw status numbered | head -20
sudo ufw status verbose

From a host inside the blocked range, a connection attempt should time out:

Bash
curl --max-time 5 http://203.0.113.10/
nc -vz 203.0.113.10 22

Turn on logging and watch the drops arrive:

Bash
sudo ufw logging on
sudo tail -f /var/log/ufw.log

Blocked packets appear with the [UFW BLOCK] prefix, including the source address and destination port. If nothing shows up, the traffic is not reaching the rule — see the next section.

Troubleshooting

  • The rule exists but traffic still gets through. An allow rule sits above it in the numbered list. Run sudo ufw status numbered and either insert the deny at position 1 or delete the broader allow.
  • You locked yourself out of SSH. Your own IP matched the range. Open your VPS provider's console, run sudo ufw disable, then delete the offending rule with sudo ufw delete <number> and re-enable UFW.
  • Docker containers are still reachable from blocked ranges. Docker writes its own iptables rules that bypass UFW's chains. See Stop Docker from bypassing your UFW firewall.
  • The cron job runs but no rules are added. Cron has a minimal PATH, so ufw may not resolve. The script above uses the full path /usr/sbin/ufw for this reason. Check /var/log/ufw-blocklist.log for errors.
  • Reloads have become slow. Each rule is a separate iptables entry. Past a few hundred ranges, switch to a set-based approach with ipset for high-performance IP blacklisting.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.