{"id":2251,"date":"2026-03-02T19:38:30","date_gmt":"2026-03-02T19:38:30","guid":{"rendered":"https:\/\/nicktailor.com\/tech-blog\/?p=2251"},"modified":"2026-03-02T20:03:14","modified_gmt":"2026-03-02T20:03:14","slug":"handleyoursecurty","status":"publish","type":"post","link":"https:\/\/nicktailor.com\/tech-blog\/handleyoursecurty\/","title":{"rendered":"Approaches to Server Security: Stop Thinking Like It&#8217;s 2010"},"content":{"rendered":"<link href=\"https:\/\/fonts.googleapis.com\/css2?family=JetBrains+Mono:wght@400;500&#038;family=Lora:ital,wght@0,400;0,600;0,700;1,400&#038;family=Inter:wght@400;500;600&#038;display=swap\" rel=\"stylesheet\"\/>\n<p><!-- HERO --><\/p>\n<p style=\"font-family:'Inter',sans-serif;font-size:12px;font-weight:600;letter-spacing:0.1em;text-transform:uppercase;color:#c2410c;margin-bottom:20px;\">Server Security &nbsp;\/&nbsp; March 2026<\/p>\n<p style=\"font-family:'Lora',Georgia,serif;font-size:18px;color:#6b6760;font-style:italic;line-height:1.75;margin-bottom:40px;padding-bottom:32px;border-bottom:1px solid #e2e0da;\">The patterns showing up in server logs over recent months suggest that the attack surface has shifted in some fairly predictable ways. A few straightforward measures appear to address the bulk of it.<\/p>\n<p><!-- SECTION: Digital Ocean --><\/p>\n<h2 style=\"font-family:'Lora',Georgia,serif;font-size:22px;font-weight:700;color:#1a1a1a;margin:0 0 18px;padding-bottom:14px;border-bottom:1px solid #e2e0da;\">The Pattern in the Logs: Digital Ocean<\/h2>\n<p style=\"font-family:'Lora',Georgia,serif;font-size:17px;line-height:1.85;color:#2c2c2c;margin-bottom:18px;\">Anyone running a public-facing server and watching their <code style=\"font-family:'JetBrains Mono',monospace;font-size:13px;background:#f0ede8;color:#c2410c;padding:2px 6px;border-radius:3px;border:1px solid #dedad4;\">\/var\/log\/auth.log<\/code> or fail2ban output will likely notice something consistent: a notable proportion of brute force and port scanning activity appears to originate from Digital Ocean IP ranges.<\/p>\n<p style=\"font-family:'Lora',Georgia,serif;font-size:17px;line-height:1.85;color:#2c2c2c;margin-bottom:18px;\">This is not particularly surprising. A low-cost VPS can be provisioned in seconds, carries a clean IP not yet on most blocklists, and can be destroyed without a trace once a campaign is complete. It would appear this has become a fairly common setup for automated credential testing.<\/p>\n<div style=\"background:#fff4ef;border-left:3px solid #c2410c;border-radius:0 6px 6px 0;padding:16px 20px;margin:24px 0;\">\n<p style=\"font-family:'Inter',sans-serif;font-size:16px;line-height:1.75;color:#2c2c2c;margin:0;\">This is not a criticism of Digital Ocean specifically. The same pattern appears across AWS, Vultr, Linode and others. It is simply where the activity seems most concentrated at present, based on log observation.<\/p>\n<\/div>\n<p style=\"font-family:'Lora',Georgia,serif;font-size:17px;line-height:1.85;color:#2c2c2c;margin-bottom:40px;\">Once you can identify where the traffic is coming from, blocking it at the network level before it reaches your services is relatively straightforward.<\/p>\n<p><!-- SECTION: Log Analysis --><\/p>\n<h2 style=\"font-family:'Lora',Georgia,serif;font-size:22px;font-weight:700;color:#1a1a1a;margin:0 0 18px;padding-bottom:14px;border-bottom:1px solid #e2e0da;\">Watching the Logs and Blocking at Range Level<\/h2>\n<p style=\"font-family:'Lora',Georgia,serif;font-size:17px;line-height:1.85;color:#2c2c2c;margin-bottom:18px;\">Blocking individual IPs as they appear is largely ineffective since the same underlying infrastructure will simply rotate addresses. Watching for patterns across a few days and then blocking the entire subnet tends to be considerably more efficient.<\/p>\n<p style=\"font-family:'Inter',sans-serif;font-size:12px;font-weight:600;color:#6b6760;text-transform:uppercase;letter-spacing:0.09em;margin:30px 0 10px;\">Step 1: Extract the Top Attacking IPs<\/p>\n<div style=\"border-radius:8px;overflow:hidden;border:1px solid #2a2a2a;margin:0 0 22px;\">\n<div style=\"display:flex;align-items:center;justify-content:space-between;padding:9px 14px;background:#161b22;border-bottom:1px solid #2a2a2a;\">\n<span style=\"font-family:'Inter',sans-serif;font-size:11px;font-weight:600;color:#8b949e;text-transform:uppercase;letter-spacing:0.09em;\">bash<\/span><br \/>\n<button onclick=\"(function(b){var p=b.parentNode.parentNode.querySelector('pre');var t=p.innerText||p.textContent;navigator.clipboard.writeText(t).then(function(){b.textContent='Copied';b.style.color='#3fb950';b.style.borderColor='#3fb950';setTimeout(function(){b.textContent='Copy';b.style.color='#8b949e';b.style.borderColor='#3a3a3a';},2000);})})(this)\" style=\"background:#21262d;border:1px solid #3a3a3a;color:#8b949e;font-family:'Inter',sans-serif;font-size:11px;font-weight:600;padding:4px 12px;border-radius:4px;cursor:pointer;\">Copy<\/button><\/div>\n<pre style=\"padding:18px 20px;overflow-x:auto;font-family:'JetBrains Mono',monospace;font-size:13px;line-height:1.75;color:#e6edf3;background:#0d1117;margin:0;\"><span style=\"color:#6e7681;font-style:italic;\"># Top attacking IPs from auth log<\/span>\ngrep \"Failed password\" \/var\/log\/auth.log | awk '{print $11}' | sort | uniq -c | sort -rn | head -20<\/pre>\n<\/div>\n<p style=\"font-family:'Lora',Georgia,serif;font-size:17px;line-height:1.85;color:#2c2c2c;margin-bottom:18px;\">Run this over several days. The same \/16 or \/24 ranges will tend to reappear. That is the signal to act on.<\/p>\n<p style=\"font-family:'Inter',sans-serif;font-size:12px;font-weight:600;color:#6b6760;text-transform:uppercase;letter-spacing:0.09em;margin:30px 0 10px;\">Step 2: Find the Full CIDR Range<\/p>\n<div style=\"border-radius:8px;overflow:hidden;border:1px solid #2a2a2a;margin:0 0 22px;\">\n<div style=\"display:flex;align-items:center;justify-content:space-between;padding:9px 14px;background:#161b22;border-bottom:1px solid #2a2a2a;\">\n<span style=\"font-family:'Inter',sans-serif;font-size:11px;font-weight:600;color:#8b949e;text-transform:uppercase;letter-spacing:0.09em;\">bash<\/span><br \/>\n<button onclick=\"(function(b){var p=b.parentNode.parentNode.querySelector('pre');var t=p.innerText||p.textContent;navigator.clipboard.writeText(t).then(function(){b.textContent='Copied';b.style.color='#3fb950';b.style.borderColor='#3fb950';setTimeout(function(){b.textContent='Copy';b.style.color='#8b949e';b.style.borderColor='#3a3a3a';},2000);})})(this)\" style=\"background:#21262d;border:1px solid #3a3a3a;color:#8b949e;font-family:'Inter',sans-serif;font-size:11px;font-weight:600;padding:4px 12px;border-radius:4px;cursor:pointer;\">Copy<\/button><\/div>\n<pre style=\"padding:18px 20px;overflow-x:auto;font-family:'JetBrains Mono',monospace;font-size:13px;line-height:1.75;color:#e6edf3;background:#0d1117;margin:0;\">whois 167.99.1.1 | grep -i \"CIDR\\|NetRange\\|inetnum\"<\/pre>\n<\/div>\n<p style=\"font-family:'Inter',sans-serif;font-size:12px;font-weight:600;color:#6b6760;text-transform:uppercase;letter-spacing:0.09em;margin:30px 0 10px;\">Step 3: Block the Entire Range<\/p>\n<p style=\"font-family:'Lora',Georgia,serif;font-size:17px;line-height:1.85;color:#2c2c2c;margin-bottom:18px;\">Rather than managing individual IPs, the script below blocks all known Digital Ocean IPv4 ranges in a single pass. Save it as <code style=\"font-family:'JetBrains Mono',monospace;font-size:13px;background:#f0ede8;color:#c2410c;padding:2px 6px;border-radius:3px;border:1px solid #dedad4;\">block-digitalocean.sh<\/code> and run as root. It skips ranges already blocked, detects your OS, and persists the rules across reboots on Debian, Ubuntu, AlmaLinux, and RHEL.<\/p>\n<div style=\"border-radius:8px;overflow:hidden;border:1px solid #2a2a2a;margin:0 0 22px;\">\n<div style=\"display:flex;align-items:center;justify-content:space-between;padding:9px 14px;background:#161b22;border-bottom:1px solid #2a2a2a;\">\n<span style=\"font-family:'Inter',sans-serif;font-size:11px;font-weight:600;color:#8b949e;text-transform:uppercase;letter-spacing:0.09em;\">bash<\/span><br \/>\n<button onclick=\"(function(b){var p=b.parentNode.parentNode.querySelector('pre');var t=p.innerText||p.textContent;navigator.clipboard.writeText(t).then(function(){b.textContent='Copied';b.style.color='#3fb950';b.style.borderColor='#3fb950';setTimeout(function(){b.textContent='Copy';b.style.color='#8b949e';b.style.borderColor='#3a3a3a';},2000);})})(this)\" style=\"background:#21262d;border:1px solid #3a3a3a;color:#8b949e;font-family:'Inter',sans-serif;font-size:11px;font-weight:600;padding:4px 12px;border-radius:4px;cursor:pointer;\">Copy<\/button><\/div>\n<pre style=\"padding:18px 20px;overflow-x:auto;font-family:'JetBrains Mono',monospace;font-size:13px;line-height:1.75;color:#e6edf3;background:#0d1117;margin:0;\">sudo chmod +x block-digitalocean.sh\nsudo .\/block-digitalocean.sh<\/pre>\n<\/div>\n<p><!-- FULL SCRIPT --><\/p>\n<h2 style=\"font-family:'Lora',Georgia,serif;font-size:22px;font-weight:700;color:#1a1a1a;margin:0 0 18px;padding-bottom:14px;border-bottom:1px solid #e2e0da;\">The Script: block-digitalocean.sh<\/h2>\n<div style=\"border-radius:8px;overflow:hidden;border:1px solid #2a2a2a;margin:0 0 40px;\">\n<div style=\"display:flex;align-items:center;justify-content:space-between;padding:9px 14px;background:#161b22;border-bottom:1px solid #2a2a2a;\">\n<span style=\"font-family:'Inter',sans-serif;font-size:11px;font-weight:600;color:#8b949e;text-transform:uppercase;letter-spacing:0.09em;\">bash: block-digitalocean.sh<\/span><br \/>\n<button onclick=\"(function(b){var p=b.parentNode.parentNode.querySelector('pre');var t=p.innerText||p.textContent;navigator.clipboard.writeText(t).then(function(){b.textContent='Copied';b.style.color='#3fb950';b.style.borderColor='#3fb950';setTimeout(function(){b.textContent='Copy';b.style.color='#8b949e';b.style.borderColor='#3a3a3a';},2000);})})(this)\" style=\"background:#21262d;border:1px solid #3a3a3a;color:#8b949e;font-family:'Inter',sans-serif;font-size:11px;font-weight:600;padding:4px 12px;border-radius:4px;cursor:pointer;\">Copy<\/button><\/div>\n<pre style=\"padding:18px 20px;overflow-x:auto;font-family:'JetBrains Mono',monospace;font-size:13px;line-height:1.75;color:#e6edf3;background:#0d1117;margin:0;\">#!\/bin\/bash\n<span style=\"color:#6e7681;font-style:italic;\">#\n# Block Digital Ocean IP Ranges\n# Usage: sudo .\/block-digitalocean.sh\n#<\/span>\n\nset -e\n\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nNC='\\033[0m'\n\nif [[ $EUID -ne 0 ]]; then\n   echo -e \"${RED}Error: This script must be run as root${NC}\"\n   exit 1\nfi\n\nDO_RANGES=(\n    \"5.101.0.0\/16\"    \"24.144.0.0\/16\"   \"24.199.0.0\/16\"\n    \"37.139.0.0\/16\"   \"45.55.0.0\/16\"    \"46.101.0.0\/16\"\n    \"64.23.0.0\/16\"    \"64.225.0.0\/16\"   \"64.226.0.0\/16\"\n    \"64.227.0.0\/16\"   \"67.205.0.0\/16\"   \"67.207.0.0\/16\"\n    \"68.183.0.0\/16\"   \"69.55.0.0\/16\"    \"80.240.0.0\/16\"\n    \"82.196.0.0\/16\"   \"95.85.0.0\/16\"    \"103.253.0.0\/16\"\n    \"104.131.0.0\/16\"  \"104.236.0.0\/16\"  \"104.248.0.0\/16\"\n    \"107.170.0.0\/16\"  \"128.199.0.0\/16\"  \"129.212.0.0\/16\"\n    \"134.122.0.0\/16\"  \"134.199.0.0\/16\"  \"134.209.0.0\/16\"\n    \"137.184.0.0\/16\"  \"138.68.0.0\/16\"   \"138.197.0.0\/16\"\n    \"139.59.0.0\/16\"   \"141.0.0.0\/16\"    \"142.93.0.0\/16\"\n    \"143.110.0.0\/16\"  \"143.198.0.0\/16\"  \"143.244.0.0\/16\"\n    \"144.126.0.0\/16\"  \"146.185.0.0\/16\"  \"146.190.0.0\/16\"\n    \"147.182.0.0\/16\"  \"152.42.0.0\/16\"   \"157.230.0.0\/16\"\n    \"157.245.0.0\/16\"  \"159.65.0.0\/16\"   \"159.89.0.0\/16\"\n    \"159.203.0.0\/16\"  \"159.223.0.0\/16\"  \"161.35.0.0\/16\"\n    \"162.243.0.0\/16\"  \"163.47.0.0\/16\"   \"164.90.0.0\/16\"\n    \"164.92.0.0\/16\"   \"165.22.0.0\/16\"   \"165.227.0.0\/16\"\n    \"165.232.0.0\/16\"  \"165.245.0.0\/16\"  \"167.71.0.0\/16\"\n    \"167.99.0.0\/16\"   \"167.172.0.0\/16\"  \"168.144.0.0\/16\"\n    \"170.64.0.0\/16\"   \"174.138.0.0\/16\"  \"178.62.0.0\/16\"\n    \"178.128.0.0\/16\"  \"185.14.0.0\/16\"   \"188.166.0.0\/16\"\n    \"188.226.0.0\/16\"  \"192.34.0.0\/16\"   \"192.81.0.0\/16\"\n    \"192.241.0.0\/16\"  \"198.199.0.0\/16\"  \"198.211.0.0\/16\"\n    \"204.48.0.0\/16\"   \"206.81.0.0\/16\"   \"206.189.0.0\/16\"\n    \"207.154.0.0\/16\"  \"208.68.0.0\/16\"   \"209.38.0.0\/16\"\n    \"209.97.0.0\/16\"\n)\n\nis_blocked() { iptables -L INPUT -n | grep -q \"$1\"; }\n\nsave_iptables() {\n    if command -v netfilter-persistent &> \/dev\/null; then\n        netfilter-persistent save\n    elif [[ -f \/etc\/redhat-release ]]; then\n        iptables-save > \/etc\/sysconfig\/iptables\n    else\n        iptables-save > \/etc\/iptables.rules\n        if [[ ! -f \/etc\/network\/if-pre-up.d\/iptables ]]; then\n            echo '#!\/bin\/sh\\n\/sbin\/iptables-restore < \/etc\/iptables.rules' \\\n              > \/etc\/network\/if-pre-up.d\/iptables\n            chmod +x \/etc\/network\/if-pre-up.d\/iptables\n        fi\n    fi\n}\n\nadded=0; skipped=0\n\nfor range in \"${DO_RANGES[@]}\"; do\n    if is_blocked \"$range\"; then ((skipped++))\n    else\n        iptables -I INPUT -s \"$range\" -j DROP -m comment --comment \"DigitalOcean Block\"\n        ((added++))\n    fi\ndone\n\necho \"Blocked: $added | Skipped: $skipped\"\n[[ $added -gt 0 ]] && save_iptables\n\necho \"Done. Verify: iptables -L INPUT -n | grep 'DigitalOcean' | wc -l\"<\/pre>\n<\/div>\n<hr style=\"border:none;border-top:1px solid #e2e0da;margin:48px 0;\"\/>\n<p><!-- SECTION 1 --><\/p>\n<h2 style=\"font-family:'Lora',Georgia,serif;font-size:22px;font-weight:700;color:#1a1a1a;margin:0 0 18px;padding-bottom:14px;border-bottom:1px solid #e2e0da;\"><span style=\"display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;background:#c2410c;color:#fff;font-family:'Inter',sans-serif;font-size:13px;font-weight:700;border-radius:5px;margin-right:12px;vertical-align:middle;\">1<\/span>Avoid Predictable Usernames<\/h2>\n<p style=\"font-family:'Lora',Georgia,serif;font-size:17px;line-height:1.85;color:#2c2c2c;margin-bottom:18px;\">Every automated credential campaign works from roughly the same list: <code style=\"font-family:'JetBrains Mono',monospace;font-size:13px;background:#f0ede8;color:#c2410c;padding:2px 6px;border-radius:3px;border:1px solid #dedad4;\">admin<\/code>, <code style=\"font-family:'JetBrains Mono',monospace;font-size:13px;background:#f0ede8;color:#c2410c;padding:2px 6px;border-radius:3px;border:1px solid #dedad4;\">administrator<\/code>, <code style=\"font-family:'JetBrains Mono',monospace;font-size:13px;background:#f0ede8;color:#c2410c;padding:2px 6px;border-radius:3px;border:1px solid #dedad4;\">root<\/code>, <code style=\"font-family:'JetBrains Mono',monospace;font-size:13px;background:#f0ede8;color:#c2410c;padding:2px 6px;border-radius:3px;border:1px solid #dedad4;\">user<\/code>, <code style=\"font-family:'JetBrains Mono',monospace;font-size:13px;background:#f0ede8;color:#c2410c;padding:2px 6px;border-radius:3px;border:1px solid #dedad4;\">test<\/code>. If your system account appears on that list, a significant portion of the work has already been done before any real effort is made.<\/p>\n<p style=\"font-family:'Lora',Georgia,serif;font-size:17px;line-height:1.85;color:#2c2c2c;margin-bottom:18px;\">The less obvious improvement is to move away from English usernames entirely. Credential wordlists are almost exclusively English-centric. A username like <code style=\"font-family:'JetBrains Mono',monospace;font-size:13px;background:#f0ede8;color:#c2410c;padding:2px 6px;border-radius:3px;border:1px solid #dedad4;\">gweinyddwr<\/code> (Welsh), <code style=\"font-family:'JetBrains Mono',monospace;font-size:13px;background:#f0ede8;color:#c2410c;padding:2px 6px;border-radius:3px;border:1px solid #dedad4;\">rendszergazda<\/code> (Hungarian), or <code style=\"font-family:'JetBrains Mono',monospace;font-size:13px;background:#f0ede8;color:#c2410c;padding:2px 6px;border-radius:3px;border:1px solid #dedad4;\">j\u00e4rjestelm\u00e4nvalvoja<\/code> (Finnish) simply will not appear in any standard dictionary attack.<\/p>\n<div style=\"border-radius:8px;overflow:hidden;border:1px solid #2a2a2a;margin:0 0 40px;\">\n<div style=\"display:flex;align-items:center;justify-content:space-between;padding:9px 14px;background:#161b22;border-bottom:1px solid #2a2a2a;\">\n<span style=\"font-family:'Inter',sans-serif;font-size:11px;font-weight:600;color:#8b949e;text-transform:uppercase;letter-spacing:0.09em;\">bash<\/span><br \/>\n<button onclick=\"(function(b){var p=b.parentNode.parentNode.querySelector('pre');var t=p.innerText||p.textContent;navigator.clipboard.writeText(t).then(function(){b.textContent='Copied';b.style.color='#3fb950';b.style.borderColor='#3fb950';setTimeout(function(){b.textContent='Copy';b.style.color='#8b949e';b.style.borderColor='#3a3a3a';},2000);})})(this)\" style=\"background:#21262d;border:1px solid #3a3a3a;color:#8b949e;font-family:'Inter',sans-serif;font-size:11px;font-weight:600;padding:4px 12px;border-radius:4px;cursor:pointer;\">Copy<\/button><\/div>\n<pre style=\"padding:18px 20px;overflow-x:auto;font-family:'JetBrains Mono',monospace;font-size:13px;line-height:1.75;color:#e6edf3;background:#0d1117;margin:0;\"><span style=\"color:#6e7681;font-style:italic;\"># Create a non-English admin user<\/span>\nadduser gweinyddwr\nusermod -aG sudo gweinyddwr\n\n<span style=\"color:#6e7681;font-style:italic;\"># Disable root SSH login<\/span>\necho \"PermitRootLogin no\" >> \/etc\/ssh\/sshd_config\nsystemctl restart sshd<\/pre>\n<\/div>\n<p><!-- SECTION 2 --><\/p>\n<h2 style=\"font-family:'Lora',Georgia,serif;font-size:22px;font-weight:700;color:#1a1a1a;margin:0 0 18px;padding-bottom:14px;border-bottom:1px solid #e2e0da;\"><span style=\"display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;background:#c2410c;color:#fff;font-family:'Inter',sans-serif;font-size:13px;font-weight:700;border-radius:5px;margin-right:12px;vertical-align:middle;\">2<\/span>A Practical Approach to Password Entropy<\/h2>\n<p style=\"font-family:'Lora',Georgia,serif;font-size:17px;line-height:1.85;color:#2c2c2c;margin-bottom:18px;\">Take a memorable word, run it through an MD5 hash, and use a portion of the output as the password. The result is genuinely high-entropy, looks entirely random to anyone who does not know the source word, and can be regenerated at any time without ever being written down.<\/p>\n<div style=\"border-radius:8px;overflow:hidden;border:1px solid #2a2a2a;margin:0 0 22px;\">\n<div style=\"display:flex;align-items:center;justify-content:space-between;padding:9px 14px;background:#161b22;border-bottom:1px solid #2a2a2a;\">\n<span style=\"font-family:'Inter',sans-serif;font-size:11px;font-weight:600;color:#8b949e;text-transform:uppercase;letter-spacing:0.09em;\">bash<\/span><br \/>\n<button onclick=\"(function(b){var p=b.parentNode.parentNode.querySelector('pre');var t=p.innerText||p.textContent;navigator.clipboard.writeText(t).then(function(){b.textContent='Copied';b.style.color='#3fb950';b.style.borderColor='#3fb950';setTimeout(function(){b.textContent='Copy';b.style.color='#8b949e';b.style.borderColor='#3a3a3a';},2000);})})(this)\" style=\"background:#21262d;border:1px solid #3a3a3a;color:#8b949e;font-family:'Inter',sans-serif;font-size:11px;font-weight:600;padding:4px 12px;border-radius:4px;cursor:pointer;\">Copy<\/button><\/div>\n<pre style=\"padding:18px 20px;overflow-x:auto;font-family:'JetBrains Mono',monospace;font-size:13px;line-height:1.75;color:#e6edf3;background:#0d1117;margin:0;\">echo -n \"lighthouse\" | md5sum\n<span style=\"color:#6e7681;font-style:italic;\"># Output:   6f6c60b5a8e5f6a4b2c3d1e9f7a8b0c2<\/span>\n<span style=\"color:#6e7681;font-style:italic;\"># Password: 6f6c60b5a8e5 (first 12 characters)<\/span><\/pre>\n<\/div>\n<p style=\"font-family:'Lora',Georgia,serif;font-size:17px;line-height:1.85;color:#2c2c2c;margin-bottom:40px;\">No dictionary-based attack will arrive at <code style=\"font-family:'JetBrains Mono',monospace;font-size:13px;background:#f0ede8;color:#c2410c;padding:2px 6px;border-radius:3px;border:1px solid #dedad4;\">6f6c60b5<\/code> by working through common English words. Additional complexity can be introduced by using a phrase rather than a single word, selecting a different character range, or appending a symbol.<\/p>\n<p><!-- SECTION 3 --><\/p>\n<h2 style=\"font-family:'Lora',Georgia,serif;font-size:22px;font-weight:700;color:#1a1a1a;margin:0 0 18px;padding-bottom:14px;border-bottom:1px solid #e2e0da;\"><span style=\"display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;background:#c2410c;color:#fff;font-family:'Inter',sans-serif;font-size:13px;font-weight:700;border-radius:5px;margin-right:12px;vertical-align:middle;\">3<\/span>Restrict SSH to Known IP Ranges<\/h2>\n<p style=\"font-family:'Lora',Georgia,serif;font-size:17px;line-height:1.85;color:#2c2c2c;margin-bottom:18px;\">There is generally no good reason for SSH to be reachable from the open internet. Restricting access to your known IP ranges at the firewall level means the majority of automated scanners will receive no response and move on.<\/p>\n<p style=\"font-family:'Inter',sans-serif;font-size:12px;font-weight:600;color:#6b6760;text-transform:uppercase;letter-spacing:0.09em;margin:30px 0 10px;\">UFW<\/p>\n<div style=\"border-radius:8px;overflow:hidden;border:1px solid #2a2a2a;margin:0 0 22px;\">\n<div style=\"display:flex;align-items:center;justify-content:space-between;padding:9px 14px;background:#161b22;border-bottom:1px solid #2a2a2a;\">\n<span style=\"font-family:'Inter',sans-serif;font-size:11px;font-weight:600;color:#8b949e;text-transform:uppercase;letter-spacing:0.09em;\">bash<\/span><br \/>\n<button onclick=\"(function(b){var p=b.parentNode.parentNode.querySelector('pre');var t=p.innerText||p.textContent;navigator.clipboard.writeText(t).then(function(){b.textContent='Copied';b.style.color='#3fb950';b.style.borderColor='#3fb950';setTimeout(function(){b.textContent='Copy';b.style.color='#8b949e';b.style.borderColor='#3a3a3a';},2000);})})(this)\" style=\"background:#21262d;border:1px solid #3a3a3a;color:#8b949e;font-family:'Inter',sans-serif;font-size:11px;font-weight:600;padding:4px 12px;border-radius:4px;cursor:pointer;\">Copy<\/button><\/div>\n<pre style=\"padding:18px 20px;overflow-x:auto;font-family:'JetBrains Mono',monospace;font-size:13px;line-height:1.75;color:#e6edf3;background:#0d1117;margin:0;\">ufw allow from 203.0.113.0\/24 to any port 22\nufw deny 22\nufw enable<\/pre>\n<\/div>\n<p style=\"font-family:'Inter',sans-serif;font-size:12px;font-weight:600;color:#6b6760;text-transform:uppercase;letter-spacing:0.09em;margin:30px 0 10px;\">iptables<\/p>\n<div style=\"border-radius:8px;overflow:hidden;border:1px solid #2a2a2a;margin:0 0 22px;\">\n<div style=\"display:flex;align-items:center;justify-content:space-between;padding:9px 14px;background:#161b22;border-bottom:1px solid #2a2a2a;\">\n<span style=\"font-family:'Inter',sans-serif;font-size:11px;font-weight:600;color:#8b949e;text-transform:uppercase;letter-spacing:0.09em;\">bash<\/span><br \/>\n<button onclick=\"(function(b){var p=b.parentNode.parentNode.querySelector('pre');var t=p.innerText||p.textContent;navigator.clipboard.writeText(t).then(function(){b.textContent='Copied';b.style.color='#3fb950';b.style.borderColor='#3fb950';setTimeout(function(){b.textContent='Copy';b.style.color='#8b949e';b.style.borderColor='#3a3a3a';},2000);})})(this)\" style=\"background:#21262d;border:1px solid #3a3a3a;color:#8b949e;font-family:'Inter',sans-serif;font-size:11px;font-weight:600;padding:4px 12px;border-radius:4px;cursor:pointer;\">Copy<\/button><\/div>\n<pre style=\"padding:18px 20px;overflow-x:auto;font-family:'JetBrains Mono',monospace;font-size:13px;line-height:1.75;color:#e6edf3;background:#0d1117;margin:0;\">iptables -A INPUT -p tcp --dport 22 -s 203.0.113.0\/24 -j ACCEPT\niptables -A INPUT -p tcp --dport 22 -j DROP<\/pre>\n<\/div>\n<p style=\"font-family:'Lora',Georgia,serif;font-size:17px;line-height:1.85;color:#2c2c2c;margin-bottom:40px;\">For environments with dynamic IPs, a VPN is the sensible approach. Establish the connection first and SSH from within that tunnel. The VPN endpoint becomes the single controlled entry point.<\/p>\n<p><!-- SECTION 4 --><\/p>\n<h2 style=\"font-family:'Lora',Georgia,serif;font-size:22px;font-weight:700;color:#1a1a1a;margin:0 0 18px;padding-bottom:14px;border-bottom:1px solid #e2e0da;\"><span style=\"display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;background:#c2410c;color:#fff;font-family:'Inter',sans-serif;font-size:13px;font-weight:700;border-radius:5px;margin-right:12px;vertical-align:middle;\">4<\/span>Consider a Honeypot for Threat Intelligence<\/h2>\n<p style=\"font-family:'Lora',Georgia,serif;font-size:17px;line-height:1.85;color:#2c2c2c;margin-bottom:18px;\">The previous approaches are all preventative. A honeypot serves a different purpose: rather than blocking activity, it allows it into a controlled environment in order to observe it. When an attacker reaches a honeypot, you gain visibility into which vectors were used, what they do once they believe they have access, and where the traffic originated.<\/p>\n<p style=\"font-family:'Lora',Georgia,serif;font-size:17px;line-height:1.85;color:#2c2c2c;margin-bottom:18px;\">This is useful for auditing real systems. If the honeypot shows repeated attempts against a particular service or configuration, that is worth examining in production.<\/p>\n<div style=\"border-radius:8px;overflow:hidden;border:1px solid #2a2a2a;margin:0 0 22px;\">\n<div style=\"display:flex;align-items:center;justify-content:space-between;padding:9px 14px;background:#161b22;border-bottom:1px solid #2a2a2a;\">\n<span style=\"font-family:'Inter',sans-serif;font-size:11px;font-weight:600;color:#8b949e;text-transform:uppercase;letter-spacing:0.09em;\">bash: Cowrie SSH Honeypot<\/span><br \/>\n<button onclick=\"(function(b){var p=b.parentNode.parentNode.querySelector('pre');var t=p.innerText||p.textContent;navigator.clipboard.writeText(t).then(function(){b.textContent='Copied';b.style.color='#3fb950';b.style.borderColor='#3fb950';setTimeout(function(){b.textContent='Copy';b.style.color='#8b949e';b.style.borderColor='#3a3a3a';},2000);})})(this)\" style=\"background:#21262d;border:1px solid #3a3a3a;color:#8b949e;font-family:'Inter',sans-serif;font-size:11px;font-weight:600;padding:4px 12px;border-radius:4px;cursor:pointer;\">Copy<\/button><\/div>\n<pre style=\"padding:18px 20px;overflow-x:auto;font-family:'JetBrains Mono',monospace;font-size:13px;line-height:1.75;color:#e6edf3;background:#0d1117;margin:0;\">apt install python3-virtualenv libssl-dev libffi-dev build-essential\ngit clone https:\/\/github.com\/cowrie\/cowrie\ncd cowrie\nvirtualenv cowrie-env\nsource cowrie-env\/bin\/activate\npip install -r requirements.txt\ncp etc\/cowrie.cfg.dist etc\/cowrie.cfg\nbin\/cowrie start<\/pre>\n<\/div>\n<p style=\"font-family:'Lora',Georgia,serif;font-size:17px;line-height:1.85;color:#2c2c2c;margin-bottom:40px;\">Cowrie presents a convincing SSH environment. Everything an attacker types within it is logged in full. The session logs tend to be instructive.<\/p>\n<p><!-- SECTION 5 --><\/p>\n<h2 style=\"font-family:'Lora',Georgia,serif;font-size:22px;font-weight:700;color:#1a1a1a;margin:0 0 18px;padding-bottom:14px;border-bottom:1px solid #e2e0da;\"><span style=\"display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;background:#c2410c;color:#fff;font-family:'Inter',sans-serif;font-size:13px;font-weight:700;border-radius:5px;margin-right:12px;vertical-align:middle;\">5<\/span>Maintain Reliable Backups<\/h2>\n<p style=\"font-family:'Lora',Georgia,serif;font-size:17px;line-height:1.85;color:#2c2c2c;margin-bottom:18px;\">The layers above reduce the likelihood of a successful intrusion considerably. They do not eliminate it entirely. A zero-day, a misconfigured service, or a compromised credential can all create an opening regardless of how well everything else is configured.<\/p>\n<div style=\"background:#fff4ef;border-left:3px solid #c2410c;border-radius:0 6px 6px 0;padding:16px 20px;margin:24px 0;\">\n<p style=\"font-family:'Inter',sans-serif;font-size:16px;line-height:1.75;color:#2c2c2c;margin:0;\">A well-maintained backup changes the calculus significantly. If an attacker gains access, causes damage, and the system is restored within a few minutes from a clean snapshot, the effort has achieved nothing of lasting consequence. The time spent on the attack is simply wasted.<\/p>\n<\/div>\n<p style=\"font-family:'Inter',sans-serif;font-size:12px;font-weight:600;color:#6b6760;text-transform:uppercase;letter-spacing:0.09em;margin:30px 0 10px;\">Daily rsync to a Remote Server<\/p>\n<div style=\"border-radius:8px;overflow:hidden;border:1px solid #2a2a2a;margin:0 0 22px;\">\n<div style=\"display:flex;align-items:center;justify-content:space-between;padding:9px 14px;background:#161b22;border-bottom:1px solid #2a2a2a;\">\n<span style=\"font-family:'Inter',sans-serif;font-size:11px;font-weight:600;color:#8b949e;text-transform:uppercase;letter-spacing:0.09em;\">bash<\/span><br \/>\n<button onclick=\"(function(b){var p=b.parentNode.parentNode.querySelector('pre');var t=p.innerText||p.textContent;navigator.clipboard.writeText(t).then(function(){b.textContent='Copied';b.style.color='#3fb950';b.style.borderColor='#3fb950';setTimeout(function(){b.textContent='Copy';b.style.color='#8b949e';b.style.borderColor='#3a3a3a';},2000);})})(this)\" style=\"background:#21262d;border:1px solid #3a3a3a;color:#8b949e;font-family:'Inter',sans-serif;font-size:11px;font-weight:600;padding:4px 12px;border-radius:4px;cursor:pointer;\">Copy<\/button><\/div>\n<pre style=\"padding:18px 20px;overflow-x:auto;font-family:'JetBrains Mono',monospace;font-size:13px;line-height:1.75;color:#e6edf3;background:#0d1117;margin:0;\"><span style=\"color:#6e7681;font-style:italic;\"># Sync web root and config to a remote backup server<\/span>\nrsync -avz --delete \/var\/www\/ user@backup-server:\/backups\/www\/\nrsync -avz --delete \/etc\/ user@backup-server:\/backups\/etc\/<\/pre>\n<\/div>\n<p style=\"font-family:'Inter',sans-serif;font-size:12px;font-weight:600;color:#6b6760;text-transform:uppercase;letter-spacing:0.09em;margin:30px 0 10px;\">Nightly Database Dumps via Cron<\/p>\n<div style=\"border-radius:8px;overflow:hidden;border:1px solid #2a2a2a;margin:0 0 22px;\">\n<div style=\"display:flex;align-items:center;justify-content:space-between;padding:9px 14px;background:#161b22;border-bottom:1px solid #2a2a2a;\">\n<span style=\"font-family:'Inter',sans-serif;font-size:11px;font-weight:600;color:#8b949e;text-transform:uppercase;letter-spacing:0.09em;\">bash<\/span><br \/>\n<button onclick=\"(function(b){var p=b.parentNode.parentNode.querySelector('pre');var t=p.innerText||p.textContent;navigator.clipboard.writeText(t).then(function(){b.textContent='Copied';b.style.color='#3fb950';b.style.borderColor='#3fb950';setTimeout(function(){b.textContent='Copy';b.style.color='#8b949e';b.style.borderColor='#3a3a3a';},2000);})})(this)\" style=\"background:#21262d;border:1px solid #3a3a3a;color:#8b949e;font-family:'Inter',sans-serif;font-size:11px;font-weight:600;padding:4px 12px;border-radius:4px;cursor:pointer;\">Copy<\/button><\/div>\n<pre style=\"padding:18px 20px;overflow-x:auto;font-family:'JetBrains Mono',monospace;font-size:13px;line-height:1.75;color:#e6edf3;background:#0d1117;margin:0;\"><span style=\"color:#6e7681;font-style:italic;\"># MySQL \/ MariaDB nightly backup<\/span>\nmysqldump -u root -p --all-databases | gzip > \/backups\/db-$(date +%F).sql.gz\n\n<span style=\"color:#6e7681;font-style:italic;\"># Cron entry: runs at 2am daily<\/span>\n0 2 * * * mysqldump -u root -p --all-databases | gzip > \/backups\/db-$(date +%F).sql.gz<\/pre>\n<\/div>\n<p style=\"font-family:'Lora',Georgia,serif;font-size:17px;line-height:1.85;color:#2c2c2c;margin-bottom:40px;\">A backup that has never been tested is not a backup in any meaningful sense. Run a restore drill on a test machine periodically so the steps are familiar when they are actually needed.<\/p>\n<hr style=\"border:none;border-top:1px solid #e2e0da;margin:48px 0;\"\/>\n<p><!-- SUMMARY TABLE --><\/p>\n<h2 style=\"font-family:'Lora',Georgia,serif;font-size:22px;font-weight:700;color:#1a1a1a;margin:0 0 18px;padding-bottom:14px;border-bottom:1px solid #e2e0da;\">Summary<\/h2>\n<table style=\"width:100%;border-collapse:collapse;border:1px solid #e2e0da;border-radius:8px;overflow:hidden;font-size:15px;margin-bottom:40px;\">\n<thead>\n<tr style=\"background:#f3f2ef;\">\n<th style=\"text-align:left;padding:12px 16px;font-family:'Inter',sans-serif;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.08em;color:#6b6760;border-bottom:1px solid #e2e0da;\">Layer<\/th>\n<th style=\"text-align:left;padding:12px 16px;font-family:'Inter',sans-serif;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.08em;color:#6b6760;border-bottom:1px solid #e2e0da;\">Approach<\/th>\n<th style=\"text-align:left;padding:12px 16px;font-family:'Inter',sans-serif;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.08em;color:#6b6760;border-bottom:1px solid #e2e0da;\">What It Addresses<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr style=\"border-bottom:1px solid #e2e0da;\">\n<td style=\"padding:13px 16px;font-family:'Inter',sans-serif;font-weight:600;color:#c2410c;font-size:12px;text-transform:uppercase;letter-spacing:0.06em;\">Network<\/td>\n<td style=\"padding:13px 16px;font-weight:600;color:#1a1a1a;\">Block known attack ranges<\/td>\n<td style=\"padding:13px 16px;color:#6b6760;font-family:'Inter',sans-serif;font-size:14.5px;\">Removes entire blocks of abusive infrastructure<\/td>\n<\/tr>\n<tr style=\"border-bottom:1px solid #e2e0da;background:#faf9f7;\">\n<td style=\"padding:13px 16px;font-family:'Inter',sans-serif;font-weight:600;color:#c2410c;font-size:12px;text-transform:uppercase;letter-spacing:0.06em;\">Identity<\/td>\n<td style=\"padding:13px 16px;font-weight:600;color:#1a1a1a;\">Non-English usernames<\/td>\n<td style=\"padding:13px 16px;color:#6b6760;font-family:'Inter',sans-serif;font-size:14.5px;\">Dictionary and credential stuffing campaigns<\/td>\n<\/tr>\n<tr style=\"border-bottom:1px solid #e2e0da;\">\n<td style=\"padding:13px 16px;font-family:'Inter',sans-serif;font-weight:600;color:#c2410c;font-size:12px;text-transform:uppercase;letter-spacing:0.06em;\">Auth<\/td>\n<td style=\"padding:13px 16px;font-weight:600;color:#1a1a1a;\">MD5-derived passwords<\/td>\n<td style=\"padding:13px 16px;color:#6b6760;font-family:'Inter',sans-serif;font-size:14.5px;\">Brute force and pattern-based cracking<\/td>\n<\/tr>\n<tr style=\"border-bottom:1px solid #e2e0da;background:#faf9f7;\">\n<td style=\"padding:13px 16px;font-family:'Inter',sans-serif;font-weight:600;color:#c2410c;font-size:12px;text-transform:uppercase;letter-spacing:0.06em;\">Access<\/td>\n<td style=\"padding:13px 16px;font-weight:600;color:#1a1a1a;\">IP-restricted SSH<\/td>\n<td style=\"padding:13px 16px;color:#6b6760;font-family:'Inter',sans-serif;font-size:14.5px;\">Automated scanning and opportunistic access<\/td>\n<\/tr>\n<tr style=\"border-bottom:1px solid #e2e0da;\">\n<td style=\"padding:13px 16px;font-family:'Inter',sans-serif;font-weight:600;color:#c2410c;font-size:12px;text-transform:uppercase;letter-spacing:0.06em;\">Intel<\/td>\n<td style=\"padding:13px 16px;font-weight:600;color:#1a1a1a;\">Honeypot deployment<\/td>\n<td style=\"padding:13px 16px;color:#6b6760;font-family:'Inter',sans-serif;font-size:14.5px;\">Visibility into attacker methods and tooling<\/td>\n<\/tr>\n<tr style=\"background:#faf9f7;\">\n<td style=\"padding:13px 16px;font-family:'Inter',sans-serif;font-weight:600;color:#c2410c;font-size:12px;text-transform:uppercase;letter-spacing:0.06em;\">Recovery<\/td>\n<td style=\"padding:13px 16px;font-weight:600;color:#1a1a1a;\">Tested backups and snapshots<\/td>\n<td style=\"padding:13px 16px;color:#6b6760;font-family:'Inter',sans-serif;font-size:14.5px;\">Ensures a successful attack has no lasting impact<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p style=\"font-family:'Lora',Georgia,serif;font-size:17px;line-height:1.85;color:#6b6760;font-style:italic;border-left:3px solid #ccc9c0;padding-left:20px;margin-bottom:40px;\">None of this requires significant budget or specialist tooling. Most of it is a matter of configuration discipline. The automated activity showing up in server logs at present does not appear especially sophisticated. Systems that present even modest resistance tend to be skipped in favour of easier targets.<\/p>\n<p><!-- FURTHER READING --><\/p>\n<p style=\"font-family:'Inter',sans-serif;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.1em;color:#6b6760;margin-bottom:16px;\">Further Reading<\/p>\n<p><a href=\"https:\/\/nicktailor.com\/tech-blog\/how-to-deploy-open-akcauthorized-key-chain\/\" target=\"_blank\" rel=\"noopener\" style=\"display:block;text-decoration:none;background:#ffffff;border:1px solid #e2e0da;border-radius:8px;padding:22px 26px;margin-bottom:40px;\"><\/p>\n<p style=\"font-family:'Lora',Georgia,serif;font-size:17px;font-weight:700;color:#1a1a1a;margin-bottom:10px;\">How to Deploy OpenAKC (Authorized Key Chain)<\/p>\n<p style=\"font-family:'Inter',sans-serif;font-size:15px;color:#6b6760;line-height:1.75;margin-bottom:14px;\">The approaches above reduce the attack surface considerably. OpenAKC takes a different step altogether. It is an open-source authentication gateway that allows the <code style=\"font-family:'JetBrains Mono',monospace;font-size:12.5px;background:#f0ede8;color:#c2410c;padding:2px 5px;border-radius:3px;border:1px solid #dedad4;\">authorized_keys<\/code> mechanism to be disabled entirely across an estate, with SSH trust managed centrally. It also introduces the ability to strip specific Linux capabilities from root, meaning even a fully privileged user cannot touch files or directories you have designated as protected. If centralised access control, full session recording, and granular root capability management are relevant to your environment, the deployment guide is worth reading.<\/p>\n<p><span style=\"font-family:'Inter',sans-serif;font-size:12px;font-weight:600;color:#c2410c;\">nicktailor.com &#8599;<\/span><br \/>\n<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Server Security &nbsp;\/&nbsp; March 2026 The patterns showing up in server logs over recent months suggest that the attack surface has shifted in some fairly predictable ways. A few straightforward measures appear to address the bulk of it. The Pattern in the Logs: Digital Ocean Anyone running a public-facing server and watching their \/var\/log\/auth.log or fail2ban output will likely notice<a href=\"https:\/\/nicktailor.com\/tech-blog\/handleyoursecurty\/\" class=\"read-more\">Read More &#8230;<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[138,131],"tags":[],"class_list":["post-2251","post","type-post","status-publish","format-standard","hentry","category-linux","category-security"],"_links":{"self":[{"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/posts\/2251","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/comments?post=2251"}],"version-history":[{"count":11,"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/posts\/2251\/revisions"}],"predecessor-version":[{"id":2263,"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/posts\/2251\/revisions\/2263"}],"wp:attachment":[{"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/media?parent=2251"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/categories?post=2251"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/nicktailor.com\/tech-blog\/wp-json\/wp\/v2\/tags?post=2251"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}