<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Log Audit</title>
    <description>The latest articles on DEV Community by Log Audit (@logaudit).</description>
    <link>https://dev.to/logaudit</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3834285%2F82d85925-4f35-44b0-b8e1-a79420171699.png</url>
      <title>DEV Community: Log Audit</title>
      <link>https://dev.to/logaudit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/logaudit"/>
    <language>en</language>
    <item>
      <title>Apache vs Nginx Log Analysis: Security Patterns to Watch</title>
      <dc:creator>Log Audit</dc:creator>
      <pubDate>Fri, 03 Apr 2026 08:15:47 +0000</pubDate>
      <link>https://dev.to/logaudit/apache-vs-nginx-log-analysis-security-patterns-to-watch-fi0</link>
      <guid>https://dev.to/logaudit/apache-vs-nginx-log-analysis-security-patterns-to-watch-fi0</guid>
      <description>&lt;h1&gt;
  
  
  Apache vs Nginx Log Analysis: Security Patterns to Watch
&lt;/h1&gt;

&lt;p&gt;Most security log analysis guides focus on one or the other. But many production environments run both — Nginx as a reverse proxy in front of Apache, or Apache handling legacy apps while Nginx serves new services. Understanding the differences matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Log Format Differences
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Nginx Default Combined Format
&lt;/h3&gt;

&lt;p&gt;\&lt;/p&gt;

</description>
      <category>nginx</category>
      <category>apache</category>
      <category>security</category>
      <category>devops</category>
    </item>
    <item>
      <title>Top 5 Security Events Hidden in Your Server Logs</title>
      <dc:creator>Log Audit</dc:creator>
      <pubDate>Fri, 03 Apr 2026 08:15:11 +0000</pubDate>
      <link>https://dev.to/logaudit/top-5-security-events-hidden-in-your-server-logs-db0</link>
      <guid>https://dev.to/logaudit/top-5-security-events-hidden-in-your-server-logs-db0</guid>
      <description>&lt;h1&gt;
  
  
  Top 5 Security Events Hidden in Your Server Logs
&lt;/h1&gt;

&lt;p&gt;Your server logs are a security goldmine — and a graveyard. They contain evidence of every attack, every reconnaissance probe, every failed intrusion attempt. They also contain evidence of successful ones that you haven't noticed yet.&lt;/p&gt;

&lt;p&gt;Here are the 5 most dangerous security events that hide in plain sight in your logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Slow Brute Force Attacks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What they look like:&lt;/strong&gt; A single IP making 1–2 login attempts per hour. Slow enough to bypass rate limits. Patient enough to fly under the radar for weeks.&lt;/p&gt;

&lt;p&gt;\&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>monitoring</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>What Logs Do You Need for GDPR Compliance?</title>
      <dc:creator>Log Audit</dc:creator>
      <pubDate>Fri, 03 Apr 2026 08:14:35 +0000</pubDate>
      <link>https://dev.to/logaudit/what-logs-do-you-need-for-gdpr-compliance-409f</link>
      <guid>https://dev.to/logaudit/what-logs-do-you-need-for-gdpr-compliance-409f</guid>
      <description>&lt;h1&gt;
  
  
  What Logs Do You Need for GDPR Compliance?
&lt;/h1&gt;

&lt;p&gt;GDPR doesn't have a single "you must log X" requirement — but Articles 5, 25, 30, and 32 together create a clear picture of what logging you need to demonstrate compliance. Get it wrong and you're not just non-compliant, you may be logging PII you shouldn't be.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Two Logging Challenges Under GDPR
&lt;/h2&gt;

&lt;p&gt;GDPR creates a double-edged problem for logging:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;You must log&lt;/strong&gt; — to demonstrate accountability, detect breaches, and respond to access requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You must not over-log&lt;/strong&gt; — logging PII unnecessarily violates data minimisation principles&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This tension is why GDPR logging is tricky.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You're Required to Log
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Article 30 — Records of Processing Activities
&lt;/h3&gt;

&lt;p&gt;You need to maintain a record of all data processing activities. For logging purposes, this means documenting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who accesses personal data&lt;/li&gt;
&lt;li&gt;When they access it&lt;/li&gt;
&lt;li&gt;For what purpose&lt;/li&gt;
&lt;li&gt;From which system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't necessarily in your server logs — but your server logs are evidence that supports this record.&lt;/p&gt;

&lt;h3&gt;
  
  
  Article 32 — Security of Processing
&lt;/h3&gt;

&lt;p&gt;You must implement "appropriate technical measures" including "a process for regularly testing, assessing and evaluating the effectiveness of technical and organisational measures."&lt;/p&gt;

&lt;p&gt;In practice: you need logs that let you detect and investigate security incidents.&lt;/p&gt;

