The guide
An address list is only as good as its order: what allow and deny really mean to a server
Letting visitors in by network address is the oldest access rule on the web. It is cheap and useful, and it goes wrong quietly, because the same lines mean different things to different servers.
- 128 bits the length of an IPv6 address, four times an IPv4 one
- 3 blocks what the five addresses from .5 to .9 need in CIDR form
- 2^72 addresses in a single IPv6 /56
- 100.64.0.0/10 set aside for providers who share addresses among customers
- 1 day default preferred life of a temporary IPv6 address
Read the full guide Fold the guide
Every connection to a server arrives with a return address. Deciding who gets in by reading that address is the simplest access rule there is: no passwords, no accounts, nothing for the visitor to do.
That simplicity is why address lists are everywhere, on admin pages, staging sites, payment callbacks and office intranets. It is also why they rot. A list is a few lines of text that nobody rereads, and the lines mean one thing to the person who wrote them and sometimes another to the software that obeys them.
The argument here is that most trouble with these lists has nothing to do with addresses. It comes from order: from not knowing which line wins when two of them hold the same visitor.
What an address can and cannot tell you
An address says where a packet claims to come from, and for a single packet that claim can simply be false. RFC 2827, written in May 2000 to curb floods of forged traffic, asks providers to drop packets from their customers that carry a source address outside the customer’s own range. It admits that even where this is done, an attacker can still forge the address of a neighbour inside the same permitted range.
An address is also not a person. RFC 1918 set aside private ranges for use inside organisations, whose machines reach the outside world only through gateways. In April 2012 RFC 6598 added 100.64.0.0/10 for providers who place many customers behind shared addresses, a practice known as carrier-grade address translation. Allow one such address and you allow everyone who shares it. Deny it and you deny them all.
Then there are proxies. RFC 7239 states the problem plainly: a proxy makes requests appear to come from the proxy’s own address, and information about the original sender is lost. The Apache documentation carries the same warning beside its address rules. A server that sits behind a load balancer and has not been told to take the client address from it sees one visitor, the balancer, for every request, so an address list there either admits everybody or nobody.
So an address list is a fence around a place, not a lock on a door. It is good at keeping an admin page off the open internet and at cutting noise. It is not a login, and it should not be the only thing guarding anything that matters.
CIDR in plain words
Lists are written in blocks, not single addresses, and the notation for blocks is CIDR. RFC 4632 describes it in one sentence: an address followed by a slash and the number of leading bits that are fixed. In 192.0.2.0/24 the first 24 bits are fixed and the last 8 are free, which makes 256 addresses. Each bit added to the prefix halves the block.
Two consequences follow, and both catch people out. The first is that the free bits must be zero in the written form. Something like 192.0.2.10/24 is not a block; it is a host address with a mask attached, and software disagrees about what to do with it. Some rounds it down in silence, some refuses it. A careful list holds 192.0.2.0/24 and nothing vaguer.
The second is that blocks only come in sizes that are powers of two, and only at boundaries that are multiples of their size. A range such as 192.0.2.5 to 192.0.2.9 is five addresses, which is no power of two, so it takes 3 blocks: 192.0.2.5 alone, then 192.0.2.6/31, then 192.0.2.8/31. The worst IPv4 range needs 62.
Here is a worked example to try. Enter 192.0.2.64/26 and 192.0.2.128/26. They touch: one ends at .127 and the other begins at .128, and together they hold 128 addresses, the size of a /25. Yet they cannot be merged into one block, because the /25 blocks in that network run from .0 to .127 and from .128 to .255. The two quarters straddle that line and have no parent in common. Replace the second with 192.0.2.0/26 and the pair does merge, into 192.0.2.0/25. Adjacent is not the same as mergeable, and a tool that forgets it produces a block covering addresses nobody listed.
The same list, three meanings
Now put an allow and a deny in the same list so that they overlap, and ask a server what it will do. The answer depends on which server you ask.
The access module of nginx reads its allow and deny rules in sequence and stops at the first one that matches. Order is everything. The documentation’s own example puts a deny for one address above the allow for the network that holds it, and ends with a rule denying all, since anything that reaches the bottom has matched nothing.
The geo module from the same project maps addresses to values in a different way: the most specific match is used. The entry with the longest prefix wins wherever it stands, so a narrow exception inside a wide block always takes effect, and order no longer matters at all.
Apache’s older directives worked a third way, which its documentation contrasts with a typical firewall. Under the setting Order Deny,Allow, every Deny line is evaluated first, and a match is refused unless an Allow line matches too. The lines form two groups, and their order in the file means nothing. Those directives are now deprecated in favour of the Require form, which has no order either: an address gets in when some Require ip line holds it, and a Require not ip line can only veto, never admit.
Take one list and read it all three ways. Allow 2001:db8:a::/48, an office network, and below it deny 2001:db8:a:ff00::/56, the guest wifi inside that network. Under most specific match the guests are kept out, as intended. Under first match they are let in, because the wide allow is reached first and the deny below it never runs. Under denies then allows they are let in as well, since an allow outranks any deny. The deny covers 2^72 addresses, and in two readings out of three it does nothing.
Nothing reports this. The server loads the file without complaint. That is the real cost of a stale or misordered entry: it reads like protection to whoever reviews the list.
Keeping a list honest
A tidy list is one where every line does work. Entries that are covered by earlier ones, entries that repeat what the default already says, and neighbours that could be one block all add length without adding meaning, and length is where mistakes hide. Removing them is safe only if the result gives every address the same answer as before, and that can be checked exactly, since both lists are finite sets of blocks.
Special ranges deserve a second look whenever they appear. IANA keeps two registries of them, set up by RFC 6890, covering loopback, link-local, private and documentation space among others. An allow for a private range is sensible inside a network and meaningless on a public server. The examples in this essay use 192.0.2.0/24 and 2001:db8::/32, which RFC 5737 and RFC 3849 reserve for documentation and which should never appear in a working list.
IPv6 changes the habits more than the rules. An address is 128 bits, and it has many spellings: RFC 4291 allows leading zeros, capitals and a double colon standing for a run of zeros. RFC 5952 picks one canonical spelling so that two lines naming the same address look the same, which matters when people search a list by eye.
Single IPv6 addresses also make poor entries. RFC 6177 notes that the registries encourage giving end sites a /56, while RFC 8981 has devices generate temporary addresses with a default preferred life of 1 day. A laptop’s address today is gone tomorrow, and its network is the thing that stays. List the prefix.
The last pitfall is forgetting a family entirely. A list with only IPv4 entries says nothing about IPv6, so every IPv6 visitor gets whatever the default is. With a default of deny, the colleague whose connection prefers IPv6 is locked out. With a default of allow, the whole fence has a gap as wide as the larger internet.
No tool can tell you whether an address deserves to be on the list, and none can see a proxy in front of your server. What can be checked is narrower: that every line parses, that each one takes effect under the rule your server follows, and that the text you load says what you meant.
Sources
Every source consulted for this guide. Links checked October 10, 2026.
- Classless Inter-domain Routing (CIDR): The Internet Address Assignment and Aggregation Plan RFC 4632, Internet Engineering Task Force
- IP Version 6 Addressing Architecture RFC 4291, Internet Engineering Task Force
- A Recommendation for IPv6 Address Text Representation RFC 5952, Internet Engineering Task Force
- Special-Purpose IP Address Registries RFC 6890, Internet Engineering Task Force
- IPv4 Special-Purpose Address Space Internet Assigned Numbers Authority (IANA)
- IPv6 Special-Purpose Address Space Internet Assigned Numbers Authority (IANA)
- IPv4 Address Blocks Reserved for Documentation RFC 5737, Internet Engineering Task Force
- IPv6 Address Prefix Reserved for Documentation RFC 3849, Internet Engineering Task Force
- Network Ingress Filtering: Defeating Denial of Service Attacks which employ IP Source Address Spoofing RFC 2827, Internet Engineering Task Force
- IANA-Reserved IPv4 Prefix for Shared Address Space RFC 6598, Internet Engineering Task Force
- Forwarded HTTP Extension RFC 7239, Internet Engineering Task Force
- IPv6 Address Assignment to End Sites RFC 6177, Internet Engineering Task Force
- Temporary Address Extensions for Stateless Address Autoconfiguration in IPv6 RFC 8981, Internet Engineering Task Force
- Module ngx_http_access_module nginx documentation
- Module ngx_http_geo_module nginx documentation
- mod_authz_host - Apache HTTP Server Version 2.4 The Apache Software Foundation
- mod_authz_core - Apache HTTP Server Version 2.4 The Apache Software Foundation
- mod_access_compat - Apache HTTP Server Version 2.4 The Apache Software Foundation