Pv6 Client Blocking Issue on IPv4-Only Server (DirectAdmin + Nginx + ModSecurity + CSF + Cloudflare)

castris

Verified User
Joined
Apr 16, 2021
Messages
153
Location
Arcenillas
IPv6 Client Blocking Issue on IPv4-Only Server (DirectAdmin + Nginx + ModSecurity + CSF + Cloudflare)



1. PROBLEM SUMMARY

On a DirectAdmin server with Nginx + ModSecurity + CSF, when traffic arrives through Cloudflare from IPv6 clients, the system only logs and processes the original client's IPv6 address.

ISSUE: The server does NOT have IPv6 enabled at the network level, so CSF firewall CANNOT effectively block these IPv6 addresses, leaving the server vulnerable to repeated attacks from the same IPs.

EXPECTED BEHAVIOR: Nginx/ModSecurity should be able to obtain or generate a usable IPv4 address for firewall blocking, since:
  • DirectAdmin and Nginx document Cloudflare support
  • Backend server only operates with IPv4
  • CSF needs IPv4 addresses for effective blocking



2. SYSTEM CONFIGURATION

Operating System: Ubuntu 24.04.3 LTS
Nginx: nginx/1.29.3 (OpenSSL 3.0.13)
ModSecurity: v3.0.14 with connector nginx v1.0.4
OWASP CRS: 4.20.0
CSF: v15.05 (DirectAdmin)
DirectAdmin: CustomBuild

Network configuration:
  • Server only has IPv4: 5.135.93.85/26
  • NO global IPv6 addresses configured
  • CSF configured with IPV6 = "0"

Nginx configuration (/etc/nginx/nginx-cloudflare.conf):
Code:
real_ip_header X-Forwarded-For;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 104.16.0.0/13;
[... Cloudflare IPv4 ranges ...]
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2a06:98c0::/29;
[... other Cloudflare IPv6 ranges ...]



3. CURRENT BEHAVIOR (PROBLEMATIC)

Connection flow:
  1. Client with IPv6 (e.g., 2a06:98c0:3600::103) → Cloudflare
  2. Cloudflare → Server (IPv4 connection from Cloudflare range)
  3. Cloudflare sends headers: X-Forwarded-For: 2a06:98c0:3600::103
  4. Nginx processes real_ip_header and obtains: 2a06:98c0:3600::103
  5. ModSecurity detects attack and logs IP: 2a06:98c0:3600::103
  6. ModSecurity/LFD adds to CSF: 2a06:98c0:3600::103
  7. CSF CANNOT block (server without functional IPv6)

Evidence in logs:

Nginx access log:
Code:
2a06:98c0:3600::103 - - [22/Nov/2025:06:01:18 +0100] "GET /wordpress/wp-admin/setup-config.php HTTP/2.0" 406 558

ModSecurity error log:
Code:
[client 2a06:98c0:3600::103] ModSecurity: Access denied with code 406 (phase 2)
[id "949110"] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"]

CSF deny list:
Code:
2a06:98c0:3600::103 # BFM: mod_security1=1211 - Sat Nov 22 04:01:32 2025

Result:
  • NO effective rule for 2a06:98c0:3600::103 in ip6tables
  • Server does not accept IPv6 connections
  • CSF blocking is completely USELESS

Attack statistics:
  • 72+ attempts blocked by ModSecurity from same IPv6
  • 0 effective firewall-level blocks
  • Attacker can continue indefinitely



4. EXPECTED BEHAVIOR

OPTION 1 (PREFERRED): Cloudflare Pseudo IPv4
  • Cloudflare has "Pseudo IPv4" feature that generates consistent IPv4 from IPv6
  • Nginx should be able to receive this IPv4 instead of/in addition to IPv6
  • Question: Is there Nginx configuration to prefer IPv4 when available?

OPTION 2: ModSecurity/Nginx converts IPv6 to IPv4 identifier
  • Generate consistent hash/transformation from IPv6 → IPv4
  • Allow effective blocking in IPv4-only firewall

