Before you start
- Use an account with
sudo. Keep your current SSH session open and make sure you have a console or recovery access. - Read the real client address from your logs. Never block your own address, your monitoring service, a reverse proxy or a CDN. A shared public IP may belong to many honest users.
- The addresses
203.0.113.45,203.0.113.0/24and2001:db8::45below are examples. Replace them with the real address or network. - Change one layer at a time. Do not mix UFW, firewalld and raw firewall rules unless you know how they work together.
| Method | Scope | Use it when |
|---|---|---|
| UFW | All traffic that reaches the server | UFW already runs on your Ubuntu or Debian server |
| Apache 2.4 | One website or folder | You use Apache |
| Nginx | One server block or location | You use Nginx |
Block with UFW
First look at the status and the rule order:
sudo ufw status verbose
sudo ufw status numbered
Continue only if UFW is active. If it is off, do not turn it on from a remote session until SSH and all needed services are allowed.
Block all traffic from one address. The rule goes to position 1, so an earlier broad allow rule cannot override it:
sudo ufw insert 1 deny from 203.0.113.45
sudo ufw status numbered
To block only web traffic:
sudo ufw insert 1 deny from 203.0.113.45 to any port 80 proto tcp
sudo ufw insert 1 deny from 203.0.113.45 to any port 443 proto tcp
For a range or an IPv6 address, use the same syntax. IPv6 rules work only when IPv6 is enabled in the UFW settings:
sudo ufw insert 1 deny from 203.0.113.0/24
sudo ufw insert 1 deny from 2001:db8::45
Block in Apache 2.4
Add this to the directory section of your Apache config. You can also use .htaccess if your host allows it. Merge the lines with your current rules and do not replace them:
<RequireAll>
Require all granted
Require not ip 203.0.113.45
</RequireAll>
The blocked visitor gets HTTP 403. SSH and other services are not affected. Do not mix this with the old Order, Allow, Deny style.
If you changed the main config on Ubuntu, test it before you reload:
sudo apachectl configtest
sudo systemctl reload apache2
For a .htaccess change, test with a real request and read the error log. configtest does not check .htaccess files.
Block in Nginx
Nginx does not read .htaccess. Put the lines in the server or location block that handles the request. Put deny before allow:
deny 203.0.113.45;
allow all;
Test the config and reload only if it passes:
sudo nginx -t
sudo systemctl reload nginx
Behind a trusted reverse proxy, set up real client IP handling first. Otherwise the rule sees the proxy address.
Check and undo
- Request the site from the blocked address and look for a 403. Read the firewall rules and logs.
- Write down the address, the reason and the date. IP addresses change owners, so remove old blocks.
To remove the UFW rules from above:
sudo ufw delete deny from 203.0.113.45
sudo ufw delete deny from 203.0.113.45 to any port 80 proto tcp
sudo ufw delete deny from 203.0.113.45 to any port 443 proto tcp
For Apache or Nginx, delete the lines you added, test the config and reload.
Think wider
Manual blocks never expire. A tool such as Fail2Ban can ban addresses for a while based on log patterns. It needs the right filters and ban settings.
Quick recap
- Pick the layer: UFW for the whole server, Apache or Nginx for one site.
- Test the config before you reload.
- Keep a record and remove old blocks.