Setting up SSH key authentication on Linux VPS

Setting up SSH key authentication on Linux VPS

What You’ll Need

  • Hetzner VPS or Contabo VPS for hosting your Linux server
  • DigitalOcean as an alternative hosting provider
  • A local machine running Linux, macOS, or Windows with WSL2
  • SSH client (built-in on Linux/macOS, included with Windows 10+)
  • Basic command-line familiarity

Table of Contents

Why SSH Keys Beat Passwords

I’ve managed dozens of VPS instances across hosting providers, and I can tell you with certainty: password-based SSH authentication is the security equivalent of leaving your front door unlocked. SSH keys eliminate brute-force attacks, eliminate password reuse risks, and let you authenticate without ever transmitting a password across the network. They’re cryptographically stronger, faster to use once configured, and they’re the industry standard for good reason.

The basic principle is simple. You generate a keypair: a private key (stays on your local machine, never shared) and a public key (goes on the server). When you connect, the server challenges you with data encrypted by your public key. Only your private key can decrypt it, proving your identity without password transmission. It’s beautiful cryptography in action.

If you’re planning to run production workloads on your VPS, SSH keys are non-negotiable. Whether you’re building web scraping data pipelines on VPS or running background jobs with scheduled tasks, you need secure, passwordless authentication that plays well with automation scripts and CI/CD systems.

Generating Your SSH Keypair Locally

I always start on my local machine. Let me walk you through the complete process, whether you’re on Linux, macOS, or Windows with WSL2.

Open your terminal and run this command:

ssh-keygen -t ed25519 -C "your-email@example.com" -f ~/.ssh/id_ed25519

Let me break down what’s happening here. The -t ed25519 flag specifies the ED25519 algorithm, which is modern, compact, and more secure than the older RSA algorithm. The -C flag adds a comment (I use my email, but it can be anything) to identify the key. The -f flag specifies exactly where to save the key and what to name it. I’m using ~/.ssh/id_ed25519, which is the standard location.

When prompted for a passphrase, I recommend entering one. This passphrase encrypts your private key on disk. If someone gets your machine, they can’t use your key without the passphrase. Enter your passphrase (it won’t echo to the screen):

Enter passphrase (empty for no passphrase):

Then confirm it:

Enter same passphrase again:

The command generates two files:

~/.ssh/id_ed25519          (private key, keep this secret)
~/.ssh/id_ed25519.pub      (public key, this goes on the server)

Let me verify the keys were created correctly:

ls -la ~/.ssh/id_ed25519*

You should see output similar to:

-rw-------  1 user  group   399 Dec 15 10:24 /home/user/.ssh/id_ed25519
-rw-r--r--  1 user  group    95 Dec 15 10:24 /home/user/.ssh/id_ed25519.pub

Notice the permissions: the private key has 600 (readable/writable only by you), and the public key has 644 (readable by everyone). This is correct and essential for security.

Now let’s see the actual content of the public key:

cat ~/.ssh/id_ed25519.pub

Output will look something like:

ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKp7q8X3Zx9mQ2r4L5dY6vN8oP1sT2uV3wX4yZ5aB6cD your-email@example.com

Copy this entire line (the ssh-ed25519 part through your email comment). You’ll paste it on the server.

If you ever need to view your private key’s fingerprint to verify it:

ssh-keygen -l -f ~/.ssh/id_ed25519

This outputs a hash like:

256 SHA256:aBcDeFgHiJkLmNoPqRsTuVwXyZ1a2b3c4d5e6f7g8h your-email@example.com (ED25519)

This fingerprint is useful when you need to verify a key in your SSH config or when troubleshooting connections.

Uploading Your Public Key to the VPS

Now I need to get my public key onto the server. I’m going to assume you have a fresh VPS from Hetzner VPS, Contabo VPS, or DigitalOcean, and you’ve received root credentials via email or the hosting dashboard.

First connection requires a password, which is fine since we’re only doing it once. Connect to your VPS:

ssh root@YOUR_VPS_IP_ADDRESS