&lt;h3&gt;
  
  
  Article 33 — Breach Notification
&lt;/h3&gt;

&lt;p&gt;You have 72 hours to notify your supervisory authority of a breach. Without logs, you can't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Confirm a breach occurred&lt;/li&gt;
&lt;li&gt;Determine what data was affected&lt;/li&gt;
&lt;li&gt;Establish a timeline&lt;/li&gt;
&lt;li&gt;Identify who was impacted&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Minimum Logging Requirements for GDPR
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Authentication Events
&lt;/h3&gt;

&lt;p&gt;Every login to systems that process personal data:&lt;br&gt;
\&lt;/p&gt;

</description>
      <category>gdpr</category>
      <category>compliance</category>
      <category>logging</category>
      <category>security</category>
    </item>
    <item>
      <title>How to Detect Brute Force Attacks in Nginx Access Logs</title>
      <dc:creator>Log Audit</dc:creator>
      <pubDate>Fri, 03 Apr 2026 08:13:43 +0000</pubDate>
      <link>https://dev.to/logaudit/how-to-detect-brute-force-attacks-in-nginx-access-logs-50bh</link>
      <guid>https://dev.to/logaudit/how-to-detect-brute-force-attacks-in-nginx-access-logs-50bh</guid>
      <description>&lt;h1&gt;
  
  
  How to Detect Brute Force Attacks in Nginx Access Logs
&lt;/h1&gt;

&lt;p&gt;Brute force attacks are relentless. Automated bots hammer login endpoints thousands of times per minute hoping to hit a weak password. Your Nginx access logs record every attempt — here's how to find them before the attackers succeed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Brute Force Attack Looks Like
&lt;/h2&gt;

&lt;p&gt;A single IP hammering your login endpoint:&lt;/p&gt;

&lt;p&gt;\&lt;/p&gt;

</description>
      <category>security</category>
      <category>nginx</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>SOC2 Type II Audit Checklist for SaaS Teams (2026)</title>
      <dc:creator>Log Audit</dc:creator>
      <pubDate>Fri, 03 Apr 2026 08:13:40 +0000</pubDate>
      <link>https://dev.to/logaudit/soc2-type-ii-audit-checklist-for-saas-teams-2026-16pc</link>
      <guid>https://dev.to/logaudit/soc2-type-ii-audit-checklist-for-saas-teams-2026-16pc</guid>
      <description>&lt;h1&gt;
  
  
  SOC2 Type II Audit Checklist for SaaS Teams (2026)
&lt;/h1&gt;

&lt;p&gt;SOC2 Type II is no longer just for enterprise. More and more B2B SaaS buyers are asking for it before they sign. If you're a small team trying to get certified without a dedicated compliance team, this checklist covers what you actually need — with a focus on logging.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is SOC2 Type II?
&lt;/h2&gt;

&lt;p&gt;SOC2 (System and Organization Controls 2) is a framework developed by the AICPA. Type I is a point-in-time snapshot. Type II covers a period of time (usually 6–12 months) and verifies that your controls are operating effectively throughout that period.&lt;/p&gt;

&lt;p&gt;The five Trust Service Criteria are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt; (required)&lt;/li&gt;
&lt;li&gt;Availability&lt;/li&gt;
&lt;li&gt;Processing Integrity&lt;/li&gt;
&lt;li&gt;Confidentiality&lt;/li&gt;
&lt;li&gt;Privacy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most SaaS teams pursue Security + Availability as the minimum scope.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Logging Requirements Auditors Care About
&lt;/h2&gt;

&lt;p&gt;Logging is one of the most scrutinized areas of a SOC2 audit. Here's what auditors look for:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Access Logs
&lt;/h3&gt;

&lt;p&gt;Every authentication event should be logged:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Successful logins (user, IP, timestamp, user-agent)&lt;/li&gt;
&lt;li&gt;Failed login attempts&lt;/li&gt;
&lt;li&gt;Password resets&lt;/li&gt;
&lt;li&gt;MFA challenges&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;\&lt;/p&gt;

</description>
      <category>soc2</category>
      <category>compliance</category>
      <category>security</category>
      <category>devops</category>
    </item>
    <item>
      <title>5 Nginx Log Patterns Every SaaS Developer Should Monitor</title>
      <dc:creator>Log Audit</dc:creator>
      <pubDate>Wed, 01 Apr 2026 10:01:00 +0000</pubDate>
      <link>https://dev.to/logaudit/5-nginx-log-patterns-every-saas-developer-should-monitor-243n</link>
      <guid>https://dev.to/logaudit/5-nginx-log-patterns-every-saas-developer-should-monitor-243n</guid>
      <description>&lt;p&gt;Most SaaS applications run behind Nginx, and most teams only look at their logs when something breaks. That is a mistake. Your access logs are a real-time feed of what is happening to your application — including attacks, abuse, and infrastructure problems — if you know what to look for.&lt;/p&gt;

