Blocking Automated Attacks with Nginx

Every server exposed to the internet gets hammered by automated scanners. Yesterday, I noticed suspicious traffic hitting my Go API and decided to dig into the logs. What I found was a textbook example of automated vulnerability scanning—and here’s how I blocked it with nginx.

The Attack Pattern

Looking at my server logs, I spotted several distinct attack signatures:

Cisco VPN Exploits

GET /+CSCOL+/Java.jar
GET /+CSCOE+/logon_forms.js

These probe for vulnerabilities in Cisco AnyConnect VPN appliances. Attackers hope you’re running an unpatched Cisco device.

PHPUnit Remote Code Execution (CVE-2017-9841)

GET /vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
GET /laravel/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
GET /api/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php

This is a classic—scanners try dozens of path variations hoping to find an exposed PHPUnit installation that allows arbitrary code execution.

Spring Boot Actuator Probes

GET /actuator/health
GET /debug/pprof/

Exposed actuator endpoints can leak sensitive information or allow remote code execution in misconfigured Spring applications.

Generic Path Enumeration

GET /aaa9
GET /ab2g
GET /alive.php
GET /geoserver/web/

Shotgun approach—try common paths and see what sticks.

Why Block If You’re Not Running PHP?

My API is written in Go. None of these PHP or Java exploits can actually harm it. So why bother?

  • Log noise — Thousands of 404s make it harder to spot real issues
  • Resource usage — Every request consumes CPU and bandwidth
  • Attack surface — Today’s scanner might find tomorrow’s misconfiguration
  • Professionalism — Clean logs, clean infrastructure

The Nginx Solution

Here’s the configuration I deployed:

# Rate limiting zone (add to http block)
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

server {
    server_name api.example.com;

    # Apply rate limiting
    limit_req zone=api_limit burst=20 nodelay;

    # Block requests by raw IP (scanners rarely use hostnames)
    if ($host ~* "^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$") {
        return 444;
    }

    # Block empty user agents
    if ($http_user_agent = "") {
        return 444;
    }

    # Block hidden files (.env, .git, etc.)
    location ~ /\. {
        access_log off;
        return 444;
    }

    # Block exploit paths
    location ~* (phpunit|eval-stdin|CSCOE|CSCOL|actuator|geoserver|phpinfo|\.php$|\.jar$|\.jsp$) {
        access_log off;
        return 444;
    }

    # Block common scanner paths
    location ~* ^/(sdk|cgi-bin|webui|evox|dana-na|wp-|wordpress|console|debug|admin\.php) {
        access_log off;
        return 444;
    }

    # Block sensitive file extensions
    location ~* \.(env|git|config|ini|log|sql|bak|old|swp)$ {
        access_log off;
        return 444;
    }

    # Your actual application
    location / {
        proxy_pass http://localhost:7020;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Key Techniques Explained

Return 444 Instead of 403/404

return 444;

The 444 response is nginx-specific. It closes the connection immediately without sending any response. This wastes the scanner’s time, reveals nothing about your server, and uses minimal resources.

Silence the Logs

access_log off;

Blocked requests don’t need to be logged. This keeps your access logs clean and meaningful.

Block Raw IP Access

if ($host ~* "^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$") {
    return 444;
}

Legitimate users access your site via domain name. Scanners often hit raw IPs directly. This single rule eliminates a huge chunk of bot traffic.

Rate Limiting

limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req zone=api_limit burst=20 nodelay;

Even requests that slip through get rate-limited. 10 requests per second with a burst of 20 is generous for legitimate users but throttles aggressive scanners.

Testing Your Configuration

After deploying, verify it works:

# Should get no response (connection dropped)
curl -v https://your-domain.com/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php

# Should get no response
curl -v -A "" https://your-domain.com/

# Should work normally
curl -v https://your-domain.com/

If blocking works correctly, you’ll see “Empty reply from server” for the malicious requests.

Results

After deploying these rules:

  • Scanner requests no longer reach my Go application
  • Access logs contain only legitimate traffic
  • Server resources freed up from processing junk requests

Wrap-Up

You can’t stop people from scanning your server, but you can make their efforts futile. A few nginx rules block the vast majority of automated attacks before they ever reach your application.

The internet is hostile. Your nginx config doesn’t have to be a welcome mat.

Need help securing your infrastructure or building robust backend services? Reach out to the team at Datum Brain @ build@datumbrain.com.