{"id":293,"date":"2026-08-24T20:29:51","date_gmt":"2026-08-25T00:29:51","guid":{"rendered":"https:\/\/securebydefault.io\/blog\/?p=293"},"modified":"2026-08-24T20:29:52","modified_gmt":"2026-08-25T00:29:52","slug":"i-automated-my-incident-response","status":"publish","type":"post","link":"https:\/\/securebydefault.io\/blog\/i-automated-my-incident-response\/","title":{"rendered":"I Automated My Incident Response"},"content":{"rendered":"\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"538\" src=\"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/ir_playbook_blog_hero-1-1024x538.png\" alt=\"\" class=\"wp-image-302\" srcset=\"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/ir_playbook_blog_hero-1-1024x538.png 1024w, https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/ir_playbook_blog_hero-1-300x158.png 300w, https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/ir_playbook_blog_hero-1-768x403.png 768w, https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/ir_playbook_blog_hero-1.png 1200w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"has-ast-global-color-2-color has-text-color has-link-color has-medium-font-size wp-elements-1 wp-block-paragraph\">The Setup<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-wide\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">After building a home SOC lab with Splunk and catching a web shell scanner probing my server with 580 requests, I had a follow-up thought: what if I did not have to be at the keyboard to respond to this stuff?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The manual workflow was getting old. Attacker hits the server. Splunk alerts. I log in, investigate, run ufw deny, close the terminal. Repeat. It worked but it required me to actually be awake and paying attention, which is frankly a design flaw.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So I built an automated incident response playbook script. It runs as a persistent systemd service, monitors my server logs in real time, detects two specific threat patterns, fires a formatted Slack alert with the attacker details, and automatically blocks the IP via UFW. No manual intervention required.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Within 24 hours of deploying it, three real attackers had been detected and blocked overnight while I slept. Which is either a great outcome or a depressing commentary on how many people are scanning the internet at 11 PM on a Sunday. Probably both.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"137\" src=\"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/slack-start-script-1-1024x137.png\" alt=\"\" class=\"wp-image-298\" srcset=\"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/slack-start-script-1-1024x137.png 1024w, https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/slack-start-script-1-300x40.png 300w, https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/slack-start-script-1-764x102.png 764w, https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/slack-start-script-1.png 1379w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"has-ast-global-color-2-color has-text-color has-link-color has-medium-font-size wp-elements-2 wp-block-paragraph\">What the Bot Detects<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-wide\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">The script monitors two threat patterns that showed up repeatedly in the Splunk lab data.<\/p>\n\n\n\n<p class=\"has-ast-global-color-2-color has-text-color has-link-color wp-elements-3 wp-block-paragraph\">SSH Brute Force Attempts<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The script reads the systemd journal for Invalid user events. When the same IP hits the threshold of 3 failed attempts it fires a Critical Slack alert and blocks the IP. On a hardened server with SSH key-only authentication these events still appear in the journal even though the connection is rejected before any password is attempted. That means the detection works correctly regardless of whether password authentication is enabled.<br><\/p>\n\n\n\n<style>\n.callout-blue { background: #EFF6FF; border-left: 4px solid #1D4ED8; border-radius: 4px; padding: 14px 16px; font-family: Arial, sans-serif; font-size: 14px; line-height: 1.6; color: #1f2937; margin-bottom: 20px; }\n.callout-blue .callout-title { font-weight: 700; color: #1D4ED8; margin-bottom: 6px; }\n<\/style>\n\n<div class=\"callout-blue\">\n  <div class=\"callout-title\">Why systemd journal instead of auth.log?<\/div>\n  On Ubuntu 22.04 and 24.04, SSH authentication events go to the systemd journal rather than being written to \/var\/log\/auth.log by default. The script uses journalctl to read from the journal directly, which is the correct approach on modern Ubuntu systems.\n<\/div>\n\n\n\n<p class=\"has-ast-global-color-2-color has-text-color has-link-color wp-elements-4 wp-block-paragraph\">Web Shell Scanner Activity<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The script reads the Nginx access log and flags any IP sending requests to suspicious PHP paths.  The pattern list covers randomized PHP filenames, WordPress-specific backdoor paths, .env file probes, and known shell names.  After 10 matching requests from the same IP it fires an alert and blocks the IP. <\/p>\n\n\n\n<style>\n.callout-purple { background: #F5F3FF; border-left: 4px solid #7C3AED; border-radius: 4px; padding: 14px 16px; font-family: Arial, sans-serif; font-size: 14px; line-height: 1.6; color: #1f2937; margin-bottom: 20px; }\n.callout-purple .callout-title { font-weight: 700; color: #7C3AED; margin-bottom: 6px; }\n<\/style>\n\n<div class=\"callout-purple\">\n  <div class=\"callout-title\">Why 10 requests for web scanning vs 3 for SSH?<\/div>\n  Web scanners typically send dozens to hundreds of requests in a burst. Setting the threshold too low would generate alerts for legitimate crawlers and search bots that occasionally hit a PHP path. 10 requests from the same IP to suspicious paths in a short window is a reliable signal of automated scanning rather than accidental traffic.\n<\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"has-ast-global-color-2-color has-text-color has-link-color has-medium-font-size wp-elements-5 wp-block-paragraph\">How it Works<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-wide\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">The architecture is intentionally simple. A Python script runs in an infinite loop with a 60-second sleep between each check. On each iteration, it reads new lines from the Nginx access log and queries the systemd journal for SSH events since the last check. If a threshold is crossed it fires a Slack webhook and runs UFW.<br><\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"794\" height=\"646\" src=\"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/main-detection.png\" alt=\"\" class=\"wp-image-299\" srcset=\"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/main-detection.png 794w, https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/main-detection-300x244.png 300w, https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/main-detection-767x624.png 767w\" sizes=\"auto, (max-width: 794px) 100vw, 794px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The script runs as a systemd service so it starts automatically on every reboot and restarts itself if it crashes.  The service file is straightforward:<\/p>\n\n\n\n<style>\n.code-block { background: #1E1E2E; border-radius: 6px; padding: 16px 20px; font-family: 'Courier New', monospace; font-size: 13px; line-height: 1.8; color: #00D4FF; margin-bottom: 20px; overflow-x: auto; white-space: pre; }\n<\/style>\n\n<div class=\"code-block\">[Unit]\nDescription=IR Playbook Bot &#8211; Automated Incident Response\nAfter=network.target\n\n[Service]\nType=simple\nUser=agent47\nExecStart=\/usr\/bin\/python3 \/home\/agent47\/ir_playbook.py\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target<\/div>\n\n\n\n<p class=\"wp-block-paragraph\">One important detail, the script needs to run two sudo commands (journalctl and ufw) without a password prompt since there is no interactive terminal when running as a service. The fix is a targeted sudoers entry that grants passwordless access to exactly those two commands and nothing else. Least privilege, same as it should be.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"249\" src=\"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/server-script-1024x249.png\" alt=\"\" class=\"wp-image-300\" srcset=\"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/server-script-1024x249.png 1024w, https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/server-script-300x73.png 300w, https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/server-script-764x186.png 764w, https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/server-script.png 1056w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"has-ast-global-color-2-color has-text-color has-link-color has-medium-font-size wp-elements-6 wp-block-paragraph\"><br>What Happened When it Went Live<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-wide\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">I deployed the bot on a Sunday evening. By Monday morning it had caught three real attackers. None of these were test data. All three were legitimate malicious actors scanning the internet and running into my server.<br><\/p>\n\n\n\n<style>\n.detection-table { width: 100%; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 14px; margin-bottom: 20px; }\n.detection-table th { background: #1A1A2E; color: #ffffff; font-weight: 600; padding: 10px 14px; text-align: left; }\n.detection-table td { padding: 10px 14px; border-bottom: 1px solid #e5e7eb; vertical-align: middle; }\n.detection-table tr:nth-child(even) td { background: #F9FAFB; }\n.detection-table tr:nth-child(odd) td { background: #ffffff; }\n.detection-table .blocked { color: #991B1B; font-weight: 700; background: #FEF2F2 !important; }\n<\/style>\n\n<table class=\"detection-table\">\n  <tbody><tr>\n    <th>Time<\/th>\n    <th>Attacker IP<\/th>\n    <th>Path Probed<\/th>\n    <th>Attack Type<\/th>\n    <th>Result<\/th>\n  <\/tr>\n  <tr>\n    <td>7:54 PM Sun<\/td>\n    <td>20.38.9.144<\/td>\n    <td>\/.env.sample<\/td>\n    <td>Credential harvesting<\/td>\n    <td class=\"blocked\">Blocked<\/td>\n  <\/tr>\n  <tr>\n    <td>8:06 PM Sun<\/td>\n    <td>74.248.96.44<\/td>\n    <td>\/100.php<\/td>\n    <td>Web shell scanner<\/td>\n    <td class=\"blocked\">Blocked<\/td>\n  <\/tr>\n  <tr>\n    <td>11:00 PM Sun<\/td>\n    <td>20.151.129.194<\/td>\n    <td>\/classwithtostring.php?p=<\/td>\n    <td>PHP object injection<\/td>\n    <td class=\"blocked\">Blocked<\/td>\n  <\/tr>\n<\/tbody><\/table>\n\n\n\n<p class=\"has-ast-global-color-2-color has-text-color has-link-color wp-elements-7 wp-block-paragraph\">Detection 1: Credential Harvesting at 7:54 PM<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The first alert came in less than two hours after deployment. 20.38.9.144 was probing for \/.env.sample, a file that contains application environment variables including database passwords, API keys, and service credentials. This is one of the most common automated scans on the internet because exposed .env files are a goldmine for attackers who find one. This server does not have one. The probe returned 404, Slack fired, UFW blocked.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"767\" height=\"184\" src=\"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/scanner-attack-1.png\" alt=\"\" class=\"wp-image-308\" srcset=\"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/scanner-attack-1.png 767w, https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/scanner-attack-1-300x72.png 300w\" sizes=\"auto, (max-width: 767px) 100vw, 767px\" \/><\/figure>\n\n\n\n<p class=\"has-ast-global-color-2-color has-text-color has-link-color wp-elements-8 wp-block-paragraph\"><br>Detection 2: Web Shell Scanner at 8:06 PM<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Twelve minutes later a second IP showed up. 74.248.96.44 was probing for \/100.php, a randomized PHP filename associated with web shell scanning. The same pattern we saw in the Splunk lab with 580 requests from 4.223.96.99, just a different IP and filename. Detected, alerted, blocked.<br><\/p>\n\n\n\n<figure class=\"wp-block-image alignwide size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"716\" height=\"186\" src=\"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/slack-attack-2.png\" alt=\"\" class=\"wp-image-309\" srcset=\"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/slack-attack-2.png 716w, https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/slack-attack-2-300x78.png 300w\" sizes=\"auto, (max-width: 716px) 100vw, 716px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"has-ast-global-color-2-color has-text-color has-link-color wp-elements-9 wp-block-paragraph\">Detection 3: PHP Object Injection at 11:00 PM<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The late night one was the most interesting. 20.151.129.194 was probing for \/classwithtostring.php?p=, a path associated with PHP object injection attacks and Java deserialization exploits. The ?p= query parameter is the tell: the attacker was testing whether a PHP file would accept and execute a serialized object passed as a parameter. This is a more sophisticated probe than a generic web shell scanner and suggests an automated tool specifically targeting PHP deserialization vulnerabilities. Detected at 11 PM while I was asleep. Blocked automatically. Zero manual intervention.<br><\/p>\n\n\n\n<figure class=\"wp-block-image alignwide size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"760\" height=\"190\" src=\"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/slack-attack-3.png\" alt=\"\" class=\"wp-image-310\" srcset=\"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/slack-attack-3.png 760w, https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/slack-attack-3-300x75.png 300w\" sizes=\"auto, (max-width: 760px) 100vw, 760px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"has-ast-global-color-2-color has-text-color has-link-color has-medium-font-size wp-elements-10 wp-block-paragraph\">The Key Functions<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-wide\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">The script has three main pieces worth understanding if you want to build your own.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"has-ast-global-color-2-color has-text-color has-link-color wp-elements-11 wp-block-paragraph\">The Slack Alert Function<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Every detection routes through a single send_slack_alert function that formats the message with an emoji, timestamp, and structured fields before posting to the webhook. Keeping alerting centralized means the format is consistent regardless of which detection triggered it.<br><\/p>\n\n\n\n<style>\n.code-block { background: #1E1E2E; border-radius: 6px; padding: 16px 20px; font-family: 'Courier New', monospace; font-size: 13px; line-height: 1.8; color: #00D4FF; margin-bottom: 20px; overflow-x: auto; white-space: pre; }\n<\/style>\n\n<div class=\"code-block\">def send_slack_alert(title, message, severity=&#8217;WARNING&#8217;):\n    emoji = &#8216;:rotating_light:&#8217; if severity == &#8216;CRITICAL&#8217; else &#8216;:warning:&#8217;\n    timestamp = datetime.now().strftime(&#8216;%Y-%m-%d %H:%M:%S&#8217;)\n    payload = {\n        &#8216;text&#8217;: f'{emoji} *{title}*\\n{message}\\n_Detected at {timestamp}_&#8217;\n    }\n    response = requests.post(WEBHOOK_URL, json=payload, timeout=10)<\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"has-ast-global-color-2-color has-text-color has-link-color wp-elements-12 wp-block-paragraph\">The UFW Block Function<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Auto-blocking is optional via the AUTO_BLOCK flag. When enabled, a confirmed attacker IP gets a UFW deny rule added immediately after the alert fires. The function uses subprocess to run the UFW command and reports success or failure back to the caller so the Slack message can include the block confirmation.<br><\/p>\n\n\n\n<div class=\"code-block\">def block_ip(ip):\n    subprocess.run(\n        [&#8216;sudo&#8217;, &#8216;ufw&#8217;, &#8216;deny&#8217;, &#8216;from&#8217;, ip, &#8216;to&#8217;, &#8216;any&#8217;],\n        check=True, capture_output=True\n    )\n    return True<\/div>\n\n\n\n<p class=\"has-ast-global-color-2-color has-text-color has-link-color wp-elements-13 wp-block-paragraph\">The Detection Loop<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The main loop tracks the last read position in the Nginx log file using Python&#8217;s file seek and tell methods. On each iteration it reads only the new lines added since the last check, which means it does not reprocess the entire log file every 60 seconds. For the SSH journal it queries journalctl with a &#8211;since timestamp set to one check interval ago.<br><\/p>\n\n\n\n<p class=\"has-ast-global-color-2-color has-text-color has-link-color has-medium-font-size wp-elements-14 wp-block-paragraph\">Build Your Own<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-wide\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">The full script and installation guide are on GitHub.  Here is the short version of what you need:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Ubuntu Linux server (tested on 24.04)<\/li>\n\n\n\n<li>Python 3 with the requests library installed<\/li>\n\n\n\n<li>A Slack workspace with an incoming webhook configured<\/li>\n\n\n\n<li>Nginx with access logging enabled<\/li>\n\n\n\n<li>UFW firewall installed and active<\/li>\n<\/ul>\n\n\n\n<style>\n.callout-amber { background: #FFFBEB; border-left: 4px solid #D97706; border-radius: 4px; padding: 14px 16px; font-family: Arial, sans-serif; font-size: 14px; line-height: 1.6; color: #1f2937; margin-bottom: 20px; }\n.callout-amber .callout-title { font-weight: 700; color: #D97706; margin-bottom: 6px; }\n<\/style>\n\n<div class=\"callout-amber\">\n  <div class=\"callout-title\">Security note before you deploy<\/div>\n  Never commit your Slack webhook URL to a public GitHub repository. GitHub&#8217;s push protection will catch it and block the push, but the URL may still be exposed in the git history. Use a placeholder in the version you push and set the real URL directly on the server. If you accidentally expose it, regenerate the webhook immediately in your Slack app settings.\n<\/div>\n\n\n\n<p class=\"wp-block-paragraph\">The full README on GitHub walks through the complete installation including the sudoers configuration, the systemd service file, and the Nginx log permission fix that is required for the script to read the access log as a non-root user.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">GitHub: https:\/\/github.com\/RonMercier\/ir-playbook-bot<\/p>\n\n\n\n<p class=\"has-ast-global-color-2-color has-text-color has-link-color has-medium-font-size wp-elements-15 wp-block-paragraph\">Takeaways<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-wide\"\/>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Automation does not have to be complex to be effective. This is a 150-line Python script running a while loop. It caught three real attackers in its first 24 hours.<\/li>\n\n\n\n<li>The systemd service pattern is worth learning. Any Python script that needs to run continuously on a Linux server should be a service, not a cron job or a backgrounded terminal session that disappears on reboot.<\/li>\n\n\n\n<li>The sudo least-privilege pattern matters. Granting passwordless sudo for exactly two specific commands is meaningfully more secure than broad sudo access, even on a personal server.<\/li>\n\n\n\n<li>Real attack data is more compelling than lab data. Every detection in this post came from genuine malicious activity, not simulated traffic. If you run a public-facing server, you will see real attackers within hours of deployment.<\/li>\n<\/ul>\n\n\n\n<style>\n.callout-green { background: #F0FDF4; border-left: 4px solid #059669; border-radius: 4px; padding: 14px 16px; font-family: Arial, sans-serif; font-size: 14px; line-height: 1.6; color: #1f2937; margin-bottom: 20px; }\n.callout-green .callout-title { font-weight: 700; color: #059669; margin-bottom: 6px; }\n<\/style>\n\n<div class=\"callout-green\">\n  <div class=\"callout-title\">What is next<\/div>\n  The obvious extension is adding more detection patterns, failed authentication attempts for other services, port scanning signatures in the firewall logs, and repeated 404s suggesting directory enumeration. A future version will also log all detections to a local file for historical analysis rather than relying solely on Slack message history.\n<\/div>\n\n\n\n<div style=\"margin:48px 0 20px;padding:36px 32px;background:#050C18;border:1px solid #1A3A5C;border-top:3px solid #00D4FF;border-radius:8px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;text-align:center;\">\n\n  <div style=\"font-family:'Courier New',monospace;font-size:11px;letter-spacing:0.18em;text-transform:uppercase;color:#00D4FF;margin-bottom:14px;\">\n    \/\/ Before you go\n  <\/div>\n\n  <h3 style=\"margin:0 0 14px;font-size:24px;font-weight:800;color:#EEF5FF;line-height:1.25;\">\n    Get the security checklist most<br>businesses skip.\n  <\/h3>\n\n  <p style=\"margin:0 auto 24px;max-width:440px;font-size:15px;line-height:1.65;color:#8BB8D8;\">\n    A free 25-point audit covering the exact gaps attackers hit first, written by an engineer in plain language. Plus one practical security breakdown every Tuesday.\n  <\/p>\n\n  <a href=\"https:\/\/newsletter.securebydefault.io\" target=\"_blank\" rel=\"noopener\"\n     style=\"display:inline-block;background:#00D4FF;color:#050C18;text-decoration:none;\n     font-weight:700;font-size:15px;padding:15px 36px;border-radius:4px;letter-spacing:0.02em;\">\n    Get the Free Checklist &rarr;\n  <\/a>\n\n  <p style=\"margin:18px 0 0;font-family:'Courier New',monospace;font-size:11px;color:#4A7A9B;letter-spacing:0.04em;\">\n    Free on signup &nbsp;\u00b7&nbsp; Unsubscribe anytime &nbsp;\u00b7&nbsp; ~1 email per week\n  <\/p>\n\n<\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>I built a Python bot that monitors my Linux server, detects SSH brute force and web shell scanning, sends Slack alerts, and auto-blocks attackers via UFW.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[86,6,37,119],"tags":[121,48,127,107,41,8,7,120,124,125,122,123,126,111,14,110],"class_list":["post-293","post","type-post","status-publish","format-standard","hentry","category-career-development","category-cloud-security","category-incident-response","category-projects","tag-automation","tag-blue-team","tag-brute-force","tag-home-lab","tag-incident-response","tag-linux","tag-nginx","tag-python","tag-securebydefault","tag-security-automation","tag-slack","tag-ssh","tag-systemd","tag-ubuntu","tag-ufw","tag-web-shell"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.3 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>IR Playbook Bot: Automated Incident Response on Linux<\/title>\n<meta name=\"description\" content=\"I built a Python bot that monitors my Linux server, detects SSH brute force and web shell scanning, sends Slack alerts, and auto-blocks attackers via UFW.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/securebydefault.io\/blog\/ir-playbook-bot-automated-incident-response\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"I automated incident response on my Linux server\" \/>\n<meta property=\"og:description\" content=\"Python bot, Slack alerts, UFW auto-blocking. Deployed Sunday. Blocked three real attackers overnight without touching the keyboard.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/securebydefault.io\/blog\/ir-playbook-bot-automated-incident-response\/\" \/>\n<meta property=\"og:site_name\" content=\"SecureByDefault\" \/>\n<meta property=\"article:published_time\" content=\"2026-08-25T00:29:51+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-08-25T00:29:52+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/ir_playbook_blog_hero-1.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Ron Mercier\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Ron Mercier\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"9 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/ir-playbook-bot-automated-incident-response\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/i-automated-my-incident-response\\\/\"},\"author\":{\"name\":\"Ron Mercier\",\"@id\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/#\\\/schema\\\/person\\\/2ee989263a69e3324bce0cbed28ec0e8\"},\"headline\":\"I Automated My Incident Response\",\"datePublished\":\"2026-08-25T00:29:51+00:00\",\"dateModified\":\"2026-08-25T00:29:52+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/i-automated-my-incident-response\\\/\"},\"wordCount\":1542,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/#\\\/schema\\\/person\\\/2ee989263a69e3324bce0cbed28ec0e8\"},\"image\":{\"@id\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/ir-playbook-bot-automated-incident-response\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/ir_playbook_blog_hero-1-1024x538.png\",\"keywords\":[\"automation\",\"blue team\",\"Brute Force\",\"Home Lab\",\"incident response\",\"linux\",\"nginx\",\"Python\",\"SecureByDefault\",\"Security Automation\",\"Slack\",\"SSH\",\"Systemd\",\"Ubuntu\",\"ufw\",\"Web Shell\"],\"articleSection\":[\"Career Development\",\"Cloud Security\",\"Incident Response\",\"Projects\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/securebydefault.io\\\/blog\\\/ir-playbook-bot-automated-incident-response\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/i-automated-my-incident-response\\\/\",\"url\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/ir-playbook-bot-automated-incident-response\\\/\",\"name\":\"IR Playbook Bot: Automated Incident Response on Linux\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/ir-playbook-bot-automated-incident-response\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/ir-playbook-bot-automated-incident-response\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/ir_playbook_blog_hero-1-1024x538.png\",\"datePublished\":\"2026-08-25T00:29:51+00:00\",\"dateModified\":\"2026-08-25T00:29:52+00:00\",\"description\":\"I built a Python bot that monitors my Linux server, detects SSH brute force and web shell scanning, sends Slack alerts, and auto-blocks attackers via UFW.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/ir-playbook-bot-automated-incident-response\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/securebydefault.io\\\/blog\\\/ir-playbook-bot-automated-incident-response\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/ir-playbook-bot-automated-incident-response\\\/#primaryimage\",\"url\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/ir_playbook_blog_hero-1.png\",\"contentUrl\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/ir_playbook_blog_hero-1.png\",\"width\":1200,\"height\":630},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/ir-playbook-bot-automated-incident-response\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"I Automated My Incident Response\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/\",\"name\":\"SecureByDefault\",\"description\":\"Cloud Security &amp; Cybersecurity for IT Professionals\",\"publisher\":{\"@id\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/#\\\/schema\\\/person\\\/2ee989263a69e3324bce0cbed28ec0e8\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/#\\\/schema\\\/person\\\/2ee989263a69e3324bce0cbed28ec0e8\",\"name\":\"Ron Mercier\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/SecureByDefault_Log.png\",\"url\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/SecureByDefault_Log.png\",\"contentUrl\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/SecureByDefault_Log.png\",\"width\":512,\"height\":512,\"caption\":\"Ron Mercier\"},\"logo\":{\"@id\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/SecureByDefault_Log.png\"},\"sameAs\":[\"https:\\\/\\\/securebydefault.io\\\/blog\",\"https:\\\/\\\/www.linkedin.com\\\/in\\\/ron-mercier\\\/\",\"https:\\\/\\\/www.youtube.com\\\/channel\\\/UCDyWOTMI23S8Y3zwPoX3UkQ\"],\"url\":\"https:\\\/\\\/securebydefault.io\\\/blog\\\/author\\\/sbd_admin\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"IR Playbook Bot: Automated Incident Response on Linux","description":"I built a Python bot that monitors my Linux server, detects SSH brute force and web shell scanning, sends Slack alerts, and auto-blocks attackers via UFW.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/securebydefault.io\/blog\/ir-playbook-bot-automated-incident-response\/","og_locale":"en_US","og_type":"article","og_title":"I automated incident response on my Linux server","og_description":"Python bot, Slack alerts, UFW auto-blocking. Deployed Sunday. Blocked three real attackers overnight without touching the keyboard.","og_url":"https:\/\/securebydefault.io\/blog\/ir-playbook-bot-automated-incident-response\/","og_site_name":"SecureByDefault","article_published_time":"2026-08-25T00:29:51+00:00","article_modified_time":"2026-08-25T00:29:52+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/ir_playbook_blog_hero-1.png","type":"image\/png"}],"author":"Ron Mercier","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Ron Mercier","Est. reading time":"9 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/securebydefault.io\/blog\/ir-playbook-bot-automated-incident-response\/#article","isPartOf":{"@id":"https:\/\/securebydefault.io\/blog\/i-automated-my-incident-response\/"},"author":{"name":"Ron Mercier","@id":"https:\/\/securebydefault.io\/blog\/#\/schema\/person\/2ee989263a69e3324bce0cbed28ec0e8"},"headline":"I Automated My Incident Response","datePublished":"2026-08-25T00:29:51+00:00","dateModified":"2026-08-25T00:29:52+00:00","mainEntityOfPage":{"@id":"https:\/\/securebydefault.io\/blog\/i-automated-my-incident-response\/"},"wordCount":1542,"commentCount":0,"publisher":{"@id":"https:\/\/securebydefault.io\/blog\/#\/schema\/person\/2ee989263a69e3324bce0cbed28ec0e8"},"image":{"@id":"https:\/\/securebydefault.io\/blog\/ir-playbook-bot-automated-incident-response\/#primaryimage"},"thumbnailUrl":"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/ir_playbook_blog_hero-1-1024x538.png","keywords":["automation","blue team","Brute Force","Home Lab","incident response","linux","nginx","Python","SecureByDefault","Security Automation","Slack","SSH","Systemd","Ubuntu","ufw","Web Shell"],"articleSection":["Career Development","Cloud Security","Incident Response","Projects"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/securebydefault.io\/blog\/ir-playbook-bot-automated-incident-response\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/securebydefault.io\/blog\/i-automated-my-incident-response\/","url":"https:\/\/securebydefault.io\/blog\/ir-playbook-bot-automated-incident-response\/","name":"IR Playbook Bot: Automated Incident Response on Linux","isPartOf":{"@id":"https:\/\/securebydefault.io\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/securebydefault.io\/blog\/ir-playbook-bot-automated-incident-response\/#primaryimage"},"image":{"@id":"https:\/\/securebydefault.io\/blog\/ir-playbook-bot-automated-incident-response\/#primaryimage"},"thumbnailUrl":"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/ir_playbook_blog_hero-1-1024x538.png","datePublished":"2026-08-25T00:29:51+00:00","dateModified":"2026-08-25T00:29:52+00:00","description":"I built a Python bot that monitors my Linux server, detects SSH brute force and web shell scanning, sends Slack alerts, and auto-blocks attackers via UFW.","breadcrumb":{"@id":"https:\/\/securebydefault.io\/blog\/ir-playbook-bot-automated-incident-response\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/securebydefault.io\/blog\/ir-playbook-bot-automated-incident-response\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/securebydefault.io\/blog\/ir-playbook-bot-automated-incident-response\/#primaryimage","url":"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/ir_playbook_blog_hero-1.png","contentUrl":"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/08\/ir_playbook_blog_hero-1.png","width":1200,"height":630},{"@type":"BreadcrumbList","@id":"https:\/\/securebydefault.io\/blog\/ir-playbook-bot-automated-incident-response\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/securebydefault.io\/blog\/"},{"@type":"ListItem","position":2,"name":"I Automated My Incident Response"}]},{"@type":"WebSite","@id":"https:\/\/securebydefault.io\/blog\/#website","url":"https:\/\/securebydefault.io\/blog\/","name":"SecureByDefault","description":"Cloud Security &amp; Cybersecurity for IT Professionals","publisher":{"@id":"https:\/\/securebydefault.io\/blog\/#\/schema\/person\/2ee989263a69e3324bce0cbed28ec0e8"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/securebydefault.io\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/securebydefault.io\/blog\/#\/schema\/person\/2ee989263a69e3324bce0cbed28ec0e8","name":"Ron Mercier","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/05\/SecureByDefault_Log.png","url":"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/05\/SecureByDefault_Log.png","contentUrl":"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/05\/SecureByDefault_Log.png","width":512,"height":512,"caption":"Ron Mercier"},"logo":{"@id":"https:\/\/securebydefault.io\/blog\/wp-content\/uploads\/2026\/05\/SecureByDefault_Log.png"},"sameAs":["https:\/\/securebydefault.io\/blog","https:\/\/www.linkedin.com\/in\/ron-mercier\/","https:\/\/www.youtube.com\/channel\/UCDyWOTMI23S8Y3zwPoX3UkQ"],"url":"https:\/\/securebydefault.io\/blog\/author\/sbd_admin\/"}]}},"_links":{"self":[{"href":"https:\/\/securebydefault.io\/blog\/wp-json\/wp\/v2\/posts\/293","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/securebydefault.io\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/securebydefault.io\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/securebydefault.io\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/securebydefault.io\/blog\/wp-json\/wp\/v2\/comments?post=293"}],"version-history":[{"count":16,"href":"https:\/\/securebydefault.io\/blog\/wp-json\/wp\/v2\/posts\/293\/revisions"}],"predecessor-version":[{"id":317,"href":"https:\/\/securebydefault.io\/blog\/wp-json\/wp\/v2\/posts\/293\/revisions\/317"}],"wp:attachment":[{"href":"https:\/\/securebydefault.io\/blog\/wp-json\/wp\/v2\/media?parent=293"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/securebydefault.io\/blog\/wp-json\/wp\/v2\/categories?post=293"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/securebydefault.io\/blog\/wp-json\/wp\/v2\/tags?post=293"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}