&lt;p&gt;Here are five patterns worth monitoring continuously.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Repeated 401/403 Responses to the Same Endpoint
&lt;/h2&gt;

&lt;p&gt;A spike in &lt;code&gt;401 Unauthorized&lt;/code&gt; or &lt;code&gt;403 Forbidden&lt;/code&gt; responses targeting a single endpoint — especially &lt;code&gt;/api/login&lt;/code&gt;, &lt;code&gt;/admin&lt;/code&gt;, or &lt;code&gt;/api/token&lt;/code&gt; — is a strong indicator of brute force or credential stuffing activity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'$9 == "401" || $9 == "403" {print $7}'&lt;/span&gt; /var/log/nginx/access.log &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see hundreds of hits on &lt;code&gt;/api/login&lt;/code&gt; returning 401, an automated attack is almost certainly in progress. Combine with IP analysis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'$9 == "401" {print $1}'&lt;/span&gt; /var/log/nginx/access.log &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A single IP hammering your login endpoint warrants an immediate block via &lt;code&gt;fail2ban&lt;/code&gt; or a firewall rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Abnormal 4xx/5xx Ratios
&lt;/h2&gt;

&lt;p&gt;A healthy application has a low error rate — typically under 1-2%. A sudden spike in &lt;code&gt;500&lt;/code&gt; errors often means a deploy went wrong, a dependency is down, or something is crashing under load. A spike in &lt;code&gt;400&lt;/code&gt; errors can indicate a scanning tool probing malformed requests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $9}'&lt;/span&gt; /var/log/nginx/access.log &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'^[45]'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Track this over time with a rolling window:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 10000 /var/log/nginx/access.log &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $9}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;500&lt;/code&gt; errors appear after a deployment, you want to know within minutes — not hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. High Request Volume from a Single IP
&lt;/h2&gt;

&lt;p&gt;Legitimate users do not send 10,000 requests per hour. Bots, scrapers, and DDoS traffic do. Catching this early lets you rate-limit or block before it impacts paying users.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt; /var/log/nginx/access.log &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a more useful view, filter to the last hour only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="s1"&gt;'+%d/%b/%Y:%H'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s1"&gt;'$4 ~ date {print $1}'&lt;/span&gt; /var/log/nginx/access.log &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pair this with nginx rate limiting in your config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;limit_req_zone&lt;/span&gt; &lt;span class="nv"&gt;$binary_remote_addr&lt;/span&gt; &lt;span class="s"&gt;zone=api:10m&lt;/span&gt; &lt;span class="s"&gt;rate=30r/m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/api/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;limit_req&lt;/span&gt; &lt;span class="s"&gt;zone=api&lt;/span&gt; &lt;span class="s"&gt;burst=10&lt;/span&gt; &lt;span class="s"&gt;nodelay&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Requests to Non-Existent Routes (404 Farming)
&lt;/h2&gt;

&lt;p&gt;A wave of &lt;code&gt;404&lt;/code&gt; responses across random paths — &lt;code&gt;/wp-admin&lt;/code&gt;, &lt;code&gt;/.env&lt;/code&gt;, &lt;code&gt;/phpMyAdmin&lt;/code&gt;, &lt;code&gt;/config.json&lt;/code&gt; — is an automated scanner probing for known vulnerabilities. These are typically harmless if your app does not have those files, but they indicate your server is being actively enumerated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'$9 == "404" {print $7}'&lt;/span&gt; /var/log/nginx/access.log &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-30&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Watch for patterns like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/.env&lt;/code&gt; — environment file leakage probes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp-login.php&lt;/code&gt; — WordPress brute force&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/actuator/health&lt;/code&gt; — Spring Boot endpoint scanning&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/.git/config&lt;/code&gt; — source code exposure attempts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you see these, the traffic is coming from automated tools. Blocking the source IP is reasonable.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Slow Response Times on Critical Paths
&lt;/h2&gt;

&lt;p&gt;Nginx can log request processing time with &lt;code&gt;$request_time&lt;/code&gt;. If your checkout, login, or payment endpoints are suddenly taking 5+ seconds, something is wrong — slow queries, N+1 problems, or resource exhaustion.&lt;/p&gt;

