<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[VPS Host Review]]></title><description><![CDATA[VPS Host Review]]></description><link>https://vpshostreview.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>VPS Host Review</title><link>https://vpshostreview.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Mon, 12 Oct 2026 12:03:24 GMT</lastBuildDate><atom:link href="https://vpshostreview.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to Harden SSH Access on a VPS]]></title><description><![CDATA[Most VPS providers hand you a root password and an open port 22. That combination survives about as long as it takes an automated scanner to find your IP, often just minutes on the public internet. SS]]></description><link>https://vpshostreview.hashnode.dev/how-to-harden-ssh-access-on-a-vps</link><guid isPermaLink="true">https://vpshostreview.hashnode.dev/how-to-harden-ssh-access-on-a-vps</guid><category><![CDATA[ssh]]></category><category><![CDATA[Linux]]></category><category><![CDATA[Security]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Support]]></dc:creator><pubDate>Tue, 15 Sep 2026 21:15:53 GMT</pubDate><content:encoded><![CDATA[<p>Most VPS providers hand you a root password and an open port 22. That combination survives about as long as it takes an automated scanner to find your IP, often just minutes on the public internet. SSH is the front door to your server, and the default configuration leaves it wide open.</p>
<p>This article covers every practical SSH hardening step: key-based auth, disabling passwords, config lockdown, custom ports, TOTP two-factor, and jump host setups. If you have already run through the <a href="https://vpshostreview.com/articles/vps-security-checklist">VPS security checklist</a> or the <a href="https://vpshostreview.com/articles/how-to-set-up-a-vps-for-the-first-time">initial VPS setup guide</a>, you have the foundation. This is where you finish the job.</p>
<h2>Set Up Key-Based Authentication</h2>
<p>Key-based login replaces the guessable password with a cryptographic key pair. The private key stays on your workstation; the public key goes on the server. Without the private key file, there is nothing to brute-force.</p>
<p>Generate a key pair on your local machine:</p>
<pre><code class="language-bash">ssh-keygen -t ed25519 -C "yourname@yourmachine"
</code></pre>
<p>Ed25519 is the recommended algorithm. It produces shorter keys than RSA, uses a modern elliptic curve, and performs well on every OS released in the last decade. If you need compatibility with very old systems (OpenSSH before 6.5), fall back to RSA 4096:</p>
<pre><code class="language-bash">ssh-keygen -t rsa -b 4096 -C "yourname@yourmachine"
</code></pre>
<p>Copy the public key to the server:</p>
<pre><code class="language-bash">ssh-copy-id -i ~/.ssh/id_ed25519.pub youruser@YOUR_SERVER_IP
</code></pre>
<p>If <code>ssh-copy-id</code> is not available (Windows without WSL, for example), do it manually:</p>
<pre><code class="language-bash">cat ~/.ssh/id_ed25519.pub | ssh youruser@YOUR_SERVER_IP "mkdir -p ~/.ssh &amp;&amp; chmod 700 ~/.ssh &amp;&amp; cat &gt;&gt; ~/.ssh/authorized_keys &amp;&amp; chmod 600 ~/.ssh/authorized_keys"
</code></pre>
<p>Test the key login in a new terminal session before making any other changes:</p>
<pre><code class="language-bash">ssh youruser@YOUR_SERVER_IP
</code></pre>
<p>If that drops you into a shell without asking for a password, key auth is working. Do not close this session yet.</p>
<p><strong>Gotcha:</strong> if you skip the <code>chmod 600</code> on <code>authorized_keys</code> or <code>chmod 700</code> on <code>.ssh</code>, OpenSSH silently refuses to use the key and falls back to password auth. You will not see an error. The login just quietly asks for a password, and you will assume the key did not work. The permissions check is strict by design: the server will not trust a key file that other users on the system can read.</p>
<h2>Disable Password Authentication</h2>
<p>Once key login works, turn off passwords entirely. Open the SSH daemon config:</p>
<pre><code class="language-bash">sudo nano /etc/ssh/sshd_config
</code></pre>
<p>Find and set these directives (or add them if they appear nowhere in the file):</p>
<pre><code>PasswordAuthentication no
KbdInteractiveAuthentication no
</code></pre>
<p>On older distributions, the second directive may be called <code>ChallengeResponseAuthentication</code>. Set whichever exists to <code>no</code>.</p>
<p>Restart the SSH daemon:</p>
<pre><code class="language-bash">sudo systemctl restart sshd
</code></pre>
<p>Open a second terminal and test logging in with a password to confirm it is rejected:</p>
<pre><code class="language-bash">ssh -o PubkeyAuthentication=no youruser@YOUR_SERVER_IP
</code></pre>
<p>That command should fail immediately with "Permission denied (publickey)." If it still accepts a password, check that the directives are not overridden later in the file or inside a <code>Match</code> block.</p>
<p><strong>Gotcha:</strong> some distributions ship a separate <code>/etc/ssh/sshd_config.d/</code> directory with drop-in files that can silently re-enable password auth. Run <code>grep -r "PasswordAuthentication" /etc/ssh/sshd_config.d/</code> to make sure nothing overrides your setting. On Ubuntu 22.04 and later, look specifically for <code>50-cloud-init.conf</code>, which often contains <code>PasswordAuthentication yes</code>.</p>
<h2>Restrict Login to Named Users</h2>
<p>By default, every system account can attempt SSH login. Limit it to the accounts that actually need remote access:</p>
<pre><code>AllowUsers youruser
</code></pre>
<p>Add that directive to <code>/etc/ssh/sshd_config</code>. Only the listed usernames will be permitted to log in. Everyone else gets rejected before authentication even starts. If you have multiple admins, list them space-separated:</p>
<pre><code>AllowUsers youruser anotheradmin
</code></pre>
<p>Restart <code>sshd</code> after any config change:</p>
<pre><code class="language-bash">sudo systemctl restart sshd
</code></pre>
<p>Verify by attempting to log in as a non-listed user from another terminal. The connection should be rejected immediately with "Permission denied."</p>
<p>You should also disable root login over SSH. Root access via password is the single most common exploit path on a new VPS:</p>
<pre><code>PermitRootLogin no
</code></pre>
<p>If you need root commands, <code>sudo</code> from your regular user account after logging in.</p>
<h2>Change the Default SSH Port</h2>
<p>Moving SSH off port 22 does not stop a targeted attacker, but it eliminates the bulk of automated scanning traffic. Scanners hit port 22 on every IP range; a non-standard port avoids that noise entirely and keeps your auth logs clean.</p>
<p>In <code>/etc/ssh/sshd_config</code>:</p>
<pre><code>Port 2222
</code></pre>
<p>Pick a port between 1024 and 65535 that is not used by another service. Before restarting sshd, open the new port in your firewall:</p>
<pre><code class="language-bash">sudo ufw allow 2222/tcp
sudo ufw deny 22/tcp
</code></pre>
<p>See the <a href="https://vpshostreview.com/articles/how-to-set-up-firewall-vps-ufw">UFW firewall guide</a> for the full setup if you have not configured one yet.</p>
<p>Restart <code>sshd</code>:</p>
<pre><code class="language-bash">sudo systemctl restart sshd
</code></pre>
<p>Test from a new terminal:</p>
<pre><code class="language-bash">ssh -p 2222 youruser@YOUR_SERVER_IP
</code></pre>
<p><strong>Gotcha:</strong> if you restart sshd before opening the new port in the firewall, you lock yourself out. Always add the firewall rule first, test in a new terminal, and only then remove the rule for port 22. Keep your existing session open as a lifeline until the new port is confirmed working. If you do lock yourself out, most VPS providers offer a web console or KVM/VNC rescue session through their control panel.</p>
<h2>Harden the sshd_config</h2>
<p>Beyond the basics above, several directives tighten the SSH daemon against common attack vectors. Add these to <code>/etc/ssh/sshd_config</code>:</p>
<pre><code>MaxAuthTries 3
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
X11Forwarding no
AllowAgentForwarding no
</code></pre>
<p>What each one does:</p>
<table>
<thead>
<tr>
<th>Directive</th>
<th>What it controls</th>
</tr>
</thead>
<tbody><tr>
<td><code>MaxAuthTries 3</code></td>
<td>Disconnects after 3 failed auth attempts per connection. Slows brute-force scripts.</td>
</tr>
<tr>
<td><code>LoginGraceTime 30</code></td>
<td>Gives 30 seconds to complete login. Drops connections that sit idle at the prompt.</td>
</tr>
<tr>
<td><code>ClientAliveInterval 300</code></td>
<td>Sends a keepalive probe every 5 minutes.</td>
</tr>
<tr>
<td><code>ClientAliveCountMax 2</code></td>
<td>Disconnects after 2 missed keepalive responses (10 minutes of dead connection).</td>
</tr>
<tr>
<td><code>X11Forwarding no</code></td>
<td>Disables X11 forwarding. Unless you run GUI apps through SSH, turn this off.</td>
</tr>
<tr>
<td><code>AllowAgentForwarding no</code></td>
<td>Prevents forwarding your local SSH agent to the server. Limits lateral movement if the server is compromised.</td>
</tr>
</tbody></table>
<p>For servers that do not need SFTP subsystem access, disable it by commenting out or removing the <code>Subsystem sftp</code> line. Most VPS administration does not require SFTP when <code>scp</code> or <code>rsync</code> work over the SSH connection directly.</p>
<p>You can also restrict which ciphers and key exchange algorithms the server accepts. Adding explicit <code>Ciphers</code> and <code>KexAlgorithms</code> directives limits the daemon to modern, audited algorithms and rejects legacy options like <code>diffie-hellman-group1-sha1</code> or <code>3des-cbc</code>. The safe defaults shipped with current OpenSSH versions are reasonable, but if your compliance environment or threat model demands a narrow list, the <code>ssh-audit</code> tool (available in most package managers) will scan your server and flag weak algorithms.</p>
<p>After all changes, validate the config before restarting:</p>
<pre><code class="language-bash">sudo sshd -t
</code></pre>
<p>That command checks syntax without restarting. If it prints nothing, the config is valid. If errors appear, fix them before running <code>systemctl restart sshd</code>.</p>
<p>For brute-force protection beyond <code>MaxAuthTries</code>, pair these settings with fail2ban. The <a href="https://vpshostreview.com/articles/how-to-set-up-fail2ban-on-a-vps">fail2ban setup guide</a> walks through the SSH jail configuration in detail.</p>
<h2>Add Two-Factor Authentication (TOTP)</h2>
<p>Key-based auth is already strong, but two-factor adds a second layer: something you have (the key) plus something you know (a time-based one-time password from an authenticator app). If an attacker somehow obtains your private key, they still cannot log in without the TOTP code.</p>
<p>Install the Google Authenticator PAM module:</p>
<pre><code class="language-bash">sudo apt install libpam-google-authenticator
</code></pre>
<p>On RHEL/Rocky/Alma:</p>
<pre><code class="language-bash">sudo dnf install google-authenticator
</code></pre>
<p>Run the setup as your regular user (not root):</p>
<pre><code class="language-bash">google-authenticator
</code></pre>
<p>Answer the prompts:</p>
<ol>
<li><strong>Time-based tokens?</strong> Yes.</li>
<li><strong>Update <code>.google_authenticator</code> file?</strong> Yes.</li>
<li><strong>Disallow multiple uses of the same token?</strong> Yes.</li>
<li><strong>Increase the time window?</strong> No (keep the default 30-second window).</li>
<li><strong>Rate limiting?</strong> Yes.</li>
</ol>
<p>Scan the QR code with your authenticator app (Google Authenticator, Authy, or any TOTP-compatible app). Save the emergency scratch codes somewhere offline.</p>
<p>Configure PAM to require the TOTP code. Edit <code>/etc/pam.d/sshd</code>:</p>
<pre><code class="language-bash">sudo nano /etc/pam.d/sshd
</code></pre>
<p>Add this line at the end:</p>
<pre><code>auth required pam_google_authenticator.so
</code></pre>
<p>Then edit <code>/etc/ssh/sshd_config</code> to enable challenge-response and specify the required authentication methods:</p>
<pre><code>KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactive
</code></pre>
<p>The <code>AuthenticationMethods</code> line means the user must present a valid key <em>and then</em> pass the keyboard-interactive challenge (the TOTP prompt). Restart sshd:</p>
<pre><code class="language-bash">sudo systemctl restart sshd
</code></pre>
<p>Test from a new terminal. You should see a "Verification code:" prompt after your key is accepted. Enter the 6-digit code from your authenticator app.</p>
<p><strong>Gotcha:</strong> the earlier section set <code>KbdInteractiveAuthentication no</code> to disable passwords. This section re-enables it specifically for TOTP. The critical difference is that <code>PasswordAuthentication</code> remains <code>no</code>, which means raw passwords are still rejected. The keyboard-interactive channel is now used exclusively for the TOTP challenge because PAM handles the prompt. If you get "Permission denied" after enabling this, check that <code>AuthenticationMethods</code> lists <code>publickey,keyboard-interactive</code> (comma-separated, no spaces), not <code>publickey keyboard-interactive</code> (space-separated means "either method," not "both methods").</p>
<h2>Use a Jump Host for Layered Access</h2>
<p>A jump host (also called a bastion) is an intermediary server that sits between the public internet and your production VPS. Instead of exposing SSH on the production server directly, you SSH into the jump host first, and from there connect to the target.</p>
<p>This is practical when you manage multiple servers. The jump host is the only machine with a public SSH port; all other servers accept SSH connections only from the jump host's private IP.</p>
<p>Set up the jump host's firewall to allow SSH only from your IP:</p>
<pre><code class="language-bash">sudo ufw allow from YOUR_WORKSTATION_IP to any port 2222 proto tcp
</code></pre>
<p>On the production server, restrict SSH to the jump host's private IP only:</p>
<pre><code class="language-bash">sudo ufw allow from JUMP_HOST_PRIVATE_IP to any port 22 proto tcp
sudo ufw deny 22/tcp
</code></pre>
<p>Configure your local SSH client to proxy through the jump host automatically. Add this to <code>~/.ssh/config</code> on your workstation:</p>
<pre><code>Host jump
    HostName JUMP_HOST_PUBLIC_IP
    Port 2222
    User youruser
    IdentityFile ~/.ssh/id_ed25519

