IP-based access rules are useful when an admin path, staging site, partner endpoint, or private dashboard should not be reachable by every visitor that can reach the Nginx listener. The important detail is order: Nginx checks allow and deny rules from top to bottom and stops at the first match.
Use Nginx IP allow and block rules for HTTP request handling, not as a replacement for a firewall, CDN policy, VPN, or application authentication. The safest setup keeps the protected location narrow, confirms the real client IP Nginx sees, tests the configuration with nginx -t, reloads Nginx, and then proves the expected 200 or 403 response.
Understand Nginx IP Access Rules
The official Nginx access module documentation defines the allow and deny directives for limiting requests by client address. Both directives accept a single IP address, a CIDR network, all, and the special unix: value for UNIX-domain socket requests.
| Directive | Context | Purpose |
|---|---|---|
allow | http, server, location, limit_except | Allows a matching client address, CIDR range, UNIX socket, or all clients. |
deny | http, server, location, limit_except | Denies a matching client address, CIDR range, UNIX socket, or all clients. |
include | any | Loads reusable rule files, as long as the included file contains directives valid in that context. |
set_real_ip_from | http, server, location | Marks a proxy or load balancer address as trusted for real-client-IP replacement. |
real_ip_header | http, server, location | Names the header or PROXY protocol source used for the replacement address. |
real_ip_recursive | http, server, location | Chooses the last non-trusted forwarded address when recursive lookup is enabled. |
geo | http | Creates variables from client IP addresses, which helps with larger CIDR lists. |
Rule sequence matters more than visual grouping. A broad rule such as deny all; must come after the addresses you want to permit. If deny all; appears first, later allow rules do not rescue the request because Nginx has already found a match.
An allow rule by itself does not block unmatched clients. When no access rule matches, request processing continues, so a real allowlist needs the allowed addresses first and a final deny all;.
location /admin/ {
allow 203.0.113.10;
allow 2001:db8:1234::/48;
deny all;
}
Inheritance is the other common surprise. Access rules from a higher context are inherited only when the current context has no allow or deny directives of its own. As soon as a location defines any access rule, it replaces the inherited access-rule set for that location. Include a final deny all; whenever the location is meant to be a true allowlist.
IP addresses can represent shared offices, VPN exits, mobile carrier gateways, or a CDN edge rather than one person. Use Nginx IP rules as a practical gate for known networks, not as the only authentication control for sensitive applications.
Confirm Your Nginx Baseline
Start with Nginx already installed and serving the site or path you plan to protect. Use the matching distribution guide when the base package setup still needs work:
Find the active include layout before editing a site file. Debian and Ubuntu packages commonly use sites-enabled, while Fedora, RHEL-family, and nginx.org packages commonly load conf.d files. Some Fedora-style server blocks also include default.d snippets inside a selected server.
sudo nginx -T 2>/dev/null | grep -E '^[[:space:]]*include .*(sites-enabled|conf\.d|default\.d)'
Example output can show one or more active include directories, depending on the package source and distribution layout:
include /etc/nginx/default.d/*.conf; include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*;
If you need a fuller server-block workflow before adding access rules, use the Nginx server blocks and virtual hosts guide first.
Allow or Block IP Addresses in Nginx
Place access rules in the smallest context that owns the decision. Use a location block for an admin path, a server block for a whole hostname, and http only when the same policy should apply broadly across included virtual hosts.
Choose the Nginx Context for IP Rules
Nginx accepts allow and deny in several contexts, but the safest placement is usually the narrowest one that matches the protected surface. Broader contexts are useful when the policy is intentional for every inherited site or path.
| Context | Use It For | Watch For |
|---|---|---|
http | A shared rule that should affect all inherited virtual hosts in the same Nginx HTTP configuration. | A broad rule can affect unrelated sites loaded from conf.d or sites-enabled. |
server | A whole hostname, such as a staging domain, private app, or internal dashboard. | Child locations inherit the rule set only until they define their own allow or deny. |
location | A direct path rule for one admin area, API route, metrics endpoint, or download folder. | Another more specific location can handle the request instead, so confirm location priority when rules appear ignored. |
limit_except | Public read access with write-style methods limited to trusted addresses. | Method restrictions do not replace application authentication for real write permissions. |
Apply IP Rules Globally at the HTTP Level
Use the http context only when every server that inherits the block should share the rule. This example blocks one network across the virtual hosts inside the same Nginx HTTP configuration while leaving other clients on the normal request path.
http {
deny 198.51.100.0/24;
server {
listen 80;
server_name example.com;
location / {
try_files $uri $uri/ =404;
}
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
}
Both server blocks inherit the deny rule until a lower server or location context defines its own access-rule set. Avoid a global deny all; unless every inherited hostname is meant to be private.
Block One IP Address
Use a single deny rule when the rest of the location should stay public but one address should be refused. Replace the documentation address with the client IP from your logs.
location / {
deny 198.51.100.23;
try_files $uri $uri/ =404;
}
This blocks only 198.51.100.23. Other clients continue through normal file, proxy, or application handling unless another inherited layer blocks them.
Block a CIDR Network
Use CIDR notation when the abusive or restricted traffic comes from a known network range. Put this rule in the server or location where that network should be denied.
server {
listen 80;
server_name example.com;
deny 198.51.100.0/24;
location / {
try_files $uri $uri/ =404;
}
}
A subnet block is a blunt tool. Confirm the range belongs to the traffic you mean to block, especially when logs show cloud providers, VPNs, corporate networks, or mobile carrier gateways.
Write the network address for CIDR ranges, such as 198.51.100.0/24. If you mean one host, use a single IP address or a /32 prefix so nginx -t does not warn that low address bits are meaningless.
Allow One IP Address and Block Everyone Else
An allowlist needs the allowed address first, followed by deny all;. This pattern fits admin panels, staging paths, metrics endpoints, and private dashboards that only trusted networks should reach.
Do not omit the final deny all;. Without that fallback, clients that do not match the allow line continue through normal request handling.
location /admin/ {
allow 203.0.113.10;
deny all;
proxy_pass http://127.0.0.1:3000;
}
The request from 203.0.113.10 continues to the local application. Any other client receives 403 Forbidden before the request reaches the upstream service.
Allow a Subnet with a Denied Exception
Put specific exceptions before broader allowed networks. In this example, the whole partner subnet is allowed except one address.
location /partners/ {
deny 203.0.113.25;
allow 203.0.113.0/24;
deny all;
proxy_pass http://127.0.0.1:3000;
}
Nginx checks deny 203.0.113.25; before it reaches the broader allow 203.0.113.0/24; rule. Reversing those two lines would allow the exception address because the subnet rule would match first.
Apply IP Rules to a Whole Hostname
Place rules at the server level when the entire virtual host should be private. This is useful for an internal hostname or a staging domain.
server {
listen 80;
server_name staging.example.com;
allow 10.0.0.0/8;
allow 203.0.113.10;
deny all;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
Any location inside this server inherits the rule set unless that location defines its own allow or deny directive. If one child location must be public, make that exception explicit and review it carefully.
Limit Non-GET Methods by IP Address
The limit_except context can restrict methods other than GET and HEAD. This pattern keeps read access public while limiting write-style methods to a trusted network.
location /api/ {
proxy_pass http://127.0.0.1:3000;
limit_except GET {
allow 203.0.113.10;
deny all;
}
}
Allowing GET also allows HEAD, as documented by the Nginx core module. Use application authentication for real write permissions; method-based IP rules are only one request-layer gate.
Use Include Files for Reusable Nginx IP Lists
Reusable include files keep the same allowlist from being copied across several server blocks. The Nginx include directive loads the file in place, so the included file must contain directives that are valid where the file is included.
Create a dedicated allowlist file such as /etc/nginx/allowlists/admin-ips.conf with only the access rules:
sudo install -d -m 0755 /etc/nginx/allowlists
sudo nano /etc/nginx/allowlists/admin-ips.conf
Add only directives that are valid in the place where the file will be included:
# /etc/nginx/allowlists/admin-ips.conf
allow 203.0.113.10;
allow 203.0.113.0/24;
allow 2001:db8:1234::/48;
deny all;
Then include the file inside each protected location:
location /admin/ {
include /etc/nginx/allowlists/admin-ips.conf;
proxy_pass http://127.0.0.1:3000;
}
Keep the final deny all; inside the included file only when every location using that file should be private. If one location needs a different fallback decision, use a separate include file instead of relying on memory months later.
Test and Apply Nginx IP Access Changes
Back up the site file before editing a production allowlist or deny rule. Use the file path your Nginx include layout actually loads.
sudo cp -a /etc/nginx/conf.d/example.com.conf /etc/nginx/conf.d/example.com.conf.before-ip-rules
On a Debian-style layout, back up the source file under sites-available instead of the enabled symlink:
sudo cp -a /etc/nginx/sites-available/example.com /etc/nginx/sites-available/example.com.before-ip-rules
Test the full Nginx configuration before reloading. The official Nginx command-line parameter documentation describes -t as the syntax check that also tries to open referenced files.
sudo nginx -t
A successful syntax test prints:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
Reload Nginx after the syntax check succeeds:
sudo systemctl reload nginx
On a system without systemd, use Nginx’s reload signal instead:
sudo nginx -s reload
Use curl command examples to test the protected path from the Nginx host. This loopback check proves the origin server block and path, not public DNS or CDN behavior.
curl --noproxy '*' -D - -o /dev/null -H "Host: example.com" http://127.0.0.1/admin/
A blocked request returns 403 Forbidden:
HTTP/1.1 403 Forbidden Server: nginx
Test from a real allowed address and a real denied address before relying on the rule in production. Ordinary request headers do not change the TCP source IP that Nginx matches unless the real IP module is configured to trust a proxy header.
Do not test access rules with a temporary return 200 in the same protected location. The Nginx rewrite module can return that response before the access module denies the request, so test the real static file, proxy, or application path instead.
When a rule blocks a request, the error log commonly includes the stable phrase access forbidden by rule. Use the Nginx access and error logs guide when you need to match a public request, hostname, URI, and client address.
sudo grep 'access forbidden by rule' /var/log/nginx/error.log | tail -n 5
The matching log line should include the blocked request and the stable phrase access forbidden by rule if the denial came from the access module.
Rollback a Bad IP Access Rule
If a new rule blocks legitimate traffic, restore the backed-up site file, test syntax, and reload. Use the matching path for your layout.
sudo cp -a /etc/nginx/conf.d/example.com.conf.before-ip-rules /etc/nginx/conf.d/example.com.conf
sudo nginx -t && sudo systemctl reload nginx
For a Debian-style site file, restore the file under sites-available:
sudo cp -a /etc/nginx/sites-available/example.com.before-ip-rules /etc/nginx/sites-available/example.com
sudo nginx -t && sudo systemctl reload nginx
If the reusable allowlist file was created only for this change, remove every include /etc/nginx/allowlists/admin-ips.conf; line before deleting the file. Keep the directory when other allowlists still use it.
sudo rm -f /etc/nginx/allowlists/admin-ips.conf
sudo rmdir --ignore-fail-on-non-empty /etc/nginx/allowlists
sudo nginx -t && sudo systemctl reload nginx
Preserve Real Client IPs Behind Proxies
When Nginx sits behind a CDN, load balancer, reverse proxy, Docker network, Kubernetes ingress, or proxy manager, $remote_addr may be the proxy address. An allowlist built against visitor addresses will fail if Nginx only sees the proxy.
Trust only the proxy addresses you control before using X-Forwarded-For or another forwarded header. The official Nginx real IP module documentation covers the directive contexts and the real_ip_recursive behavior.
The real IP module is a build-time module. If nginx -t reports an unknown set_real_ip_from directive, use a package built with that module or put the IP policy at the proxy layer that already sees the client address.
http {
set_real_ip_from 10.0.0.5;
set_real_ip_from 2001:db8:10::/48;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
server {
listen 80;
server_name example.com;
location /admin/ {
allow 203.0.113.10;
deny all;
proxy_pass http://127.0.0.1:3000;
}
}
}
Do not trust
0.0.0.0/0,::/0, or arbitrary public networks as real-IP sources. A client can spoof forwarded headers when Nginx accepts those headers from untrusted senders.
Check the address Nginx logs before changing access rules. If the first access-log field is still the CDN or load-balancer address, fix the trusted proxy layer before adjusting the allowlist.
sudo tail -n 20 /var/log/nginx/access.log
For broader proxy headers, upstream routing, and local application backends, use the Nginx reverse proxy configuration guide.
Manage Large Nginx IP Block Lists
Small access lists are fine with plain allow and deny directives. For large CIDR sets, the Nginx access module documentation recommends using geo variables from the Nginx geo module instead of a long sequential rule chain.
A geo block belongs in the http context and can match individual IP addresses or CIDR prefixes. This example returns 403 for listed clients before normal server handling continues.
http {
geo $blocked_client {
default 0;
198.51.100.0/24 1;
203.0.113.25 1;
2001:db8:bad::/48 1;
}
server {
listen 80;
server_name example.com;
if ($blocked_client) {
return 403;
}
location / {
try_files $uri $uri/ =404;
}
}
}
The if block here performs one narrow action: return a status code. Avoid building complex rewrites or proxy logic into if blocks; use the Nginx if directive guide when a condition needs more than a simple return.
For operational block lists that change often, consider whether a firewall, CDN security rule, load balancer policy, or Fail2Ban workflow owns the problem better than an Nginx config reload. Nginx still needs a reload before normal configuration-file changes take effect.
Troubleshoot Nginx IP Allow and Block Rules
Troubleshooting should identify the request layer before changing rules. Most access-rule failures come from wrong order, wrong context, proxy IP handling, or a different 403 source such as filesystem permissions or application authorization.
| Symptom | Likely Cause | First Check |
|---|---|---|
Everyone receives 403 Forbidden | deny all; appears before allowed addresses, or the allowlist is in the wrong location. | Dump active allow and deny lines with sudo nginx -T. |
| A trusted user is blocked | Nginx sees a proxy, VPN, NAT, or different public address than expected. | Compare the access log client address with the allowlist. |
| A blocked client still reaches the site | The rule is in a server or location that did not handle the request. | Check server-name matching and location priority. |
nginx -t fails | The directive is in an unsupported context, the CIDR is invalid, or a semicolon is missing. | Read the file and line number printed by the syntax test. |
nginx -t warns that low address bits are meaningless | A host address was written with a network prefix, such as 192.168.1.50/24. | Use the network address for the CIDR range or use a single-host prefix. |
The log says access forbidden by rule | The access module denied the request as configured. | Review rule order and the actual client IP before removing the rule. |
Find Active Nginx Access Rules
Use the active configuration dump so included files and symlinked site files are visible in one stream:
sudo nginx -T 2>&1 | awk '/^# configuration file / { file=$0 } /^[[:space:]]*(allow|deny)[[:space:]]/ { if (file != last) { print file; last=file } print }'
Relevant output should show the order Nginx will evaluate:
# configuration file /etc/nginx/conf.d/example.com.conf:
allow 203.0.113.10;
deny all;
If deny all; appears before the allowed address in the same context, move the allowed address above it and retest with sudo nginx -t.
Fix Allowed IP Addresses Still Receiving 403
Start with the client address Nginx actually logged, not the address the user expected to have. Shared networks and edge proxies can make those values differ.
sudo grep ' 403 ' /var/log/nginx/access.log | tail -n 20
If the logged address is a CDN or load balancer, configure real IP handling for the trusted proxy or put the allowlist at the edge layer that sees the true client. If the address is correct and the log contains access forbidden by rule, repair the rule order or context.
Fix Block Rules That Do Not Match
A block rule only affects requests that reach the context where the rule lives. If another server block handles the hostname, or a more specific location bypasses the protected location, the rule may never run.
sudo nginx -T 2>&1 | awk '/^# configuration file / { file=$0 } /^[[:space:]]*(server_name|location|allow|deny)[[:space:]]/ { if (file != last) { print file; last=file } print }'
Check the matching hostname first, then check the selected location. When path matching is the issue, compare the behavior with Nginx location block priority before moving rules between locations.
Fix Syntax Errors in IP Access Rules
Nginx reports parser errors with a file and line number. A missing semicolon, invalid CIDR prefix, or access directive inside the wrong block prevents reload.
sudo nginx -t
nginx: [emerg] "allow" directive is not allowed here in /etc/nginx/conf.d/example.conf:12 nginx: configuration file /etc/nginx/nginx.conf test failed
Move allow and deny into an http, server, location, or limit_except context. Do not place access directives inside upstream, map, or arbitrary if blocks.
If nginx -t succeeds but prints low address bits are meaningless, the prefix still loaded but probably does not express the range you intended. Replace a value such as 192.168.1.50/24 with 192.168.1.0/24 for the whole subnet, or with 192.168.1.50/32 for one IPv4 host.
Separate Access Rules from Other 403 Causes
Not every 403 Forbidden response comes from allow and deny. Missing index files, file permissions, SELinux labels, dotfile rules, upstream applications, and CDN policies can all return 403. If the error log does not contain access forbidden by rule, use the Nginx 403 Forbidden troubleshooting guide to identify the real layer.
Conclusion
Nginx IP access control is ready when the protected context is narrow, allowed addresses appear before broad denials, proxy headers are trusted only from controlled edges, and the same path returns the expected 200 or 403 after a clean syntax test and reload. For adjacent hardening, combine these rules with Nginx rate limiting where repeated requests need throttling instead of a permanent block.


Formatting tips for your comment
You can use basic HTML to format your comment. Useful tags currently allowed in published comments:
<code>command</code>command<strong>bold</strong><em>italic</em><blockquote>quote</blockquote>