&lt;p&gt;First, enable timing in your log format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;log_format&lt;/span&gt; &lt;span class="s"&gt;timed&lt;/span&gt; &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="nv"&gt;$remote_addr&lt;/span&gt; &lt;span class="s"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$remote_user&lt;/span&gt; &lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$time_local&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt; &lt;span class="s"&gt;'&lt;/span&gt;
               &lt;span class="s"&gt;'"&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;$status&lt;/span&gt; &lt;span class="nv"&gt;$body_bytes_sent&lt;/span&gt; &lt;span class="s"&gt;'&lt;/span&gt;
               &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="nv"&gt;$request_time&lt;/span&gt; &lt;span class="nv"&gt;$upstream_response_time&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;access_log&lt;/span&gt; &lt;span class="n"&gt;/var/log/nginx/access.log&lt;/span&gt; &lt;span class="s"&gt;timed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then find your slowest endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $NF, $7}'&lt;/span&gt; /var/log/nginx/access.log &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A sudden increase in &lt;code&gt;$upstream_response_time&lt;/code&gt; (time your backend took) vs &lt;code&gt;$request_time&lt;/code&gt; (total time) tells you whether the bottleneck is in your app or at the Nginx layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Putting It Together
&lt;/h2&gt;

&lt;p&gt;Monitoring these five patterns manually is a start, but doing it continuously at scale requires automation. You need alerting when error rates spike, when a new IP crosses a request threshold, or when a scanner starts probing your endpoints.&lt;/p&gt;

&lt;p&gt;If you want this without spinning up a full ELK stack, &lt;a href="https://log-audit.com" rel="noopener noreferrer"&gt;LogAudit&lt;/a&gt; runs these checks against your Nginx logs automatically and alerts you when something worth acting on shows up.&lt;/p&gt;

</description>
      <category>nginx</category>
      <category>security</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
    <item>
      <title>GDPR and Your Nginx Logs — What PII Are You Accidentally Logging?</title>
      <dc:creator>Log Audit</dc:creator>
      <pubDate>Thu, 26 Mar 2026 11:46:25 +0000</pubDate>
      <link>https://dev.to/logaudit/gdpr-and-your-nginx-logs-what-pii-are-you-accidentally-logging-769</link>
      <guid>https://dev.to/logaudit/gdpr-and-your-nginx-logs-what-pii-are-you-accidentally-logging-769</guid>
      <description>&lt;h1&gt;
  
  
  GDPR and Your Nginx Logs — What PII Are You Accidentally Logging?
&lt;/h1&gt;

&lt;p&gt;Most developers know GDPR applies to their databases and forms. Few realise their nginx access logs may already be GDPR non-compliant — logging personal data without consent, no retention policy, no protection.&lt;/p&gt;

&lt;p&gt;Here is what your default nginx log line looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;185.22.30.11 - - [26/Mar/2026:08:20:00 +0000] "GET /reset-password?token=abc123&amp;amp;email=john.doe@example.com HTTP/1.1" 200 512
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You just logged an email address. That is personal data under GDPR Article 4. And it is sitting in a plaintext file on your server.&lt;/p&gt;

&lt;h2&gt;
  
  
  What PII Ends Up in Nginx Logs?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Email addresses in query parameters
&lt;/h3&gt;

&lt;p&gt;Password resets, magic links, email verification — all commonly pass email as a URL parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /verify?email=user@example.com&amp;amp;token=xyz
GET /unsubscribe?email=user@example.com
GET /invite?ref=user@example.com
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Names and usernames in URLs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /profile/john.doe
GET /u/jane_smith/settings
GET /invoices/acme-corp-march-2024
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Session tokens and auth tokens
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/data?session=eyJhbGciOiJSUzI1NiJ9...
GET /download?token=sk_live_abc123
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. IP addresses
&lt;/h3&gt;