OPTION 3: Clear documentation
  • If obtaining IPv4 is NOT possible, clearly document that:
    • IPv4-only servers CANNOT effectively block IPv6 clients
    • CSF/ModSecurity must be configured differently in these scenarios
    • IPv6 on server or alternative solutions required (rate limiting)



5. SPECIFIC TECHNICAL QUESTIONS

For DirectAdmin:
  1. How should nginx-cloudflare.conf be configured on IPv4-only servers?
  2. Is there integration with Cloudflare Pseudo IPv4?
  3. How to handle ModSecurity blocks when CSF cannot block IPv6?

For Nginx:
  1. Can real_ip_header prefer IPv4 when multiple IPs are available?
  2. Is there a module/configuration to convert IPv6 to IPv4-blockable format?
  3. Is it possible to configure different real_ip_header per vhost in shared hosting?



6. CURRENT WORKAROUNDS (SUBOPTIMAL)

Temporary #1: Disable LF_MODSEC_PERM
  • CSF doesn't add permanent ModSecurity blocks
  • ModSecurity still blocks each attempt (406)
  • Attacker can retry indefinitely

Temporary #2: Rate limiting in Nginx
  • Limit requests per IP (works with IPv6)
  • Not real blocking, just throttling
  • More server load

Temporary #3: Disable nginx-cloudflare.conf
  • Only viable if NO domain uses Cloudflare proxy
  • Loses functionality for domains that do use Cloudflare



7. REQUEST

Please provide:
  1. Recommended solution or configuration for this scenario
  2. Documentation on limitations of IPv4-only servers with Cloudflare
  3. Roadmap if this is a pending feature to implement

I'm available to provide more information, logs, or perform tests.
 
Hello,

CSF/LFD can not block users IPs from accessing your server if traffic is proxied over CloudFlare. You might configure CSF/LFD to block IPs using CloudFlare API though.
 
Solved - It was a Nginx logging issue, not Cloudflare

Thanks for the reply, but the issue wasn't about CSF/Cloudflare API integration.

After deeper analysis, the problem was simpler: Nginx's real_ip module replaces $remote_addr with the header value. When X-Forwarded-For contains an IPv6, that's what gets logged - even on an IPv4-only server.

The actual TCP connection is always IPv4 (from Cloudflare edge nodes), but logs showed the header content instead.

Solution: Use $realip_remote_addr variable, which preserves the original TCP connection IP before the real_ip module processes it.


I'll post the full DirectAdmin/Nginx implementation below.

Markdown (GitHub flavored):
# Nginx IPv4 Logging Issue with Cloudflare real_ip Module

## Problem Description

When using Nginx with the `real_ip` module configured for Cloudflare, the access logs show IPv6 addresses from the `X-Forwarded-For` header instead of the actual TCP connection IPv4 address.

### Environment
- Server has **no IPv6** configured (IPv4 only)
- Nginx listens only on IPv4 addresses
- Cloudflare configuration is active via `/etc/nginx/nginx-cloudflare.conf`
- Uses `real_ip_header X-Forwarded-For;` directive

### Issue
The access logs display:
```
2a06:98c0:3600::103 - - [01/Dec/2025:11:19:00 +0100] "GET /wp-admin/setup-config.php HTTP/2.0" 406 558
```