Replace YOUR_VPS_IP_ADDRESS with your actual IP. You’ll be prompted for the root password from your hosting provider:

The authenticity of host 'XXX.XXX.XXX.XXX (XXX.XXX.XXX.XXX)' can't be established.
ED25519 key fingerprint is SHA256:xxxxxxxxxxxxx.
This key is added to ECDSA key fingerprint is SHA256:xxxxxxxxxxxxx.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

Type yes and hit enter. Then paste your root password.

Now that I’m on the server, I need to set up the .ssh directory and the authorized_keys file if they don’t exist:

mkdir -p ~/.ssh
chmod 700 ~/.ssh
touch ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

Let me explain these permissions. The .ssh directory itself needs 700 (read, write, execute for owner only). The authorized_keys file needs 600 (read and write for owner only). SSH is picky about permissions for security reasons. If these are wrong, SSH authentication won’t work.

Now, append your public key to the authorized_keys file:

echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKp7q8X3Zx9mQ2r4L5dY6vN8oP1sT2uV3wX4yZ5aB6cD your-email@example.com" >> ~/.ssh/authorized_keys

Paste your actual public key (the one you copied from your local machine). The >> operator appends to the file without overwriting existing content, which matters if you’re adding multiple keys.

Verify it was added:

cat ~/.ssh/authorized_keys

You should see your public key displayed. If you’re planning to add more keys later (for other team members or machines), you can repeat this echo command. Each key should be on its own line.

💡 Fast-Track Your Project: Don’t want to configure this yourself? I build custom n8n pipelines and bots. Message me with code SYS3-HUGO.

Hardening SSH Configuration

At this point, you have key authentication working. But we’re not done. I need to harden the SSH configuration to disable password authentication entirely and improve security overall.

While still logged in as root, open the SSH config file:

nano /etc/ssh/sshd_config

Or if you prefer vim:

vim /etc/ssh/sshd_config

I’m looking for and modifying these specific lines. First, find the PermitRootLogin line. It probably looks like:

#PermitRootLogin prohibit-password

Change it to:

PermitRootLogin no

This disables root login via SSH entirely. Root should never log in remotely.

Next, find the PubkeyAuthentication line:

#PubkeyAuthentication yes