&lt;p&gt;Under GDPR, IP addresses are considered personal data in most EU member states (CJEU ruling, 2016). Your nginx access log logs every IP by default.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Health and sensitive data in URLs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /doctors/oncology/book?patient=john-doe
GET /therapy/session?user_id=4421
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Health-related data is special category data under GDPR Article 9 — even stricter rules apply.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Log retention&lt;/strong&gt;: GDPR requires data minimisation. Keeping access logs for 90 days "just in case" may not have a lawful basis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log access&lt;/strong&gt;: Who can read your access logs? If it is everyone on your team, you may be over-sharing personal data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log storage&lt;/strong&gt;: Are logs encrypted at rest? GDPR Article 32 requires appropriate technical measures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Breach notification&lt;/strong&gt;: If your log files are exfiltrated, that is a personal data breach requiring notification within 72 hours.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What You Can Do
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Option 1: Strip PII from log format
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;map&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr_masked&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;~^&lt;/span&gt;&lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;d+&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;d+&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;d+)&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s"&gt;.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;default&lt;/span&gt;               &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;log_format&lt;/span&gt; &lt;span class="s"&gt;gdpr_safe&lt;/span&gt; &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="nv"&gt;$remote_addr_masked&lt;/span&gt; &lt;span class="s"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$remote_user&lt;/span&gt; &lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$time_local&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt; &lt;span class="s"&gt;'&lt;/span&gt;
                     &lt;span class="s"&gt;'"&lt;/span&gt;&lt;span class="nv"&gt;$request_method&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$server_protocol&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="s"&gt;'&lt;/span&gt;
                     &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="nv"&gt;$status&lt;/span&gt; &lt;span class="nv"&gt;$body_bytes_sent&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Option 2: Exclude sensitive URL patterns
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;map&lt;/span&gt; &lt;span class="nv"&gt;$request_uri&lt;/span&gt; &lt;span class="nv"&gt;$log_it&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;~*email=&lt;/span&gt;  &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;~*token=&lt;/span&gt;  &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;default&lt;/span&gt;   &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;access_log&lt;/span&gt; &lt;span class="n"&gt;/var/log/nginx/access.log&lt;/span&gt; &lt;span class="s"&gt;combined&lt;/span&gt; &lt;span class="s"&gt;if=&lt;/span&gt;&lt;span class="nv"&gt;$log_it&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Option 3: Redact PII from existing logs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Run daily via cron&lt;/span&gt;
&lt;span class="nv"&gt;LOG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/log/nginx/access.log.1
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'s/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/[REDACTED]/g'&lt;/span&gt; &lt;span class="nv"&gt;$LOG&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Option 4: Set a log retention policy
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;/var/log/nginx/*.log&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;daily&lt;/span&gt;
    &lt;span class="s"&gt;rotate&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
    &lt;span class="s"&gt;compress&lt;/span&gt;
    &lt;span class="s"&gt;missingok&lt;/span&gt;
    &lt;span class="s"&gt;notifempty&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Building a GDPR-Ready Log Pipeline
&lt;/h2&gt;

&lt;p&gt;The goal is &lt;strong&gt;privacy by design&lt;/strong&gt; (GDPR Article 25):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Collect only what you need — strip PII at ingestion&lt;/li&gt;
&lt;li&gt;Retain only as long as needed — 30-day rotation as default&lt;/li&gt;
&lt;li&gt;Encrypt logs at rest&lt;/li&gt;
&lt;li&gt;Restrict access to log files&lt;/li&gt;
&lt;li&gt;Scan regularly for PII that slips through&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Quick Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Review nginx log format for PII fields&lt;/li&gt;
&lt;li&gt;[ ] Check if query parameters contain emails or tokens&lt;/li&gt;
&lt;li&gt;[ ] Set log rotation to 30 days max&lt;/li&gt;
&lt;li&gt;[ ] Encrypt log storage&lt;/li&gt;
&lt;li&gt;[ ] Document lawful basis for log retention in your DPIA&lt;/li&gt;
&lt;li&gt;[ ] Set up automated PII scanning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GDPR compliance is not just about your database. Your logs are data too. Tools like &lt;a href="https://log-audit.com" rel="noopener noreferrer"&gt;LogAudit&lt;/a&gt; scan nginx logs automatically for PII exposure, API key leaks, and compliance issues — so you catch problems before your next audit.&lt;/p&gt;

</description>
      <category>nginx</category>
      <category>gdpr</category>
      <category>security</category>
      <category>devops</category>
    </item>
    <item>
      <title>5 Security Patterns You Should Be Scanning For in Your Apache/Nginx Logs</title>
      <dc:creator>Log Audit</dc:creator>
      <pubDate>Tue, 24 Mar 2026 08:45:36 +0000</pubDate>
      <link>https://dev.to/logaudit/5-security-patterns-you-should-be-scanning-for-in-your-apachenginx-logs-4i88</link>
      <guid>https://dev.to/logaudit/5-security-patterns-you-should-be-scanning-for-in-your-apachenginx-logs-4i88</guid>
      <description>&lt;p&gt;Every web server generates logs. Most teams ignore them until something breaks. But your access logs are a goldmine of security intelligence — if you know what to look for.&lt;/p&gt;

&lt;p&gt;After analyzing millions of log lines across production systems, here are 5 attack patterns that show up consistently. Each one is easy to detect, and catching them early can save you from a breach.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Path Traversal Attempts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it looks like:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;192.168.1.105 - - [24/Mar/2026:03:14:22 +0000] "GET /images/../../etc/passwd HTTP/1.1" 400 0
10.0.0.33 - - [24/Mar/2026:03:14:45 +0000] "GET /static/%2e%2e/%2e%2e/etc/shadow HTTP/1.1" 403 0
203.0.113.50 - - [24/Mar/2026:03:15:01 +0000] "GET /download?file=../../../proc/self/environ HTTP/1.1" 200 1245
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; Attackers use &lt;code&gt;../&lt;/code&gt; sequences (or URL-encoded variants like &lt;code&gt;%2e%2e&lt;/code&gt;) to escape the web root and read system files. The third example is the scariest — it returned a 200, meaning the server actually served the file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to scan for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requests containing &lt;code&gt;..&lt;/code&gt; or &lt;code&gt;%2e%2e&lt;/code&gt; in the URI&lt;/li&gt;
&lt;li&gt;Any 200 responses to paths referencing &lt;code&gt;/etc/&lt;/code&gt;, &lt;code&gt;/proc/&lt;/code&gt;, or system directories
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s2"&gt;"(&lt;/span&gt;&lt;span class="se"&gt;\.\\&lt;/span&gt;&lt;span class="s2"&gt;./|%2e%2e|%252e)"&lt;/span&gt; /var/log/nginx/access.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. Brute Force Detection (Repeated 401s)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it looks like:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;198.51.100.14 - admin [24/Mar/2026:04:00:01 +0000] "POST /admin/login HTTP/1.1" 401 112
198.51.100.14 - admin [24/Mar/2026:04:00:01 +0000] "POST /admin/login HTTP/1.1" 401 112
198.51.100.14 - admin [24/Mar/2026:04:00:02 +0000] "POST /admin/login HTTP/1.1" 401 112
198.51.100.14 - admin [24/Mar/2026:04:00:03 +0000] "POST /admin/login HTTP/1.1" 200 3842
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; A burst of 401 responses from the same IP followed by a 200 is the textbook signature of a successful brute force attack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to scan for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More than 5-10 failed auth attempts (401/403) from a single IP within a short window&lt;/li&gt;
&lt;li&gt;A 200 response after a series of failures (indicates successful compromise)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt; 401 &lt;span class="o"&gt;{&lt;/span&gt;print &lt;span class="se"&gt;\}&lt;/span&gt; /var/log/nginx/access.log | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. XSS Attempts in Query Strings
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it looks like:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;10.0.0.88 - - [24/Mar/2026:05:22:10 +0000] "GET /search?q=&amp;lt;script&amp;gt;alert(document.cookie)&amp;lt;/script&amp;gt; HTTP/1.1" 200 4521
172.16.0.5 - - [24/Mar/2026:05:22:33 +0000] "GET /profile?name=%3Cimg%20src%3Dx%20onerror%3Dalert(1)%3E HTTP/1.1" 200 3200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; XSS payloads in URLs mean someone is actively testing whether your application sanitizes input. If those requests return 200, your app may be vulnerable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to scan for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;svg&lt;/code&gt;, &lt;code&gt;&amp;lt;img&lt;/code&gt; tags in query parameters&lt;/li&gt;
&lt;li&gt;Event handlers like &lt;code&gt;onerror=&lt;/code&gt;, &lt;code&gt;onload=&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;URL-encoded versions of the above
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s2"&gt;"(&amp;lt;script|%3Cscript|onerror=|onload=|javascript:)"&lt;/span&gt; /var/log/nginx/access.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Sensitive File Access (.env, .git, wp-admin probing)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it looks like:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;45.33.32.156 - - [24/Mar/2026:06:10:05 +0000] "GET /.env HTTP/1.1" 200 890
45.33.32.156 - - [24/Mar/2026:06:10:06 +0000] "GET /.git/config HTTP/1.1" 200 234
45.33.32.156 - - [24/Mar/2026:06:10:07 +0000] "GET /wp-admin/ HTTP/1.1" 302 0
45.33.32.156 - - [24/Mar/2026:06:10:08 +0000] "GET /phpinfo.php HTTP/1.1" 200 52314
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; This is an automated scanner probing for exposed config files. A &lt;code&gt;.env&lt;/code&gt; file returning 200 is a critical incident — it likely contains database credentials, API keys, and secrets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to scan for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requests for dotfiles: &lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;.git/&lt;/code&gt;, &lt;code&gt;.htaccess&lt;/code&gt;, &lt;code&gt;.aws/credentials&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Admin panel probing: &lt;code&gt;/wp-admin&lt;/code&gt;, &lt;code&gt;/phpmyadmin&lt;/code&gt;, &lt;code&gt;/adminer&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Any of the above returning a 200 status code
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s2"&gt;"(&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;env|&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;git|wp-admin|phpinfo|&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;aws|server-status)"&lt;/span&gt; /var/log/nginx/access.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. API Keys and Tokens Leaked in URLs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it looks like:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;10.0.0.22 - - [24/Mar/2026:07:30:15 +0000] "GET /api/data?api_key=sk_live_4eC39HqLyjWDarjtT1zdp7dc HTTP/1.1" 200 1580
10.0.0.22 - - [24/Mar/2026:07:30:16 +0000] "GET /webhook?token=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx HTTP/1.1" 200 40
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; This is not an external attack — it is your own application leaking secrets. API keys passed as query parameters get logged in plain text in access logs, browser history, and analytics tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to scan for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query parameters named &lt;code&gt;api_key&lt;/code&gt;, &lt;code&gt;token&lt;/code&gt;, &lt;code&gt;access_token&lt;/code&gt;, &lt;code&gt;secret&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Known key patterns: &lt;code&gt;sk_live_&lt;/code&gt;, &lt;code&gt;ghp_&lt;/code&gt;, &lt;code&gt;AKIA&lt;/code&gt; (AWS), &lt;code&gt;xoxb-&lt;/code&gt; (Slack)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s2"&gt;"(api_key=|token=|access_token=|secret=|sk_live_|ghp_|AKIA)"&lt;/span&gt; /var/log/nginx/access.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Immediate action:&lt;/strong&gt; If you find leaked keys, rotate them immediately.&lt;/p&gt;




&lt;h2&gt;
  
  
  Automate It
&lt;/h2&gt;

&lt;p&gt;Scanning for these patterns manually works, but it does not scale. You have to check every log rotation, across every server, every day.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://log-audit.com" rel="noopener noreferrer"&gt;LogAudit&lt;/a&gt; to automate exactly this — it scans your nginx/Apache logs against these patterns (and more), flags issues by severity, and gives you a compliance score. Free tier available if you want to try it.&lt;/p&gt;

&lt;p&gt;But regardless of what tools you use — start scanning. These patterns are hitting your servers right now.&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>nginx</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Detect Credential Stuffing Attacks in Your Nginx Logs</title>
      <dc:creator>Log Audit</dc:creator>
      <pubDate>Fri, 20 Mar 2026 11:33:45 +0000</pubDate>
      <link>https://dev.to/logaudit/how-to-detect-credential-stuffing-attacks-in-your-nginx-logs-mj8</link>
      <guid>https://dev.to/logaudit/how-to-detect-credential-stuffing-attacks-in-your-nginx-logs-mj8</guid>
      <description>&lt;h1&gt;
  
  
  How to Detect Credential Stuffing Attacks in Your Nginx Logs
&lt;/h1&gt;

&lt;p&gt;Credential stuffing is when attackers take leaked username/password lists from data breaches and try them automatically against your login endpoint. Unlike brute force (guessing random passwords), credential stuffing uses &lt;em&gt;real&lt;/em&gt; credentials — which makes it far more dangerous and harder to spot.&lt;/p&gt;

&lt;p&gt;The good news: it leaves a very clear pattern in your nginx logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Credential Stuffing Looks Like
&lt;/h2&gt;

&lt;p&gt;A normal login attempt looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;192.168.1.1 - - [20/Mar/2026:09:15:00 +0000] "POST /api/login HTTP/1.1" 200 156
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A credential stuffing attack looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;185.220.101.34 - - [20/Mar/2026:09:15:01 +0000] "POST /api/login HTTP/1.1" 401 53
185.220.101.34 - - [20/Mar/2026:09:15:02 +0000] "POST /api/login HTTP/1.1" 401 53
185.220.101.34 - - [20/Mar/2026:09:15:02 +0000] "POST /api/login HTTP/1.1" 401 53
185.220.101.34 - - [20/Mar/2026:09:15:03 +0000] "POST /api/login HTTP/1.1" 401 53
185.220.101.34 - - [20/Mar/2026:09:15:03 +0000] "POST /api/login HTTP/1.1" 200 156
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Same IP, rapid succession&lt;/strong&gt; — dozens of 401s in seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single endpoint&lt;/strong&gt; — always hitting &lt;code&gt;/login&lt;/code&gt;, &lt;code&gt;/api/auth&lt;/code&gt;, &lt;code&gt;/signin&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eventually a 200&lt;/strong&gt; — when they find a valid credential&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No other activity&lt;/strong&gt; — not browsing the site, just hammering the endpoint&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Detecting It with grep and awk
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Find IPs with many failed logins (401s) to login endpoint&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'POST /api/login'&lt;/span&gt; /var/log/nginx/access.log | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;' 401 '&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;

&lt;span class="c"&gt;# Find IPs with 10+ failed logins&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'POST /api/login'&lt;/span&gt; /var/log/nginx/access.log | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;' 401 '&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'$1 &amp;gt;= 10 {print $1, $2}'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt;

&lt;span class="c"&gt;# Check if any of those IPs then got a 200 (successful login)&lt;/span&gt;
&lt;span class="nv"&gt;SUSPECT_IP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"185.220.101.34"&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SUSPECT_IP&lt;/span&gt;&lt;span class="s2"&gt;.*POST /api/login"&lt;/span&gt; /var/log/nginx/access.log | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $9}'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Detecting Distributed Credential Stuffing
&lt;/h2&gt;

&lt;p&gt;Sophisticated attackers rotate IPs to avoid per-IP rate limits. This is harder to catch but still has a pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# High volume of 401s across many IPs in a short window&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'POST /api/login'&lt;/span&gt; /var/log/nginx/access.log | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;' 401 '&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $4}'&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;: &lt;span class="nt"&gt;-f1-3&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-10&lt;/span&gt;
&lt;span class="c"&gt;# Shows 401 counts per hour — a spike = distributed attack&lt;/span&gt;

&lt;span class="c"&gt;# Count unique IPs hitting login in last hour vs normal&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'POST /api/login'&lt;/span&gt; /var/log/nginx/access.log | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +&lt;span class="s1"&gt;'%d/%b/%Y:%H'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s1"&gt;'$4 ~ date {print $1}'&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Block It with fail2ban
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;/etc/fail2ban/filter.d/nginx-login.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Definition]&lt;/span&gt;
&lt;span class="py"&gt;failregex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;^&amp;lt;HOST&amp;gt; .* "POST /api/login HTTP.+" 401&lt;/span&gt;
&lt;span class="py"&gt;ignoreregex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add to &lt;code&gt;/etc/fail2ban/jail.local&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[nginx-login]&lt;/span&gt;
&lt;span class="py"&gt;enabled&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;http,https&lt;/span&gt;
&lt;span class="py"&gt;filter&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;nginx-login&lt;/span&gt;
&lt;span class="py"&gt;logpath&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/var/log/nginx/access.log&lt;/span&gt;
&lt;span class="py"&gt;maxretry&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;10&lt;/span&gt;
&lt;span class="py"&gt;bantime&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;3600&lt;/span&gt;
&lt;span class="py"&gt;findtime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;60&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This bans any IP that fails login 10 times within 60 seconds for 1 hour.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl restart fail2ban
fail2ban-client status nginx-login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  nginx Rate Limiting
&lt;/h2&gt;

&lt;p&gt;For defense-in-depth, add rate limiting directly in nginx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="c1"&gt;# In http block&lt;/span&gt;
&lt;span class="k"&gt;limit_req_zone&lt;/span&gt; &lt;span class="nv"&gt;$binary_remote_addr&lt;/span&gt; &lt;span class="s"&gt;zone=login:10m&lt;/span&gt; &lt;span class="s"&gt;rate=5r/m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;# In server block&lt;/span&gt;
&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/api/login&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;limit_req&lt;/span&gt; &lt;span class="s"&gt;zone=login&lt;/span&gt; &lt;span class="s"&gt;burst=10&lt;/span&gt; &lt;span class="s"&gt;nodelay&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;limit_req_status&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;# ... rest of config&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This limits each IP to 5 login attempts per minute with a burst of 10.&lt;/p&gt;

&lt;h2&gt;
  
  
  Warning Signs You've Been Hit
&lt;/h2&gt;

&lt;p&gt;Check these after finding a stuffing campaign:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Any successful logins from attacking IPs?&lt;/span&gt;
&lt;span class="nv"&gt;ATTACKER_IPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'POST /api/login'&lt;/span&gt; /var/log/nginx/access.log | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;' 401 '&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'$1 &amp;gt;= 20 {print $2}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;IP &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;$ATTACKER_IPS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;SUCCESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$IP&lt;/span&gt;&lt;span class="s2"&gt;.*POST /api/login"&lt;/span&gt; /var/log/nginx/access.log | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;' 200 '&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SUCCESS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"COMPROMISED: &lt;/span&gt;&lt;span class="nv"&gt;$IP&lt;/span&gt;&lt;span class="s2"&gt; got a 200"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SUCCESS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;fi
done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you find successful logins from attacking IPs, those accounts are compromised and need immediate password resets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond grep: Automated Detection
&lt;/h2&gt;

&lt;p&gt;Manual log analysis works for incident response but you need continuous monitoring to catch attacks as they happen — not hours later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://log-audit.com?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=credential-stuffing" rel="noopener noreferrer"&gt;LogAudit&lt;/a&gt; automatically detects credential stuffing patterns in your nginx logs in real time, alerting you the moment an attack campaign starts rather than after accounts are compromised. Free tier available.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Found this useful? The next post covers GDPR risks hiding in your nginx logs — specifically what PII you might be accidentally logging in query strings.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nginx</category>
      <category>security</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Detect SQL Injection Attempts in Your Nginx Logs</title>
      <dc:creator>Log Audit</dc:creator>
      <pubDate>Thu, 19 Mar 2026 21:11:35 +0000</pubDate>
      <link>https://dev.to/logaudit/how-to-detect-sql-injection-attempts-in-your-nginx-logs-2ca2</link>
      <guid>https://dev.to/logaudit/how-to-detect-sql-injection-attempts-in-your-nginx-logs-2ca2</guid>
      <description>&lt;p&gt;placeholder&lt;/p&gt;

</description>
      <category>nginx</category>
      <category>security</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
