Skip to content

Bits of .NET

Daily micro-tips for C#, SQL, performance, and scalable backend engineering.

  • Asp.Net Core
  • C#
  • SQL
  • JavaScript
  • CSS
  • About
  • ErcanOPAK.com
  • No Access
  • Privacy Policy
Wordpress

WordPress: Limit Login Attempts Without Plugins Using .htaccess

- 03.02.26 - ErcanOPAK

Brute force bots hammering wp-login.php with 1000s of password attempts? Block them at Apache level before they even reach WordPress.

The Plugin-Free Solution:

Add to .htaccess in WordPress root:

# Limit wp-login.php access to specific IPs
<Files wp-login.php>
    Order Deny,Allow
    Deny from all
    Allow from YOUR_IP_ADDRESS
    Allow from YOUR_OFFICE_IP
</Files>

Replace YOUR_IP_ADDRESS with your actual IP.

Why This Works:

Apache blocks requests BEFORE PHP loads. Brute force bots get 403 Forbidden instantly:

With Login Limiter Plugin:
1. Apache receives request
2. PHP starts  
3. WordPress loads
4. Plugin checks IP
5. Plugin blocks
= Server still did work for 1-3

With .htaccess:
1. Apache receives request
2. Apache blocks (if IP not allowed)
= 90% less server load

Dynamic IP Problem? Use fail2ban Instead:

If your IP changes, .htaccess whitelist won’t work. Use fail2ban to auto-block after failed attempts:

# /etc/fail2ban/filter.d/wordpress-auth.conf
[Definition]
failregex = ^<HOST> .* "POST /wp-login.php
ignoreregex =

# /etc/fail2ban/jail.local
[wordpress-auth]
enabled = true
filter = wordpress-auth
logpath = /var/log/apache2/access.log
maxretry = 3
findtime = 600
bantime = 3600

After 3 failed logins in 10 minutes → IP banned for 1 hour

Alternative: Rate Limit wp-login.php

Limit requests to 5 per minute per IP:

<IfModule mod_rewrite.c>
    RewriteEngine On
    
    # Rate limit wp-login.php  
    RewriteCond %{REQUEST_URI} ^/wp-login\.php$
    RewriteCond %{HTTP:X-Forwarded-For} !^$
    RewriteRule ^(.*)$ - [F,L]
    
    # Allow only 5 requests per minute
    RewriteCond %{REQUEST_URI} ^/wp-login\.php$
    RewriteCond %{REMOTE_ADDR} ^(.*)$
    RewriteRule ^(.*)$ - [E=REMOTE_ADDR:%1]
</IfModule>

# Use mod_evasive or mod_qos for true rate limiting

Whitelist Your Country Only:

If all your users are from one country, block all others:

<Files wp-login.php>
    Order Deny,Allow
    Deny from all
    
    # Allow USA only (use GeoIP database)
    SetEnvIf GEOIP_COUNTRY_CODE US AllowCountry
    Allow from env=AllowCountry
</Files>

Requires mod_geoip. Most attacks come from outside your country!

Check Attack Logs:

# See failed login attempts
grep "wp-login.php" /var/log/apache2/access.log | grep "POST" | wc -l

# See unique IPs attacking
grep "wp-login.php" /var/log/apache2/access.log | awk '{print $1}' | sort | uniq -c | sort -rn

# Typical output:
# 2847 requests from 185.xxx.xxx.xxx
# 1923 requests from 91.xxx.xxx.xxx  
# = Bot networks hammering login

Performance Comparison:

1000 bot login attempts per hour:

With Plugin (Wordfence, Limit Login Attempts):
- All 1000 requests hit PHP
- WordPress loads 1000 times
- Plugin checks IP 1000 times
- Database queries: 3000-5000
- Server load: HIGH

With .htaccess:
- Apache blocks 1000 requests
- 0 PHP executions
- 0 WordPress loads
- 0 database queries
- Server load: MINIMAL

Bonus: Hide wp-login.php Entirely

Rename login page (requires plugin like “WPS Hide Login” or manual .htaccess):

# Redirect wp-login.php to custom URL
RewriteRule ^wp-login\.php$ /my-secret-login [R=301,L]
RewriteRule ^my-secret-login$ /wp-login.php [L]

Bots keep attacking /wp-login.php (404), your real login is /my-secret-login

Related posts:

WordPress: Hardening Security with .htaccess Headers

WordPress: Add Custom Code Without Editing Theme Files Using Code Snippets Plugin

WordPress REST API: Turn Your Site into a Headless CMS for React/Vue Apps

Post Views: 4

Post navigation

WordPress: Speed Up Admin Dashboard by Disabling Heartbeat API
Kubernetes: Force Delete Stuck Pods in Terminating State Instantly

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

March 2026
M T W T F S S
 1
2345678
9101112131415
16171819202122
23242526272829
3031  
« Feb    

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (938)
  • How to add default value for Entity Framework migrations for DateTime and Bool (836)
  • Get the First and Last Word from a String or Sentence in SQL (828)
  • How to select distinct rows in a datatable in C# (801)
  • How to make theater mode the default for Youtube (736)
  • Add Constraint to SQL Table to ensure email contains @ (575)
  • How to enable, disable and check if Service Broker is enabled on a database in SQL Server (554)
  • Average of all values in a column that are not zero in SQL (523)
  • How to use Map Mode for Vertical Scroll Mode in Visual Studio (477)
  • Find numbers with more than two decimal places in SQL (441)

Recent Posts

  • C#: Saving Memory with yield return (Lazy Streams)
  • C#: Why Records are Better Than Classes for Data DTOs
  • C#: Creating Strings Without Memory Pressure with String.Create
  • SQL: Protecting Sensitive Data with Dynamic Data Masking
  • SQL: Writing Readable Queries with Common Table Expressions (CTE)
  • .NET Core: Handling Errors Gracefully with Middleware
  • .NET Core: Mastering Service Lifetimes (A Visual Guide)
  • Git: Surgical Stashing – Don’t Save Everything!
  • Git: Writing Commits That Your Future Self Won’t Hate
  • Ajax: Improving Perceived Speed with Skeleton Screens

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (938)
  • How to add default value for Entity Framework migrations for DateTime and Bool (836)
  • Get the First and Last Word from a String or Sentence in SQL (828)
  • How to select distinct rows in a datatable in C# (801)
  • How to make theater mode the default for Youtube (736)

Recent Posts

  • C#: Saving Memory with yield return (Lazy Streams)
  • C#: Why Records are Better Than Classes for Data DTOs
  • C#: Creating Strings Without Memory Pressure with String.Create
  • SQL: Protecting Sensitive Data with Dynamic Data Masking
  • SQL: Writing Readable Queries with Common Table Expressions (CTE)

Social

  • ErcanOPAK.com
  • GoodReads
  • LetterBoxD
  • Linkedin
  • The Blog
  • Twitter
© 2026 Bits of .NET | Built with Xblog Plus free WordPress theme by wpthemespace.com