Websites & apps

Allow or block IP addresses in Nginx

Use Nginx allow and deny rules to keep admin areas to your own IP, block abusive addresses and whole ranges, manage long lists in include files, and choose between 403 and 444.

BeginnerLinux server5 min read

Nginx can accept or refuse a request based on the visitor's IP address before it touches PHP or your app. That makes it a cheap, fast way to keep an admin panel private or shut out a scraper that will not stop. This guide covers the allow and deny rules, how they are evaluated, how to keep long lists tidy, and what to send back to blocked visitors.

Before you start

  • A Linux VPS with Nginx serving your site.
  • Your own public IP address. Search "what is my IP" in a browser, or check the access log after visiting the site.
  • If the site is behind Cloudflare or another proxy, set up real visitor IPs first, or Nginx will see only the proxy. See Show real visitor IPs in Nginx behind Cloudflare.
  • SSH access with sudo.

How allow and deny work

  • Rules can go in the http, server or location context.
  • Nginx checks them in order and stops at the first match.
  • An IP that matches nothing is allowed.
  • Rules in a location replace rules inherited from the server block; they are not merged.

So the order matters. This allows one IP and blocks everyone else:

Nginx
allow 198.51.100.20;
deny  all;

Reversed, deny all would match first and block you too.

Restrict an admin path to your IP

Put the rules in the location for the admin area:

Nginx
location ^~ /admin/ {
    allow 198.51.100.20;
    allow 203.0.113.0/24;
    deny  all;

    try_files $uri $uri/ /index.php?$args;
}

CIDR ranges such as 203.0.113.0/24 cover a whole block of addresses. IPv6 works the same way: allow 2001:db8::/48;.

^~ makes sure this prefix location wins over regex locations such as your PHP handler. If the protected area contains PHP files, nest the PHP block inside this location so they are protected too. For WordPress:

Nginx
location = /wp-login.php {
    allow 198.51.100.20;
    deny  all;
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}

Copy the PHP lines from your own config; socket paths differ between systems.

Warning: If your home or mobile IP changes, you lock yourself out of the admin area. The site itself stays up and you can always fix the rule over SSH, but consider password protecting the folder with satisfy any as a fallback.

Block abusive IPs

To block a few addresses across a whole site, add deny lines to the server block:

Nginx
server {
    server_name example.com;

    deny 192.0.2.15;
    deny 192.0.2.0/24;
}

Keep your existing directives; only add the deny lines. Everyone not listed is still allowed.

Keep long lists in include files

Lists grow. Put them in their own file so you can edit them without touching site configs:

Bash
sudo nano /etc/nginx/snippets/blocklist.conf
Nginx
deny 192.0.2.15;
deny 192.0.2.0/24;
deny 2001:db8:bad::/48;

Then include it in each site's server block:

Nginx
include /etc/nginx/snippets/blocklist.conf;

On AlmaLinux and Rocky Linux, create the snippets folder first with sudo mkdir -p /etc/nginx/snippets, or keep the file directly in /etc/nginx/. Do not put it in conf.d, because files there are loaded at the http level automatically.

For very long lists (thousands of entries), a geo block is faster than many deny lines. In the http context:

Nginx
geo $blocked_ip {
    default 0;
    include /etc/nginx/snippets/blocked-geo.conf;
}

Where each line of blocked-geo.conf looks like 192.0.2.0/24 1;. Then in the server block:

Nginx
if ($blocked_ip) {
    return 403;
}

403 or 444

  • deny returns 403 Forbidden. The visitor sees an error page, so it is clear what happened. Use this for admin areas, where a real person may hit it.
  • return 444 is special to Nginx: it closes the connection without sending any response. Bots get nothing back and waste no bandwidth. Use it in the geo example above for known abusers.

You can also show a friendlier message for 403s:

Nginx
error_page 403 /403.html;
location = /403.html {
    root /var/www/errors;
    allow all;
    internal;
}

allow all inside this location ensures blocked visitors can still see the error page.

Apply the change

Bash
sudo nginx -t && sudo systemctl reload nginx

Check it works

From an address that should be blocked, or from the server itself if 127.0.0.1 is not allowed:

Bash
curl -I https://example.com/admin/

Expected:

HTTP/2 403

From your allowed IP you should get 200 or a normal redirect. Blocked requests are also logged in the error log:

Bash
sudo grep "access forbidden by rule" /var/log/nginx/error.log | tail

Troubleshooting

  • You are blocked from your own admin area: your IP changed or you are behind a VPN. Check which IP Nginx logs for you and update the rule.
  • Blocking does nothing behind Cloudflare: Nginx sees Cloudflare's IPs. Configure the real IP module, or block at Cloudflare instead.
  • PHP files in the admin folder are not protected: the PHP regex location matched first. Use ^~ and nest the PHP block.
  • Rules from the server block stop working in one location: that location has its own allow/deny, which replaces the inherited ones. Repeat the rules or include the snippet there.
  • The abuser keeps coming back from new IPs: static lists cannot keep up. Automate it with Fail2ban jails for Nginx or block at the firewall.

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.