To block an IP address in .htaccess on Apache 2.4, wrap a Require directive in a RequireAll container. That is the modern syntax. The older Order, Allow and Deny directives still work on most servers today, which is exactly why so many tutorials keep teaching them — and why so many people end up with a file that silently does nothing.
Apache's own documentation is unusually blunt about this. It states that the Allow, Deny and Order directives are deprecated, will go away in a future version, and that you should avoid outdated tutorials recommending their use. Several of the pages ranking for this exact search are those tutorials.
So this guide gives you the correct syntax for both Apache versions, shows the documented failure that happens when you mix them, and covers the three reasons a rule that looks perfect still lets traffic through. It also covers who you are likely to lock out by accident, including yourself.
I once spent forty minutes on a site where the owner swore his block was ignored. His syntax was fine. The problem sat one level up: AllowOverride had been left at None, so Apache never read the file at all. Nothing in the logs said so. That single setting explains more broken .htaccess rules than every syntax error I have seen combined.
My honest view is that .htaccess is the most convenient place to block and one of the worst places to do it at scale. Apache re-reads the file on every single request, so a list of a few hundred ranges becomes a measurable tax on your own visitors. Use it for a handful of addresses. Once you are maintaining a real blocklist, move it up to the firewall or the CDN.
Quick Answer: Block an IP Address in .htaccess
On Apache 2.4, add a RequireAll block containing "Require all granted" and "Require not ip" followed by the address. Place the file in your site root, and never mix this with the older Order and Deny directives. Confirm what you are blocking first with an ownership check on that address.
Which .htaccess Syntax Should You Use in 2026?
Use the Require directives from mod_authz_host. Apache 2.4 has been the standard branch for years, and its IP address access control moved to a new module. The old Order, Allow and Deny directives survive only through mod_access_compat, a compatibility shim built to ease upgrades from the 2.2 branch. Apache documents both families, recommends one, and warns against the other in unusually plain language.
The wording in Apache's own access control guide leaves no room for interpretation: those directives are deprecated, they will disappear in a future release, and readers are told to avoid tutorials that still recommend them. Knowing which camp your snippet came from matters more than the snippet itself.
| Task | Apache 2.4 (use this) | Apache 2.2 (legacy) |
|---|---|---|
| Module | mod_authz_host | mod_access_compat — deprecated |
| Block one address | Require not ip | Deny from |
| Allow everyone else | Require all granted | Allow from all |
| Allowlist only | RequireAny with Require ip | Order Deny,Allow |
| Rule ordering | Container logic — explicit | Order directive — counter-intuitive |
| Future support | Current and maintained | Scheduled for removal |
That table tells you which family to pick. The next question is what to actually type.
How Do You Block a Single IP Address in .htaccess?
Open the .htaccess file in your site root, usually public_html or httpdocs, and add a RequireAll container for the IP address you want gone. Inside it, grant access to everyone, then explicitly refuse the address you want gone. Apache evaluates every requirement in a RequireAll container, so the visitor must satisfy both conditions to get through. Your blocked address fails the second one and receives a 403 Forbidden response.
Apache 2.4 — recommended
<RequireAll>
Require all granted
Require not ip 203.0.113.50
</RequireAll>
Blocking several addresses needs no extra containers. Add one Require not ip line per address inside the same block, and Apache applies them all.
Multiple addresses
<RequireAll>
Require all granted
Require not ip 203.0.113.50
Require not ip 198.51.100.22
Require not ip 192.0.2.7
</RequireAll>
On a server still running the 2.2 branch, the equivalent uses the legacy family. Treat this as a stopgap while you plan an upgrade rather than a design choice, and remember that this whole approach sits at one specific layer — our overview of blocking at every network layer covers when the web server is the right place at all.
Apache 2.2 — legacy only
Order Allow,Deny
Allow from all
Deny from 203.0.113.50
Blocking Only Form Submissions
Sometimes a visitor should keep reading but stop posting. Apache's Limit container restricts a rule to specific HTTP methods, so you can refuse POST requests from an address while leaving normal browsing untouched. Comment spam and contact-form abuse both fit this pattern better than a full block, because the offender never sees an error page and rarely works out what changed.
Block posting only
<Limit POST>
<RequireAll>
Require all granted
Require not ip 203.0.113.50
</RequireAll>
</Limit>
Use Limit carefully
A Limit container restricts a rule to the methods you name, which means every other method stays open. Apache also documents that a Limit block placed inside a Location section can silently override access restrictions set on a Directory section. Use it for narrow jobs like stopping form posts, and never as your main access control.
Both versions work in isolation. Putting them in the same configuration is where things break, and the failure is quieter than you would expect.
What Happens If You Mix Old and New Directives?
Mixing produces a block that appears correct and does not apply. Apache's upgrade documentation is explicit that combining Order, Allow or Deny with Require is technically possible but discouraged, because mod_access_compat directives can take precedence over mod_authz_host ones during configuration merging. The result is a legacy rule quietly overriding a modern one, with a 200 in your access log where you expected a 403.
Apache publishes a worked example of exactly this in its upgrading guide: a directory secured with "Require all denied" still serves content because a Location block further down uses the old Order and Allow syntax. Nothing errors. Nothing warns you. The page simply loads.
The rule that prevents this
Pick one family and convert everything at once. Apache's own guidance is to either keep the old directives for now and migrate later, or migrate the whole configuration in bulk — never both at the same time. Check your existing .htaccess, your virtual host, and any plugin-generated blocks before adding new syntax. Security plugins are a common source of legacy directives you did not write.
Single addresses handled, most real problems arrive as a group rather than one at a time.
How Do You Block an IP Range or Whole Subnet?
Use CIDR notation, meaning a network IP address followed by a slash and a prefix length such as 198.51.100.0/24, directly inside the same Require directive. Apache accepts a full address, a partial address, a network and netmask pair, or CIDR, for both IPv4 and IPv6. CIDR is the clearest of the four because the prefix length states exactly how many addresses you are removing.
Blocking ranges
<RequireAll>
Require all granted
Require not ip 198.51.100.0/24
Require not ip 203.0.113
Require not ip 2001:db8::/32
</RequireAll>
| Prefix | Addresses Removed | Reasonable To Use When |
|---|---|---|
| /32 | 1 | One repeat offender — the default choice |
| /28 | 16 | A small block assigned to one customer |
| /24 | 256 | Logs show rotation across one subnet |
| /16 | 65,536 | Rarely — this is where outages start |
Arbitrary ranges rarely map onto a single prefix. Blocking everything between two addresses usually needs several CIDR blocks stitched together, which is arithmetic worth automating — convert the span with a range to CIDR converter rather than guessing. If prefix lengths are unfamiliar territory, our explainer on how masks divide address space makes the maths concrete.
Denying traffic is only half the job. Sometimes the safer instruction is the opposite one.
How Do You Allow Only Your Own IP and Block Everyone Else?
Swap RequireAll for RequireAny and list every IP address that should get through. RequireAny grants access when at least one requirement passes, so anyone not on the list receives a 403. Administrators use this to fence off sensitive paths such as wp-admin, phpmyadmin or a staging directory, and it inverts the maths in your favour: you maintain a short list of known-good addresses instead of an endless list of known-bad ones.
Allowlist — place in the folder you are protecting
<RequireAny>
Require ip 203.0.113.42
Require ip 198.51.100.0/24
</RequireAny>
Before you save this file
Confirm your own current address and add it first. Home broadband addresses change on reconnection, so an allowlist built around a dynamic address locks you out the next time your router restarts. Either keep a second access route open, such as your host's file manager, or restrict by path rather than by address if your connection is not static.
Whichever direction you choose, the risk is the same: the wrong visitor disappears and nobody tells you.
Who Might You Be Blocking by Accident?
Search crawlers, shared connections and your own future self. Blocking an IP address range that contains genuine Googlebot removes pages from search results, and a user agent string proves nothing, because anything at all can claim to be a crawler. Mobile carriers and corporate networks put hundreds of people behind a single IP address, so one abusive visitor can cost you an entire office.
Verify Crawlers Before You Deny Them
Fake Googlebot traffic is common, and blocking the real one is far more expensive than tolerating the fake. Confirm ownership through forward-confirmed reverse DNS or the operator's published address ranges before writing anything into .htaccess. Our walkthrough on checking a crawler is genuine before you deny it covers both methods properly.
- → Payment and API callbacks. Gateways and webhooks arrive from provider ranges that look like anonymous datacentre traffic. Blocking one silently breaks checkout.
- → Uptime and security monitors. Their probes look automated because they are. Losing them means losing your early warning system.
- → Your own office or VPN. Staff routed through one gateway share one address, so a single complaint can remove your whole team.
Assuming you avoided all of that, plenty of correct-looking rules still refuse to work. Three causes account for nearly all of them.
Why Isn't Your .htaccess Block Working?
Check three things in order: whether Apache reads the file at all, whether your server actually sees the visitor's real IP address, and whether something upstream answers before Apache does. Syntax is rarely the culprit here. In my experience the file is either ignored entirely or matching the wrong IP address, and both faults produce the identical symptom of blocked traffic sailing straight through.
| Symptom | Likely Cause | Fix |
|---|---|---|
| Nothing at all happens | AllowOverride set to None — the 2.4 default | Set AllowOverride to All or AuthConfig in the vhost |
| Blocked visitor still arrives | A CDN sits in front, so Apache sees the CDN address | Block at the CDN, or configure mod_remoteip first |
| Everyone gets a 403 | RequireAny used where RequireAll was needed | Check which container logic you wrote |
| Works on some pages only | A deeper folder has its own .htaccess | Search subdirectories for conflicting files |
| Server error 500 | Directive from a module that is not loaded | Check the error log for "Invalid command" |
Five-Step Diagnostic
1 Prove Apache reads the file
Type deliberate nonsense on the first line and reload the site. A 500 error means the file is being read. A normal page means AllowOverride is blocking it and your syntax was never the issue.
2 Check which address your server logs
Open the access log and look at the addresses recorded. If every visitor shows the same handful, a proxy or CDN sits in front and you are blocking the wrong thing entirely. Restoring the real client address needs mod_remoteip, configured with your provider's trusted ranges.
3 Hunt for competing files
Apache merges .htaccess files down the directory tree. A forgotten file inside a subfolder, or one written by a security plugin, can override what you just added.
4 Look for mixed syntax families
Search every configuration file for both Order and Require. Finding both means the legacy directives may be winning the merge, exactly as Apache's upgrade guide documents.
5 Test from outside your own network
Never confirm a block from your own connection. Use a different network or a proxy, and remember the blocked visitor sees a bare 403 page — our guide on what a blocked visitor actually experiences shows what they get.
Step one settles most cases within a minute. If everything checks out and the rule works, one question remains worth asking.
When Is .htaccess the Wrong Place to Block?
Once your IP address list stops being short. Apache reads .htaccess on every request to the directory, then walks parent directories looking for more, so each rule costs a fraction of every page load your genuine visitors make. A dozen entries cost nothing measurable. A few hundred ranges become a tax you pay forever to inconvenience people who will rotate addresses next week.
- → Move to the firewall when you want to stop traffic before it reaches Apache at all, or when you need to block ports other than 80 and 443.
- → Move to the CDN or WAF when a proxy already sits in front, since Apache cannot see the real visitor from behind it anyway.
- → Move to httpd.conf if you control the server. Main configuration is parsed once at startup rather than on every request.
Country-level blocking belongs elsewhere too. Listing a nation's ranges in .htaccess produces thousands of lines and a slow site, and shared hosting rarely offers the GeoIP module that would do it properly. Hardening the wider setup pays better than growing the file, which our guide on reducing what attackers can reach covers in detail.
That is the full picture for the web server layer. Here is what to carry away.
The Short Version
Block an IP address in .htaccess using RequireAll with Require all granted and Require not ip beneath it. Reach for Order and Deny only on a server still running Apache 2.2, and never run both families in one configuration. Apache itself documents a case where the legacy directives silently win the merge and keep serving content you were certain you had denied.
IP address ranges use CIDR in the same directive, and arbitrary spans usually need several prefixes rather than one. Verify any crawler before denying it, because blocking the genuine Googlebot costs far more than tolerating an impostor, and check your own IP address before building an allowlist on a dynamic connection.
When a correct-looking rule does nothing, test whether Apache reads the file before you touch the syntax. AllowOverride defaults to None on 2.4, and a CDN in front means your server never sees the visitor you are trying to stop. Keep the list short, and once it grows past a handful of entries, move the job up a layer — the reasons address-based filtering has limits apply here more than anywhere.
Check It Before You Deny It
See what else lives on that address and whether it already carries an abuse reputation. Two minutes of checking prevents the outage you would spend an afternoon unpicking. Free, instant, no account.