Host production
    HostName PRODUCTION_PRIVATE_IP
    User youruser
    IdentityFile ~/.ssh/id_ed25519
    ProxyJump jump
</code></pre>
<p>Now <code>ssh production</code> transparently hops through the jump host. You never need to manually SSH into the bastion and then SSH again.</p>
<p>For providers that offer virtual private networks or VLANs between servers in the same data center, the production server does not need any public SSH port at all. The jump host is the only entry point.</p>
<p><strong>Gotcha:</strong> if the jump host goes down, you lose access to every server behind it. Always make sure your VPS provider's web console (KVM, VNC, or browser-based terminal) works as a recovery path. Test it before you need it, not during an outage.</p>
<h2>Keep OpenSSH Updated</h2>
<p>None of these hardening steps matter if the SSH daemon itself has a known vulnerability. Package your OpenSSH updates with your regular system patching. On Debian and Ubuntu:</p>
<pre><code class="language-bash">sudo apt update &amp;&amp; sudo apt upgrade openssh-server
</code></pre>
<p>The <a href="https://vpshostreview.com/articles/how-to-keep-vps-patched-and-updated">patching and update guide</a> covers unattended-upgrades configuration so security patches apply automatically.</p>
<h2>Pulling It All Together</h2>
<p>After working through every section, here is what SSH access to your server looks like:</p>
<ol>
<li>Key-based login only. Passwords are rejected.</li>
<li>Only named users can connect, and root login is disabled.</li>
<li>SSH listens on a non-standard port behind a firewall.</li>
<li>Failed login attempts are capped and idle connections are dropped.</li>
<li>TOTP provides a second factor beyond the key itself.</li>
<li>Optionally, a jump host removes direct public exposure entirely.</li>
</ol>
<p>Not every server needs every layer. A personal project VPS behind key auth, fail2ban, and a firewall is well protected. A production server handling customer data benefits from the full stack, especially TOTP and a jump host.</p>
<p>SSH is the starting point, not the finish line. The <a href="https://vpshostreview.com/articles/vps-security-checklist">VPS security checklist</a> covers the rest of the picture: firewalls, automatic updates, backups, and monitoring.</p>
]]></content:encoded></item></channel></rss>