This is misleading because:
1. The server doesn't support IPv6 connections
2. The actual TCP connection is IPv4 (from Cloudflare's servers)
3. The logged IPv6 address comes from the HTTP header, not the TCP connection
4. This makes it impossible to identify the real connecting IP for security/firewall purposes

### Root Cause
The `ngx_http_realip_module` replaces `$remote_addr` with the value from `X-Forwarded-For` header. When logs use `$remote_addr`, they show the header value (potentially IPv6) instead of the actual TCP connection IP (always IPv4 on this server).

## Solution

Create a custom log format that logs **both** the TCP connection IP and the forwarded IP.

### DirectAdmin-Compatible Implementation

#### Step 1: Add Custom Log Format

Add the custom log format to `/etc/nginx/nginx-includes.conf` (this file persists through DirectAdmin rebuilds):

```bash
# Edit the file
vi /etc/nginx/nginx-includes.conf
```

Add this line:
```nginx
log_format combined_realip '$realip_remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"';
```

**Variable explained:**
- `$realip_remote_addr` - The actual TCP connection IPv4 address (before real_ip module processes it)

#### Step 2: Create Custom Nginx Template

To ensure access logs use this format across all domains after rebuilds:

```bash
# Create custom templates directory if it doesn't exist
mkdir -p /usr/local/directadmin/data/templates/custom

# Copy the nginx server template
cd /usr/local/directadmin/data/templates/custom
cp ../nginx_server.conf .
cp ../nginx_server_secure.conf .
```

#### Step 3: Modify the Templates

Edit both files to use the new log format:

```bash
# Edit nginx_server.conf
vi nginx_server.conf
```

Find lines like:
```nginx
access_log /var/log/nginx/domains/|DOMAIN|.log;
```

Change to:
```nginx
access_log /var/log/nginx/domains/|DOMAIN|.log combined_realip;
```

Repeat for `nginx_server_secure.conf`.

#### Step 4: Rebuild Configurations

```bash
# Test nginx configuration
nginx -t

# Rebuild DirectAdmin configurations
da build rewrite_confs

# Reload nginx
systemctl reload nginx
```

### Result

After implementation, access logs will show:

```
172.67.145.23 - - [01/Dec/2025:13:11:23 +0100] "GET /wp-admin/setup-config.php HTTP/2.0" 406 558 "-" "Mozilla/5.0..."
```

Where:
- `172.67.145.23` = Real IPv4 TCP connection (Cloudflare or direct client connection)

## Benefits

1. **Firewall accuracy**: You can now block/allow based on the actual connecting IPv4
2. **Security monitoring**: See the real TCP connection IP
3. **Efficient logging**: No redundant information when IPs are identical
4. **Update-proof**: Custom templates in DirectAdmin persist through updates
5. **IPv4-only visibility**: Always shows IPv4 even when clients use IPv6 (via proxy)

## Verification

Test the configuration:
```bash
# Check if custom log format is loaded
nginx -T | grep combined_realip

# Monitor new log entries
tail -f /var/log/nginx/domains/yourdomain.com.log
```

## Notes

- The `/etc/nginx/nginx-includes.conf` file is **not** overwritten by DirectAdmin rebuilds
- Custom templates in `/usr/local/directadmin/data/templates/custom/` persist through updates
- This solution maintains full compatibility with Cloudflare's IP restoration while adding TCP-level visibility
- ModSecurity logs will continue to show the X-Forwarded-For IP as expected

## Alternative: Quick Fix for Single Domain

If you only need to fix one domain without modifying templates:

1. Go to DirectAdmin: **Admin Level → Custom HTTPD Configs**
2. Select your domain
3. In the Nginx configuration section, modify the `access_log` directive:
```nginx
access_log /var/log/nginx/domains/yourdomain.com.log combined_realip;
```
4. Save and reload nginx

**Note:** This method requires re-applying after each rebuild for that domain.

## References

- [DirectAdmin Nginx Customization](https://docs.directadmin.com/webservices/nginx/customizing-nginx.html)
- [Nginx ngx_http_realip_module Documentation](http://nginx.org/en/docs/http/ngx_http_realip_module.html)

Side note: Interestingly, both myself and several AI assistants initially went down the Cloudflare rabbit hole. The IPv6 in logs + Cloudflare in the setup made it seem like a proxy/header trust issue. Reality was more mundane: just a logging variable choice.


Still unclear: I have 10 IPv4-only servers with similar configs. Only 2 showed this behavior recently. I haven't identified what differs - possibly a Nginx update changed default behavior, or something in the real_ip module loading order. If anyone has insights, I'd appreciate it.



 
I might be misunderstanding something here, but if you block the connecting IPv4 address in an IP based firewall, and the connections comes through Cloudflare, you will be blocking Cloudflare. And by extension any *actual* source that goes through that same blocked edge node.
 
Cloudflare are great with their DDOS protection, but all i've ever seen is people having problems with CF accounts and things breaking when CF is in the mix. I've never really seen anyone post anything positive about it to be fair.
 
Back
Top