Uncomment it (remove the #) and ensure it says yes:

PubkeyAuthentication yes

Now find the PasswordAuthentication line:

#PasswordAuthentication yes

Uncomment it and change it to:

PasswordAuthentication no

This disables password-based SSH authentication. Only key authentication works now.

Find the AuthorizedKeysFile line. It probably looks like:

AuthorizedKeysFile	.ssh/authorized_keys .ssh/authorized_keys2

This is fine as-is. It tells SSH where to look for your public keys.

I also recommend these additional security settings. Find or add these lines:

PermitEmptyPasswords no
MaxAuthTries 3
MaxSessions 5
ClientAliveInterval 300
ClientAliveCountMax 2

Let me explain what these do:

  • PermitEmptyPasswords no: Prevents anyone from logging in with a blank password (good practice even with password auth disabled)
  • MaxAuthTries 3: Limits authentication attempts to 3 before disconnecting (stops brute-force)
  • MaxSessions 5: Limits the number of concurrent sessions per connection
  • ClientAliveInterval 300: Sends a keep-alive message every 300 seconds
  • ClientAliveCountMax 2: Closes the connection after 2 missed keep-alives

Save and exit. In nano, press Ctrl+X, then Y, then Enter. In vim, press Escape, type :wq, and hit Enter.

Now restart the SSH daemon to apply changes:

systemctl restart ssh

Or on some systems:

systemctl restart sshd

Check that SSH restarted successfully:

systemctl status ssh

You should see “active (running)” in the output. If it failed, there might be a syntax error in your config file. You can check with:

sshd -t

This performs a configuration test without restarting. If there’s an error, it will tell you.

Before you log out, open a new terminal window on your local machine and test the connection with your SSH key:

ssh -i ~/.ssh/id_ed25519 root@YOUR_VPS_IP_ADDRESS

Wait, I just disabled root login. Let me create a regular user first. Back on the VPS (still logged in as root), create a new user:

adduser deploy

You’ll be prompted for a password and personal information. The password is just for the local sudo command, not for remote login (since we disabled password authentication). Set a strong one anyway:

Adding user `deploy' ...
Adding new group `deploy' (1001) ...
Adding new home directory `/home/deploy' ...
Copying files from `/etc/skel' ...
New password:
Retype new password:
Full Name []:
Room Number []:
Work Phone []:
Home Phone []:
Other []:
Is the information correct? [y/N]

Press Enter through all the prompts except the password fields, then press y at the end.

Now add this user to the sudo group so they can run administrative commands:

usermod -aG sudo deploy

Now switch to the deploy user:

su - deploy

And set up SSH keys for this user too:

mkdir -p ~/.ssh
chmod 700 ~/.ssh

Paste your public key into the authorized_keys file:

echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKp7q8X3Zx9mQ2r4L5dY6vN8oP1sT2uV3wX4yZ5aB6cD your-email@example.com" > ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

Note: I used > (overwrite) instead of >> (append) because this is the first key for this user.

Now log out:

exit

Then exit from root:

exit

Testing and Troubleshooting

Back on your local machine, test connecting as the deploy user:

ssh -i ~/.ssh/id_ed25519 deploy@YOUR_VPS_IP_ADDRESS

You should connect without a password prompt. If your SSH key has a passphrase (which I recommended), you’ll be prompted for that passphrase, but not for a server password.

If you get Permission denied (publickey), there are several common issues:

First, verify the server logs:

ssh -i ~/.ssh/id_ed25519 -v deploy@YOUR_VPS_IP_ADDRESS

The -v flag enables verbose output. Look for lines like Offering public key and Authentication succeeded or error messages.

Make sure file permissions are exactly right:

stat -c '%a %n' ~/.ssh
stat -c '%a %n' ~/.ssh/authorized_keys

On your local machine, verify your private key isn’t world-readable:

stat -c '%a %n' ~/.ssh/id_ed25519

Should show 600.

Make sure your public key is actually in the authorized_keys file:

cat ~/.ssh/authorized_keys

Copy-paste errors happen. Verify the key starts with ssh-ed25519 and ends with your comment.

If you’re still having trouble, check the SSH daemon is actually listening:

sudo netstat -tlnp | grep sshd

Or with newer systems:

sudo ss -tlnp | grep sshd

You should see it listening on port 22.

For convenience, create an SSH config file on your local machine so you don’t need to remember the IP address or username:

nano ~/.ssh/config

Add this:

Host myvps
    HostName YOUR_VPS_IP_ADDRESS
    User deploy
    IdentityFile ~/.ssh/id_ed25519
    ServerAliveInterval 60

Now you can simply connect with:

ssh myvps

SSH will use all the settings automatically.

If you need to manage multiple servers or add multiple keys for team members, this config file becomes invaluable. Each Host block can have its own settings.

Getting Started

Ready to set up SSH key authentication? Start with your VPS provider of choice:

Once your VPS is running Linux (Ubuntu 22.04 LTS is my standard choice), follow the steps above. The entire process takes about 15 minutes the first time.

When you’re ready to automate workflows on your VPS, SSH keys integrate seamlessly with deployment tools, CI/CD systems, and automation frameworks. If you’re scheduling Python jobs with APScheduler and Redis or building structured log pipelines with Fluent Bit, passwordless SSH authentication is what makes those systems secure and scalable.

Outsource Your Automation

Don’t have time? I build production n8n workflows, WhatsApp bots, and fully automated YouTube Shorts pipelines. Hire me on Fiverr, mention SYS3-HUGO for priority. Or DM at chasebot.online.

Want to automate this yourself?

Start with n8n Cloud (free tier available) or self-host on a Hetzner VPS for full control.

Want this engine running on your own VPS?

This blog publishes itself — daily, unattended, on free API tiers. The full engine, Hugo theme, and setup guide are available as System 3.

Get System 3
system online