<?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: V.Ray</title>
    <description>The latest articles on DEV Community by V.Ray (@vray).</description>
    <link>https://dev.to/vray</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%2F3564777%2Fa8644b3b-abac-430f-b5fe-7c40731163a6.jpg</url>
      <title>DEV Community: V.Ray</title>
      <link>https://dev.to/vray</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vray"/>
    <language>en</language>
    <item>
      <title>7 Azure Security Gaps I have Seen in Production (and How to Fix Them)</title>
      <dc:creator>V.Ray</dc:creator>
      <pubDate>Mon, 15 Dec 2025 00:59:05 +0000</pubDate>
      <link>https://dev.to/vray/7-azure-security-gaps-i-have-seen-in-production-and-how-to-fix-them-o70</link>
      <guid>https://dev.to/vray/7-azure-security-gaps-i-have-seen-in-production-and-how-to-fix-them-o70</guid>
      <description>&lt;p&gt;7 Azure Security Gaps I have Seen in Production (and How to Fix Them)&lt;/p&gt;

&lt;p&gt;Note: The issues described here are from real production environments, security audits, and incident reviews — not lab or demo setups.&lt;/p&gt;

&lt;p&gt;Over the past few years, while managing 100+ Azure virtual machines in production environments running critical PeopleSoft and Oracle workloads, I have repeatedly encountered the same security gaps across different organizations.&lt;/p&gt;

&lt;p&gt;These are not theoretical vulnerabilities from security blogs. They are real-world issues that surface during security audits, incident response investigations, and routine infrastructure reviews.&lt;/p&gt;

&lt;p&gt;In this article, I will walk through seven critical Azure security gaps I have personally identified in production, along with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Real-world examples&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Detection methods using Azure-native tools&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Practical, step-by-step remediation strategies&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whether you manage a handful of VMs or hundreds, addressing these gaps will significantly improve your Azure security posture before they turn into incidents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Inadequate Network Security Group (NSG) Rules&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Problem&lt;/p&gt;

&lt;p&gt;Network Security Groups (NSGs) are the primary network-level control for Azure VMs. Yet I often find production environments with overly permissive inbound rules such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Allow * from 0.0.0.0/0&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SSH (22) or RDP (3389) exposed to the public internet&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real-world example:&lt;br&gt;
During a security audit, I found 12 production VMs with SSH open to the internet. NSG flow logs showed 50,000+ failed login attempts in 30 days.&lt;/p&gt;

&lt;p&gt;How to Detect:&lt;/p&gt;

&lt;p&gt;Azure Portal&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Network Security Groups → Inbound rules&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Look for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Source: Any or 0.0.0.0/0&lt;/li&gt;
&lt;li&gt;Ports: 22, 3389, 1433, 5432&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Review Effective security rules per VM&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;KQL (NSG Flow Logs)&lt;/p&gt;

&lt;p&gt;AzureNetworkAnalytics_CL&lt;br&gt;
| where SubType_s == "FlowLog"&lt;br&gt;
| where SrcIP_s !startswith "10."&lt;br&gt;
  and SrcIP_s !startswith "172."&lt;br&gt;
  and SrcIP_s !startswith "192.168."&lt;br&gt;
| where DestPort_d in (22, 3389)&lt;br&gt;
| summarize Attempts = count() by SrcIP_s, DestPort_d&lt;br&gt;
| order by Attempts desc&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix&lt;/strong&gt;&lt;br&gt;
Immediate&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Remove 0.0.0.0/0 access for SSH/RDP&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Whitelist trusted office or VPN IPs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use Azure Bastion&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enable Just-In-Time (JIT) VM access via Microsoft Defender for Cloud&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Long-term&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use Application Security Groups (ASGs)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Implement Azure Firewall&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enforce NSG standards using Azure Policy&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Missing Azure Backup Policies&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Problem&lt;/p&gt;

&lt;p&gt;Many teams assume Azure automatically backs up VMs. It doesn’t.&lt;br&gt;
I have seen production workloads with no Recovery Services vault and no backups.&lt;/p&gt;

&lt;p&gt;How to Detect&lt;/p&gt;

&lt;p&gt;Azure Portal&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Recovery Services vaults → Backup items&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Compare against VM inventory&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Resources&lt;br&gt;
| where type == "microsoft.compute/virtualmachines"&lt;br&gt;
| project name, resourceGroup&lt;br&gt;
| join kind=leftouter (&lt;br&gt;
    RecoveryServicesResources&lt;br&gt;
    | where type contains "protectedItems"&lt;br&gt;
    | extend vmName = tostring(split(properties.sourceResourceId, "/")[8])&lt;br&gt;
    | project vmName&lt;br&gt;
) on $left.name == $right.vmName&lt;br&gt;
| where isnull(vmName)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create Recovery Services vaults per region&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Define policies aligned with RPO/RTO&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enable Soft Delete&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Test restores quarterly&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure backup alerts via Azure Monitor&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Weak Authentication Methods&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Problem&lt;/p&gt;

&lt;p&gt;Password-based SSH and RDP access is still common. I have seen environments using the same password across multiple admin accounts, creating a single point of failure.&lt;/p&gt;

&lt;p&gt;How to Detect&lt;/p&gt;

