{"id":2251,"date":"2026-06-11T11:50:47","date_gmt":"2026-06-11T04:50:47","guid":{"rendered":"https:\/\/tutorial.emka.web.id\/?p=2251"},"modified":"2026-06-11T11:50:47","modified_gmt":"2026-06-11T04:50:47","slug":"how-to-ssh-hardening-2026","status":"publish","type":"post","link":"https:\/\/emka.web.id\/en\/2026\/06\/how-to-ssh-hardening-2026.html","title":{"rendered":"How to SSH Hardening 2026"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Many times when we install Linux, the default SSH config is very weak and have many security holes. If you run a CIS L1 Server scan on your fresh machine, it will show seven big fails on your SSH configuration. These bad fails is about missing warning banner, no limit for login tries, no limit for login grace time, too many sessions and startups allowed, not enough log detail, and no user restrictions. To fix all these problems, we can write only one simple override file and put it inside the directory <code>\/etc\/ssh\/sshd_config.d\/99-cfg-hardening.conf<\/code> so when you do package updates, the system does not delete your changes.<\/p>\n\n\n\n<!--more-->\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 1: Open the override config file<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">First, we must to write a new configuration file in the safe directory. We use a number like 99 in front of the name because SSH reads files in alphabetical order, so 99 will load last and overwrite any old settings that was there before. Open your terminal and type this command to create the file with admin permission:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>sudo vi \/etc\/ssh\/sshd_config.d\/99-cfg-hardening.conf<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you do not like vi editor because it is too hard, you can use nano editor instead. Now you must copy and paste all the lines below into this file:<\/p>\n\n\n\n<pre class=\"wp-block-syntaxhighlighter-code\">PermitRootLogin no\nPasswordAuthentication no\nPermitEmptyPasswords no\nKbdInteractiveAuthentication no\nUsePAM yes\n\nX11Forwarding no\nAllowAgentForwarding no\nAllowTcpForwarding no\n\nMaxAuthTries 3\nMaxSessions 4\nMaxStartups 10:30:60\nLoginGraceTime 30\nClientAliveInterval 300\nClientAliveCountMax 2\n\nLogLevel VERBOSE\nBanner \/etc\/issue.net\nAuthorizedKeysFile .ssh\/authorized_keys\nProtocol 2\n\nKexAlgorithms [email protected],curve25519-sha256,[email protected],diffie-hellman-group16-sha512,diffie-hellman-group18-sha512\nCiphers [email protected],[email protected],[email protected],aes256-ctr,aes192-ctr,aes128-ctr\nMACs [email protected],[email protected],[email protected]\n\nAllowGroups ssh-users<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 2: Understanding what all these config lines mean<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I will explain why we need every line in this configuration. If you do not know what they do, it is bad because you might break your server.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The first part is about how users login to the server. We write <code>PermitRootLogin no<\/code> because root is the most powerful user. If hackers try to guess your root password, they can destroy everything. So we block root from login directly. Next, <code>PasswordAuthentication no<\/code> is very important because we do not want passwords. Passwords is very easy to guess by automatic botnets. We only want to use SSH keys because they are super strong. <code>PermitEmptyPasswords no<\/code> makes sure nobody can login if they do not have a password or key. <code>KbdInteractiveAuthentication no<\/code> turns off interactive typing for password prompts. We write <code>UsePAM yes<\/code> because it lets the system use Pluggable Authentication Modules for other security checks.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The second part is about forwarding things. We write <code>X11Forwarding no<\/code> because we do not need to show graphical windows from the server on our home computer. Attackers can use X11 to steal information. We also do <code>AllowAgentForwarding no<\/code> and <code>AllowTcpForwarding no<\/code> because bad guys can use these options to tunnel into your other local networks and hack more computers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The third part is about limits for connection and time. We put <code>MaxAuthTries 3<\/code> so if someone tries to guess your key or password and fails three times, the server kicks them out. This stops brute force attacks. <code>MaxSessions 4<\/code> means one connection can only have four sessions. We use <code>MaxStartups 10:30:60<\/code> to prevent denial of service attacks. This means if 10 people try to login at the same time, the server will start dropping 30 percent of new connections, and if 60 people try, it blocks all new connection tries. <code>LoginGraceTime 30<\/code> gives you only 30 seconds to finish your login, if you take too long, it disconnects. <code>ClientAliveInterval 300<\/code> and <code>ClientAliveCountMax 2<\/code> will check if your connection is still active every 300 seconds. If you do not do anything for a long time, it will close your connection automatically so nobody can steal your open terminal.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The fourth part is about logs and files. We use <code>LogLevel VERBOSE<\/code> because the default log level is too quiet. Verbose level will log the fingerprint of the SSH key that was used for login, which is very useful for audit. We set <code>Banner \/etc\/issue.net<\/code> to show a warning text before login. <code>AuthorizedKeysFile .ssh\/authorized_keys<\/code> tells the system where to look for your public keys. <code>Protocol 2<\/code> is because protocol 1 is very old and has many security bugs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The fifth part is about crypto algorithms. We write <code>KexAlgorithms<\/code>, <code>Ciphers<\/code>, and <code>MACs<\/code> with very specific modern algorithms like curve25519, ChaCha20-Poly1305, and AES-GCM. We do not want old ciphers like 3DES or SHA1 because they are weak and computers can break them now.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The last part is <code>AllowGroups ssh-users<\/code>. This is a very strong security rule. It means only people who belong to the group called <code>ssh-users<\/code> can connect to this server. Even if someone has a correct SSH key, if they are not in this group, the server says no.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 3: Create the security group and add your user account<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you save the config file now and restart SSH, you will lock yourself out forever because the group <code>ssh-users<\/code> does not exist yet and you are not in it. We must create this group now. Run this command:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>sudo groupadd -f ssh-users<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This creates the group. Now we must add your current user account to this new group so you do not lose access. Run this command:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>sudo usermod -aG ssh-users $USER<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Please make sure you type this command correctly. The <code>$USER<\/code> variable will automatically use your current username. If you want to add another user later, you can replace <code>$USER<\/code> with their real username.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 4: Create the login warning banner file<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In our configuration, we told SSH to use a banner file at <code>\/etc\/issue.net<\/code>. If this file is empty or does not exist, it might look bad or fail security scans. We can write some scary warning text inside it to tell hackers they are not welcome. Run this command:<\/p>\n\n\n\n<pre class=\"wp-block-syntaxhighlighter-code\">sudo tee \/etc\/issue.net &gt; \/dev\/null &lt;&lt;'EOF'\nWARNING: Authorized users only. All activity is logged.\nDisconnect now if you are not an authorized user.\nEOF<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This command will write the warning message into the correct file. If someone tries to connect to your server now, they will see this text before they can do anything.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 5: Test the configuration and restart SSH service<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Before we reload the SSH service, we must check if we made any typing mistakes in our config file. If there is a mistake, SSH service will crash and you cannot connect anymore. Run this test command:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>sudo sshd -t<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If this command returns nothing and does not show any error message, it means your configuration is safe and has no syntax errors. Now you can reload the SSH service to apply all the changes:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>sudo systemctl reload sshd<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It is very important that you do not close your current terminal window yet. Keep this session open! Open a completely new terminal window on your computer and try to login to the server to see if it works. If the new login works, then you did everything correctly. If it does not work, you still have the old terminal open to fix the configuration mistakes or remove the bad lines.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 6: Bonus configuration for trusted networks<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you have a fixed network range like an office network or a home VPN, you can add a special rule at the end of your file to make things easier for your trusted IP addresses. You can use the <code>Match<\/code> block like this:<\/p>\n\n\n\n<pre class=\"wp-block-syntaxhighlighter-code\">Match Address 10.0.0.0\/8\n    PasswordAuthentication yes<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This block must be at the very bottom of the file. It means if you connect from an IP address that starts with 10., the server will let you use password authentication instead of only keys. You can change the network address to your own IP range if you want to use this option.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Conclusion<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We have successfully secured the SSH server by fixing all seven findings from the CIS L1 Server scan. We put all changes into one single file in the <code>\/etc\/ssh\/sshd_config.d\/<\/code> directory so it does not get overwritten by system updates. We limited login attempts, blocked root, turned off dangerous forwardings, used strong modern cryptography, and restricted access to a specific user group.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Many times when we install Linux, the default SSH config is very weak and have many security holes. If you run a CIS L1&#8230;<\/p>\n","protected":false},"author":27,"featured_media":2252,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[569],"tags":[4440,4441,4442,4443,4444,4445,4446,4447,4448,4449],"class_list":["post-2251","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-computer-security","tag-cis-benchmark-ssh-hardening-linux","tag-configure-ssh-keys-only-login","tag-disable-password-authentication-ssh-ubuntu","tag-how-to-change-ssh-log-level-verbose","tag-how-to-secure-ssh-server-linux","tag-limit-ssh-connection-attempts-linux","tag-restrict-ssh-access-by-ip-address","tag-setting-up-ssh-warning-banner-issue-net","tag-ssh-allowgroups-configuration-step-by-step","tag-ssh-hardening-best-practices-guide"],"_links":{"self":[{"href":"https:\/\/emka.web.id\/en\/wp-json\/wp\/v2\/posts\/2251","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/emka.web.id\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/emka.web.id\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/emka.web.id\/en\/wp-json\/wp\/v2\/users\/27"}],"replies":[{"embeddable":true,"href":"https:\/\/emka.web.id\/en\/wp-json\/wp\/v2\/comments?post=2251"}],"version-history":[{"count":0,"href":"https:\/\/emka.web.id\/en\/wp-json\/wp\/v2\/posts\/2251\/revisions"}],"wp:attachment":[{"href":"https:\/\/emka.web.id\/en\/wp-json\/wp\/v2\/media?parent=2251"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/emka.web.id\/en\/wp-json\/wp\/v2\/categories?post=2251"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/emka.web.id\/en\/wp-json\/wp\/v2\/tags?post=2251"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}