&lt;p&gt;Linux&lt;/p&gt;

&lt;p&gt;grep -i PasswordAuthentication /etc/ssh/sshd_config&lt;br&gt;
grep -i PubkeyAuthentication /etc/ssh/sshd_config&lt;/p&gt;

&lt;p&gt;Windows&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Review Azure AD Conditional Access&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check Azure AD sign-in logs&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Fix&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Linux&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Disable passwords: PasswordAuthentication no&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use SSH key-based authentication&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Store keys in Azure Key Vault&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enable Azure AD Login for Linux VMs&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Windows&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Enforce MFA&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reduce local admin usage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use Privileged Access Workstations (PAWs)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Unencrypted Data in Transit&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Problem&lt;/p&gt;

&lt;p&gt;HTTP endpoints, databases without TLS, and FTP transfers still exist in production — even for sensitive data.&lt;/p&gt;

&lt;p&gt;How to Detect&lt;/p&gt;

&lt;p&gt;Application Gateway / WAF Logs&lt;/p&gt;

&lt;p&gt;AzureDiagnostics&lt;br&gt;
| where ResourceType == "APPLICATIONGATEWAYS"&lt;br&gt;
| where requestUri_s startswith "http://"&lt;br&gt;
| summarize Count = count() by requestUri_s, clientIP_s&lt;br&gt;
| order by Count desc&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Enforce HTTPS end-to-end&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Redirect HTTP → HTTPS&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enable TLS 1.2+ for databases&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Replace FTP with SFTP/FTPS&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Manage certificates via Azure Key Vault&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;5. Improper Role-Based Access Control (RBAC)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Problem&lt;/p&gt;

&lt;p&gt;Developers often have Contributor or Owner access at subscription level, violating least privilege.&lt;/p&gt;

&lt;p&gt;How to Detect&lt;/p&gt;

&lt;p&gt;authorizationresources&lt;br&gt;
| where type == "microsoft.authorization/roleassignments"&lt;br&gt;
| where properties.roleDefinitionId contains "Owner"&lt;br&gt;
   or properties.roleDefinitionId contains "Contributor"&lt;br&gt;
| project principalId, scope = tostring(properties.scope)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Audit RBAC regularly&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Remove unnecessary subscription-level roles&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create custom roles&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use Azure AD PIM&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enable access reviews&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;6. Missing Activity Log Alerts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Problem&lt;/p&gt;

&lt;p&gt;Critical changes happen with no alerts, leaving teams blind.&lt;/p&gt;

&lt;p&gt;How to Detect&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Azure Monitor → Alerts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Filter by Activity Log&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Fix&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create alerts for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;NSG rule changes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;VM deletion/creation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;RBAC changes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Defender for Cloud policy updates&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Key Vault access changes&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;7. Exposed Management and Database Ports&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Problem&lt;/p&gt;

&lt;p&gt;Beyond SSH/RDP, I often find database ports and admin interfaces exposed to the internet.&lt;/p&gt;

&lt;p&gt;How to Detect&lt;/p&gt;

&lt;p&gt;AzureNetworkAnalytics_CL&lt;br&gt;
| where SubType_s == "FlowLog"&lt;br&gt;
| where DestPort_d in (1433, 3306, 5432, 8080, 8443, 27017, 6379)&lt;br&gt;
| where SrcIP_s !startswith "10."&lt;br&gt;
  and SrcIP_s !startswith "172."&lt;br&gt;
  and SrcIP_s !startswith "192.168."&lt;br&gt;
| summarize Count = count() by DestPort_d, DestIP_s&lt;br&gt;
| order by Count desc&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Close non-essential ports&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use Private Endpoints / Private Link&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deploy Application Gateway with WAF&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use Azure Bastion&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Follow Defender for Cloud recommendations&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Azure security is not a one-time setup. It requires continuous monitoring, audits, and proactive remediation.&lt;/p&gt;

&lt;p&gt;Most of these gaps exist not because of missing tools, but because of missing guardrails. Azure Policy, Defender for Cloud, and Infrastructure as Code can prevent most of them before they reach production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick Security Checklist (≈ 85 Minutes)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Audit NSG rules for 0.0.0.0/0 (5 min)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Verify VM backups (10 min)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Review RBAC assignments (15 min)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check SSH password authentication (10 min)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enable activity log alerts (20 min)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scan for exposed DB ports (10 min)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Identify HTTP traffic (15 min)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Let’s Discuss&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Have you seen similar Azure security issues in production?&lt;br&gt;
What guardrails do you use to prevent them?&lt;/p&gt;

</description>
      <category>azure</category>
      <category>cloud</category>
      <category>devops</category>
      <category>security</category>
    </item>
    <item>
      <title>7 SSH Security Practices Every System Administrator Should Implement</title>
      <dc:creator>V.Ray</dc:creator>
      <pubDate>Thu, 16 Oct 2025 14:08:52 +0000</pubDate>
      <link>https://dev.to/vray/7-ssh-security-practices-every-system-administrator-should-implement-1b30</link>
      <guid>https://dev.to/vray/7-ssh-security-practices-every-system-administrator-should-implement-1b30</guid>
      <description>&lt;p&gt;SSH Security Best Practices: 7 Ways to Harden Your Linux Server&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;INTRODUCTION&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;SSH (Secure Shell) is the gateway to your Linux servers, making it a prime target for attackers. A compromised SSH service can lead to complete server takeover, data breaches, and unauthorized access to your entire infrastructure.&lt;/p&gt;

&lt;p&gt;After managing Linux servers on Azure, AWS, and GCP for [X years/months], I've developed a security checklist that significantly reduces SSH attack surface. Here are 7 essential practices I implement on every production server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; These steps apply to Ubuntu, Debian, CentOS, RHEL, and most Linux distributions.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Practice #1: Disable Root Login&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Risk:&lt;/strong&gt;&lt;br&gt;
Allowing direct root SSH login is one of the most dangerous misconfigurations. Attackers constantly scan for servers with root access enabled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why This Matters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Attackers know the username (root)&lt;/li&gt;
&lt;li&gt;Only need to guess the password&lt;/li&gt;
&lt;li&gt;Root has unlimited privileges&lt;/li&gt;
&lt;li&gt;No audit trail of individual user actions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to Disable Root Login:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Step 1: Create a sudo user (if you don't have one)&lt;/p&gt;
&lt;h1&gt;
  
  
  Add new user
&lt;/h1&gt;

&lt;p&gt;sudo adduser [your-username]&lt;/p&gt;
&lt;h1&gt;
  
  
  Add to sudo group
&lt;/h1&gt;

&lt;p&gt;sudo usermod -aG sudo [your-username]&lt;/p&gt;
&lt;h1&gt;
  
  
  Test sudo access
&lt;/h1&gt;

&lt;p&gt;su - [your-username]&lt;br&gt;
sudo whoami  # Should output: root&lt;/p&gt;

&lt;p&gt;Step 2: Disable root login in SSH config&lt;/p&gt;

&lt;p&gt;sudo nano /etc/ssh/sshd_config&lt;/p&gt;
&lt;h1&gt;
  
  
  Find and change this line:
&lt;/h1&gt;

&lt;p&gt;PermitRootLogin no&lt;/p&gt;

&lt;p&gt;Step 3: Restart SSH service&lt;/p&gt;
&lt;h1&gt;
  
  
  Ubuntu/Debian:
&lt;/h1&gt;

&lt;p&gt;sudo systemctl restart sshd&lt;/p&gt;
&lt;h1&gt;
  
  
  CentOS/RHEL:
&lt;/h1&gt;

&lt;p&gt;sudo systemctl restart sshd&lt;/p&gt;

&lt;p&gt;Step 4: Test from another terminal (DON'T close your current session!)&lt;/p&gt;
&lt;h1&gt;
  
  
  This should now fail:
&lt;/h1&gt;

&lt;p&gt;ssh root@your-server-ip&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Root SSH login blocked. Users must log in with personal accounts and use sudo.&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;Practice #2: Use SSH Key Authentication (Disable Password Auth)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Risk:&lt;/strong&gt;&lt;br&gt;
Password authentication is vulnerable to brute force attacks. Attackers run automated tools trying thousands of password combinations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why SSH Keys Are Better:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2048-bit (or higher) encryption&lt;/li&gt;
&lt;li&gt;Nearly impossible to brute force&lt;/li&gt;
&lt;li&gt;No password to steal or leak&lt;/li&gt;
&lt;li&gt;Can be easily revoked&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to Set Up SSH Key Authentication:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On your local machine (if you don't have a key):&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Generate SSH key pair
&lt;/h1&gt;

&lt;p&gt;ssh-keygen -t rsa -b 4096 -C "&lt;a href="mailto:your-email@example.com"&gt;your-email@example.com&lt;/a&gt;"&lt;/p&gt;
&lt;h1&gt;
  
  
  Press Enter to save in default location (~/.ssh/id_rsa)
&lt;/h1&gt;
&lt;h1&gt;
  
  
  Set a strong passphrase (optional but recommended)
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Copy your public key to the server:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Method 1: Using ssh-copy-id (easiest)&lt;/p&gt;

&lt;p&gt;ssh-copy-id username@server-ip&lt;/p&gt;

&lt;p&gt;Method 2: Manual copy&lt;/p&gt;
&lt;h1&gt;
  
  
  On local machine:
&lt;/h1&gt;

&lt;p&gt;cat ~/.ssh/id_rsa.pub&lt;/p&gt;
&lt;h1&gt;
  
  
  Copy the output, then on the server:
&lt;/h1&gt;

&lt;p&gt;mkdir -p ~/.ssh&lt;br&gt;
echo "your-public-key-here" &amp;gt;&amp;gt; ~/.ssh/authorized_keys&lt;br&gt;
chmod 700 ~/.ssh&lt;br&gt;
chmod 600 ~/.ssh/authorized_keys&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test key-based login:&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Should work without password:
&lt;/h1&gt;

&lt;p&gt;ssh username@server-ip&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disable password authentication:&lt;/strong&gt;&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;sudo &lt;/span&gt;nano /etc/ssh/sshd_config

&lt;span class="c"&gt;# Change these lines:&lt;/span&gt;
PasswordAuthentication no
PubkeyAuthentication &lt;span class="nb"&gt;yes
&lt;/span&gt;ChallengeResponseAuthentication no

&lt;span class="c"&gt;# Restart SSH:&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart sshd


&lt;span class="k"&gt;**&lt;/span&gt;IMPORTANT:&lt;span class="k"&gt;**&lt;/span&gt; Keep a session open &lt;span class="k"&gt;until &lt;/span&gt;you confirm key-based login works!

&lt;span class="k"&gt;**&lt;/span&gt;Result:&lt;span class="k"&gt;**&lt;/span&gt; Only SSH key authentication allowed. Brute force attacks become ineffective.


&lt;span class="nt"&gt;---&lt;/span&gt;

&lt;span class="c"&gt;## **Practice #3: Change the Default SSH Port**&lt;/span&gt;

&lt;span class="k"&gt;**&lt;/span&gt;The Risk:&lt;span class="k"&gt;**&lt;/span&gt;
Automated bots constantly scan port 22 &lt;span class="o"&gt;(&lt;/span&gt;default SSH port&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; Changing it reduces noise and automated attacks.

&lt;span class="k"&gt;**&lt;/span&gt;Note:&lt;span class="k"&gt;**&lt;/span&gt; This is &lt;span class="s2"&gt;"security through obscurity"&lt;/span&gt; - not a replacement &lt;span class="k"&gt;for &lt;/span&gt;other measures, but adds an extra layer.

&lt;span class="k"&gt;**&lt;/span&gt;How to Change SSH Port:&lt;span class="k"&gt;**&lt;/span&gt;

Step 1: Choose a port number

&lt;span class="c"&gt;# Pick a high port number (1024-65535)&lt;/span&gt;
&lt;span class="c"&gt;# Example: 2222, 2345, 22022&lt;/span&gt;
&lt;span class="c"&gt;# Avoid common ports: 8080, 3000, etc.&lt;/span&gt;


Step 2: Update SSH config

&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/ssh/sshd_config

&lt;span class="c"&gt;# Find and change:&lt;/span&gt;
Port 2222  &lt;span class="c"&gt;# Or your chosen port&lt;/span&gt;


Step 3: Update firewall rules

&lt;span class="k"&gt;**&lt;/span&gt;Ubuntu/Debian &lt;span class="o"&gt;(&lt;/span&gt;UFW&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="k"&gt;**&lt;/span&gt;

&lt;span class="c"&gt;# Allow new port:&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow 2222/tcp

&lt;span class="c"&gt;# Remove old rule (after testing!):&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw delete allow 22/tcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;CentOS/RHEL (firewalld):&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Add new port:
&lt;/h1&gt;

&lt;p&gt;sudo firewall-cmd --permanent --add-port=2222/tcp&lt;br&gt;
sudo firewall-cmd --reload&lt;/p&gt;
&lt;h1&gt;
  
  
  Remove old port (after testing!):
&lt;/h1&gt;

&lt;p&gt;sudo firewall-cmd --permanent --remove-service=ssh&lt;br&gt;
sudo firewall-cmd --reload&lt;/p&gt;

&lt;p&gt;Step 4: Update cloud security groups (if applicable)&lt;/p&gt;

&lt;p&gt;[Fill in specific steps for Azure NSG, AWS Security Groups, or GCP Firewall]&lt;/p&gt;

&lt;p&gt;Azure NSG:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to Network Security Group&lt;/li&gt;
&lt;li&gt;Add inbound rule for port 2222&lt;/li&gt;
&lt;li&gt;After testing, remove port 22 rule&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AWS Security Group:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add inbound rule: TCP, port 2222, your IP&lt;/li&gt;
&lt;li&gt;After testing, remove port 22 rule&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Step 5: Restart SSH and test&lt;/p&gt;

&lt;p&gt;sudo systemctl restart sshd&lt;/p&gt;
&lt;h1&gt;
  
  
  From local machine:
&lt;/h1&gt;

&lt;p&gt;ssh -p 2222 username@server-ip&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update your SSH config for convenience:&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  On local machine (~/.ssh/config):
&lt;/h1&gt;

&lt;p&gt;Host myserver&lt;br&gt;
    HostName server-ip&lt;br&gt;
    Port 2222&lt;br&gt;
    User username&lt;/p&gt;
&lt;h1&gt;
  
  
  Now you can just type:
&lt;/h1&gt;

&lt;p&gt;ssh myserver&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; 90%+ reduction in automated attacks on SSH service.&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;Practice #4: Implement Fail2Ban&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Risk:&lt;/strong&gt;&lt;br&gt;
Even with strong security, some attacks persist. Fail2Ban automatically blocks IPs after failed login attempts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Fail2Ban Does:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monitors authentication logs&lt;/li&gt;
&lt;li&gt;Blocks IPs after X failed attempts&lt;/li&gt;
&lt;li&gt;Automatically unblocks after set time&lt;/li&gt;
&lt;li&gt;Works with firewall rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to Set Up Fail2Ban:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Step 1: Install Fail2Ban&lt;/p&gt;
&lt;h1&gt;
  
  
  Ubuntu/Debian:
&lt;/h1&gt;

&lt;p&gt;sudo apt update&lt;br&gt;
sudo apt install fail2ban -y&lt;/p&gt;
&lt;h1&gt;
  
  
  CentOS/RHEL:
&lt;/h1&gt;

&lt;p&gt;sudo yum install epel-release -y&lt;br&gt;
sudo yum install fail2ban -y&lt;/p&gt;

&lt;p&gt;Step 2: Configure Fail2Ban for SSH&lt;/p&gt;
&lt;h1&gt;
  
  
  Create local config:
&lt;/h1&gt;

&lt;p&gt;sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local&lt;/p&gt;
&lt;h1&gt;
  
  
  Edit configuration:
&lt;/h1&gt;

&lt;p&gt;sudo nano /etc/fail2ban/jail.local&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add/modify these settings:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;[sshd]&lt;br&gt;
enabled = true&lt;br&gt;
port = 2222    # Or your SSH port&lt;br&gt;
filter = sshd&lt;br&gt;
logpath = /var/log/auth.log  # Ubuntu/Debian&lt;/p&gt;
&lt;h1&gt;
  
  
  logpath = /var/log/secure   # CentOS/RHEL
&lt;/h1&gt;

&lt;p&gt;maxretry = 3   # Failed attempts before ban&lt;br&gt;
bantime = 3600  # Ban duration in seconds (1 hour)&lt;br&gt;
findtime = 600  # Time window for failed attempts (10 min)&lt;/p&gt;

&lt;p&gt;Step 3: Start and enable Fail2Ban&lt;/p&gt;

&lt;p&gt;sudo systemctl start fail2ban&lt;br&gt;
sudo systemctl enable fail2ban&lt;/p&gt;

&lt;p&gt;Step 4: Check Fail2Ban status&lt;/p&gt;
&lt;h1&gt;
  
  
  Check service status:
&lt;/h1&gt;

&lt;p&gt;sudo systemctl status fail2ban&lt;/p&gt;
&lt;h1&gt;
  
  
  Check banned IPs:
&lt;/h1&gt;

&lt;p&gt;sudo fail2ban-client status sshd&lt;/p&gt;
&lt;h1&gt;
  
  
  Unban an IP (if needed):
&lt;/h1&gt;

&lt;p&gt;sudo fail2ban-client set sshd unbanip IP_ADDRESS&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Automated protection against brute force attacks. Failed login attempts = automatic IP ban.&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;Practice #5: Use Two-Factor Authentication (2FA)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Risk:&lt;/strong&gt;&lt;br&gt;
Even with SSH keys, a compromised key can grant full access. 2FA adds another layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How 2FA Works:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Something you have (SSH key)&lt;/li&gt;
&lt;li&gt;Something you know (2FA code)&lt;/li&gt;
&lt;li&gt;Both required for access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to Set Up Google Authenticator for SSH:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Step 1: Install Google Authenticator&lt;/p&gt;
&lt;h1&gt;
  
  
  Ubuntu/Debian:
&lt;/h1&gt;

&lt;p&gt;sudo apt install libpam-google-authenticator -y&lt;/p&gt;
&lt;h1&gt;
  
  
  CentOS/RHEL:
&lt;/h1&gt;

&lt;p&gt;sudo yum install google-authenticator -y&lt;/p&gt;

&lt;p&gt;Step 2: Configure Google Authenticator for your user&lt;/p&gt;
&lt;h1&gt;
  
  
  Run as your regular user (not root):
&lt;/h1&gt;

&lt;p&gt;google-authenticator&lt;/p&gt;
&lt;h1&gt;
  
  
  Answer the questions:
&lt;/h1&gt;
&lt;h1&gt;
  
  
  - Do you want authentication tokens to be time-based? YES
&lt;/h1&gt;
&lt;h1&gt;
  
  
  - Scan QR code with Google Authenticator app
&lt;/h1&gt;
&lt;h1&gt;
  
  
  - Save emergency scratch codes in safe place
&lt;/h1&gt;
&lt;h1&gt;
  
  
  - Do you want to disallow multiple uses? YES
&lt;/h1&gt;
&lt;h1&gt;
  
  
  - Increase time window? NO (unless needed)
&lt;/h1&gt;
&lt;h1&gt;
  
  
  - Enable rate-limiting? YES
&lt;/h1&gt;

&lt;p&gt;Step 3: Configure SSH to use 2FA&lt;/p&gt;
&lt;h1&gt;
  
  
  Edit PAM configuration:
&lt;/h1&gt;

&lt;p&gt;sudo nano /etc/pam.d/sshd&lt;/p&gt;
&lt;h1&gt;
  
  
  Add this line at the top:
&lt;/h1&gt;

&lt;p&gt;auth required pam_google_authenticator.so&lt;/p&gt;

&lt;p&gt;Step 4: Update SSH daemon config&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;sudo &lt;/span&gt;nano /etc/ssh/sshd_config

&lt;span class="c"&gt;# Add or modify these lines:&lt;/span&gt;
ChallengeResponseAuthentication &lt;span class="nb"&gt;yes
&lt;/span&gt;AuthenticationMethods publickey,keyboard-interactive


Step 5: Restart SSH

&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart sshd


&lt;span class="k"&gt;**&lt;/span&gt;Testing &lt;span class="o"&gt;(&lt;/span&gt;from another terminal!&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="k"&gt;**&lt;/span&gt;

ssh username@server-ip
&lt;span class="c"&gt;# You'll need:&lt;/span&gt;
&lt;span class="c"&gt;# 1. Your SSH key&lt;/span&gt;
&lt;span class="c"&gt;# 2. Verification code from Google Authenticator app&lt;/span&gt;


&lt;span class="k"&gt;**&lt;/span&gt;Result:&lt;span class="k"&gt;**&lt;/span&gt; Even &lt;span class="k"&gt;if &lt;/span&gt;SSH key is compromised, attacker needs your phone to log &lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="k"&gt;**&lt;/span&gt;Optional:&lt;span class="k"&gt;**&lt;/span&gt; Exempt trusted IPs from 2FA

&lt;span class="c"&gt;# Edit /etc/pam.d/sshd:&lt;/span&gt;
auth &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;success&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;done &lt;/span&gt;&lt;span class="nv"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ignore] pam_succeed_if.so user ingroup no2fa
auth required pam_google_authenticator.so

&lt;span class="c"&gt;# Create group:&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;groupadd no2fa
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; no2fa username


&lt;span class="nt"&gt;---&lt;/span&gt;

&lt;span class="c"&gt;## **Practice #6: Restrict SSH Access by IP**&lt;/span&gt;

&lt;span class="k"&gt;**&lt;/span&gt;The Risk:&lt;span class="k"&gt;**&lt;/span&gt;
If you always connect from known IPs &lt;span class="o"&gt;(&lt;/span&gt;office, home, VPN&lt;span class="o"&gt;)&lt;/span&gt;, why allow connections from anywhere?

&lt;span class="k"&gt;**&lt;/span&gt;How to Whitelist IP Addresses:&lt;span class="k"&gt;**&lt;/span&gt;

&lt;span class="k"&gt;**&lt;/span&gt;Method 1: Using sshd_config &lt;span class="o"&gt;(&lt;/span&gt;Simple&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="k"&gt;**&lt;/span&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/ssh/sshd_config

&lt;span class="c"&gt;# Add at the end:&lt;/span&gt;
AllowUsers username@192.168.1.100
AllowUsers username@10.0.0.0/8
AllowUsers username@203.0.113.0/24

&lt;span class="c"&gt;# Or allow specific IPs for all users:&lt;/span&gt;
&lt;span class="c"&gt;# Match Address 192.168.1.0/24,10.0.0.0/8&lt;/span&gt;
&lt;span class="c"&gt;#     AllowUsers *&lt;/span&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart sshd


&lt;span class="k"&gt;**&lt;/span&gt;Method 2: Using Firewall &lt;span class="o"&gt;(&lt;/span&gt;More flexible&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="k"&gt;**&lt;/span&gt;

&lt;span class="k"&gt;**&lt;/span&gt;UFW &lt;span class="o"&gt;(&lt;/span&gt;Ubuntu/Debian&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="k"&gt;**&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
bash&lt;/p&gt;

&lt;h1&gt;
  
  
  Remove general SSH allow:
&lt;/h1&gt;

&lt;p&gt;sudo ufw delete allow 2222/tcp&lt;/p&gt;

&lt;h1&gt;
  
  
  Add specific IPs:
&lt;/h1&gt;

&lt;p&gt;sudo ufw allow from 192.168.1.100 to any port 2222 proto tcp&lt;br&gt;
sudo ufw allow from 203.0.113.0/24 to any port 2222 proto tcp&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;firewalld (CentOS/RHEL):&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Create rich rules:
&lt;/h1&gt;

&lt;p&gt;sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.100" port port="2222" protocol="tcp" accept'&lt;br&gt;
sudo firewall-cmd --reload&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Method 3: Cloud Security Groups (Best for cloud)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;[Fill in your specific cloud platform steps]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Network Security Group → Inbound rules&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Source: IP Addresses&lt;/li&gt;
&lt;li&gt;Source IP: Your.IP.Address/32&lt;/li&gt;
&lt;li&gt;Destination port: 2222&lt;/li&gt;
&lt;li&gt;Action: Allow&lt;/li&gt;
&lt;li&gt;Priority: 100&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;AWS Security Groups:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Edit inbound rules&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Type: Custom TCP&lt;/li&gt;
&lt;li&gt;Port: 2222&lt;/li&gt;
&lt;li&gt;Source: My IP (auto-fills your current IP)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; SSH access only from your trusted IPs. All other connections blocked.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Practice #7: Enable and Monitor SSH Logs&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Risk:&lt;/strong&gt;&lt;br&gt;
Without logging, you won't know if someone is attacking or has gained access to your server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Logging Matters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detect attack patterns&lt;/li&gt;
&lt;li&gt;Forensic analysis after incidents&lt;/li&gt;
&lt;li&gt;Compliance requirements&lt;/li&gt;
&lt;li&gt;Early warning of compromises&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to Set Up Comprehensive SSH Logging:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Step 1: Ensure logging is enabled&lt;/p&gt;

&lt;p&gt;sudo nano /etc/ssh/sshd_config&lt;/p&gt;

&lt;h1&gt;
  
  
  Verify these settings:
&lt;/h1&gt;

&lt;p&gt;SyslogFacility AUTH&lt;br&gt;
LogLevel VERBOSE  # Or INFO for less detail&lt;/p&gt;

&lt;p&gt;sudo systemctl restart sshd&lt;/p&gt;

&lt;p&gt;Step 2: View SSH logs&lt;/p&gt;

&lt;h1&gt;
  
  
  Ubuntu/Debian:
&lt;/h1&gt;

&lt;p&gt;sudo tail -f /var/log/auth.log&lt;/p&gt;

&lt;h1&gt;
  
  
  CentOS/RHEL:
&lt;/h1&gt;

&lt;p&gt;sudo tail -f /var/log/secure&lt;/p&gt;

&lt;h1&gt;
  
  
  Using journalctl:
&lt;/h1&gt;

&lt;p&gt;sudo journalctl -u sshd -f&lt;/p&gt;

&lt;p&gt;Step 3: Set up log monitoring script&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
bash
# Create monitoring script:
sudo nano /usr/local/bin/ssh-monitor.sh


**Add this script:**

#!/bin/bash
# Monitor SSH failed login attempts

LOGFILE="/var/log/auth.log"  # Change for CentOS: /var/log/secure
THRESHOLD=5
EMAIL="your-email@example.com"

# Count failed attempts in last hour
FAILED=$(grep "Failed password" $LOGFILE | grep "$(date '+%b %e %H')" | wc -l)

if [ $FAILED -gt $THRESHOLD ]; then
    echo "WARNING: $FAILED failed SSH attempts in the last hour" | mail -s "SSH Alert" $EMAIL
fi


Make executable and add to cron:

sudo chmod +x /usr/local/bin/ssh-monitor.sh

# Run every hour:
sudo crontab -e
# Add:
0 * * * * /usr/local/bin/ssh-monitor.sh


Step 4: Useful commands for log analysis

# Show all failed login attempts:
sudo grep "Failed password" /var/log/auth.log

# Show successful logins:
sudo grep "Accepted publickey" /var/log/auth.log

# Count attacks by IP:
sudo grep "Failed password" /var/log/auth.log | awk '{print $(NF-3)}' | sort | uniq -c | sort -nr

# Show last 10 successful SSH logins:
sudo last -10

# Show current SSH connections:
who
# or
w


**Result:** Full visibility into SSH access attempts, successful logins, and attack patterns.


---

## **BONUS: Quick Security Audit Checklist**

Run these commands to verify your SSH security:

# Check SSH config:
sudo sshd -T | grep -E 'permitrootlogin|passwordauthentication|port|challengeresponse'

# Verify SSH is running:
sudo systemctl status sshd

# Check who's currently connected:
who

# View recent SSH activity:
sudo last | head -20

# Check for suspicious failed attempts:
sudo grep "Failed password" /var/log/auth.log | tail -50

# Verify Fail2Ban is active:
sudo fail2ban-client status sshd


---

## **THE RESULTS**

After implementing all 7 practices:

✅ Zero successful brute force attacks (blocked by Fail2Ban and key auth)
✅ 95% reduction in attack attempts (changed port + IP whitelisting)
✅ Complete audit trail of all access (comprehensive logging)
✅ Additional 2FA layer prevents compromised key exploits
✅ Peace of mind that servers are hardened against SSH attacks

**Time investment:** 2-3 hours to set up
**Security improvement:** Significant reduction in attack surface
**Maintenance:** Minimal (just monitor logs periodically)


---

## **KEY TAKEAWAYS**

- Never allow root login via SSH - use sudo instead
- SSH keys are far superior to password authentication
- Changing the default port significantly reduces automated attacks
- Fail2Ban provides automated defense against brute force
- 2FA adds critical protection even if SSH keys are compromised
- IP whitelisting is the most effective restriction if feasible
- Logging is essential - you can't protect what you can't see

Implement these seven practices in order - each builds on the previous one. Even implementing just the first three will dramatically improve your SSH security.



## **CONCLUSION**

SSH is the primary entry point to your Linux servers, making it the most critical service to secure. These seven practices form a defense-in-depth strategy that protects against the vast majority of SSH attacks.

Start with practices #1 and #2 (disable root login and use SSH keys) - these provide the biggest security improvement with minimal effort. Then gradually implement the remaining practices based on your risk tolerance and requirements.

Have you implemented SSH hardening on your servers? What additional practices do you use? Share your experiences in the comments below!


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>linux</category>
      <category>cybersecurity</category>
      <category>systemadministration</category>
      <category>devops</category>
    </item>
    <item>
      <title>How I Secured User File Access Using ACL and Chroot Jail: A Step-by-Step Guide</title>
      <dc:creator>V.Ray</dc:creator>
      <pubDate>Tue, 14 Oct 2025 14:30:00 +0000</pubDate>
      <link>https://dev.to/vray/how-i-secured-user-file-access-using-acl-and-chroot-jail-a-step-by-step-guide-4g1m</link>
      <guid>https://dev.to/vray/how-i-secured-user-file-access-using-acl-and-chroot-jail-a-step-by-step-guide-4g1m</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In a recent project, I faced a security challenge: multiple users needed access to specific files and folders on Linux servers, but couldn't have full system access. Some users were accidentally accessing files outside their designated areas, and I needed a secure, scalable solution. I dont want user can navigate to other filesystem or folder.&lt;/p&gt;

&lt;p&gt;After evaluating several approaches, I implemented a combination of Access Control Lists (ACL) and chroot jail that solved the problem completely. Here's exactly how I did it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I was managing a Redhat Linux version 8 server where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple users needed access to different directories&lt;/li&gt;
&lt;li&gt;Each user should only see and access their specific folders&lt;/li&gt;
&lt;li&gt;Some users were navigating to parent directories they shouldn't access&lt;/li&gt;
&lt;li&gt;Traditional Linux permissions (chmod/chown) weren't granular enough&lt;/li&gt;
&lt;li&gt;The solution needed to be easy to manage as the user base grew&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What I Tried First:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Basic Linux permissions (chmod 750)&lt;/strong&gt; - Too restrictive; couldn't give multiple users different access levels&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linux groups&lt;/strong&gt; - Became messy with users needing different combinations of access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate user accounts per folder&lt;/strong&gt; - Not scalable and hard to maintain&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these solved the core problem: users could still navigate outside their designated folders.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Solution
&lt;/h2&gt;

&lt;p&gt;I implemented a two-layer approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;ACL (Access Control Lists)&lt;/strong&gt; - For granular file permissions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chroot Jail&lt;/strong&gt; - To restrict users to specific directories&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's the step-by-step process:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Set Up the Directory Structure
&lt;/h3&gt;

&lt;p&gt;I organized the folders for easy ACL management:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/home/
  ├── userdata/
  │   ├── user1_folder/
  │   ├── user2_folder/
  │   └── shared_folder/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Created with:&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;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /home/userdata/&lt;span class="o"&gt;{&lt;/span&gt;user1_folder,user2_folder,shared_folder&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Implement ACL for Fine-Grained Permissions
&lt;/h3&gt;

&lt;p&gt;Instead of traditional chmod, I used ACL for specific user permissions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify ACL is enabled:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mount | &lt;span class="nb"&gt;grep &lt;/span&gt;acl


&lt;span class="k"&gt;**&lt;/span&gt;Set specific user permissions:&lt;span class="k"&gt;**&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
bash&lt;/p&gt;
&lt;h1&gt;
  
  
  Give user1 read/write access
&lt;/h1&gt;

&lt;p&gt;sudo setfacl -m u:user1:rwx /home/userdata/user1_folder&lt;/p&gt;
&lt;h1&gt;
  
  
  Give user2 read-only access to shared folder
&lt;/h1&gt;

&lt;p&gt;sudo setfacl -m u:user2:r-x /home/userdata/shared_folder&lt;/p&gt;
&lt;h1&gt;
  
  
  View current ACL settings
&lt;/h1&gt;

&lt;p&gt;getfacl /home/userdata/user1_folder&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
**The advantage:** I could give different users different permission levels on the same folder - impossible with basic chmod.

### Step 3: Implement Chroot Jail

ACL controlled permissions, but users could still navigate to parent directories. Chroot jail restricted each user to their designated folder.

**Modified SSH config for SFTP users:**
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
bash&lt;br&gt;
sudo nano /etc/ssh/sshd_config&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
**Added these lines:**
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Match User user1&lt;br&gt;
    ChrootDirectory /home/userdata/user1_folder&lt;br&gt;
    ForceCommand internal-sftp&lt;br&gt;
    AllowTcpForwarding no&lt;br&gt;
    X11Forwarding no&lt;/p&gt;

&lt;p&gt;Match User user2&lt;br&gt;
    ChrootDirectory /home/userdata/user2_folder&lt;br&gt;
    ForceCommand internal-sftp&lt;br&gt;
    AllowTcpForwarding no&lt;br&gt;
    X11Forwarding no&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
**Important:** Chroot directories must be owned by root:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
bash&lt;br&gt;
sudo chown root:root /home/userdata/user1_folder&lt;br&gt;
sudo chmod 755 /home/userdata/user1_folder&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
**Create writable subdirectory:**
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
bash&lt;br&gt;
sudo mkdir /home/userdata/user1_folder/files&lt;br&gt;
sudo chown user1:user1 /home/userdata/user1_folder/files&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
### Step 4: Restart SSH and Test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
bash&lt;br&gt;
sudo systemctl restart sshd&lt;/p&gt;

&lt;h1&gt;
  
  
  Test connection
&lt;/h1&gt;

&lt;p&gt;sftp user1@server-ip&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


---

## The Results

After implementing this solution:

✅ All users have secure, isolated access to their designated folders  
✅ Zero unauthorized file access incidents since implementation  
✅ Easy to add new users - just copy the Match User block  
✅ Reduced administrative overhead for permission management  
✅ System has been running smoothly for several months  

The two-layer approach (ACL + chroot) provided both flexibility and security.

---

## Key Takeaways

- ACL provides more flexibility than chmod for multi-user environments
- Chroot jail is essential for SFTP/SSH access - ACL alone isn't enough
- Always ensure chroot directories are owned by root (755 permissions)
- Test with a non-privileged account before deploying
- Keep SSH logs enabled to monitor access patterns
- Document ACL rules - they're not visible in `ls -la` output

---

## Conclusion

Combining ACL and chroot jail solved the file access security challenge effectively. If you're managing Linux servers with multiple users needing restricted access, this approach will save hours of manual permission management.

Have you implemented chroot jail in your environment? What challenges did you face? Drop your questions in the comments!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>ubuntu</category>
      <category>security</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
