All articles🇬🇧 English
8 min

SSH Keys on Ubuntu — Generation, Configuration, Security

How to set up SSH keys on Ubuntu: ssh-keygen ed25519, ~/.ssh/config, file permissions, agent forwarding, fail2ban. Practical guide.

sshssh-keygened25519ubuntusecurityfail2banserver

SSH keys are a pair of cryptographic files (private + public) that replace password authentication when connecting to a server. An ed25519 key at 256 bits is impossible to crack by brute force — unlike a password like qwerty123 which can be guessed in seconds. If you're setting up a VPS from scratch, SSH keys are one of the first steps after basic server preparation.

Generating SSH Keys (ssh-keygen)

ssh-keygen is the utility for creating SSH keys, built into OpenSSH on Ubuntu. For new servers use the Ed25519 algorithm:

ssh-keygen -t ed25519 -C "deployer@myproject"

The -C flag adds a comment — useful when you have multiple keys. By default keys are saved in ~/.ssh/:

~/.ssh/id_ed25519       — private key (never share with anyone)
~/.ssh/id_ed25519.pub   — public key (copied to servers)

Passphrase during generation is an additional layer of protection. If someone obtains the private key file, it's useless without the passphrase. For server keys used in CI/CD, passphrase is usually skipped — otherwise the pipeline can't connect automatically.

Ed25519 vs RSA — Which to Choose

ParameterEd25519RSA-4096
Key length256 bits4096 bits
SecurityEquivalentEquivalent
Signing speed20–30x fasterSlower
Signature size64 bytes512 bytes
SupportOpenSSH 6.5+ (2014)Everywhere

Ed25519 is preferred: shorter, faster, not susceptible to weak parameter attacks (unlike DSA and ECDSA with NIST curves). RSA-4096 is only needed if the server is older than 2014 or runs software that doesn't support Ed25519.

I once ran into a situation where a legacy CentOS 6 server wouldn't accept ed25519 — had to generate a separate RSA key just for it:

ssh-keygen -t rsa -b 4096 -C "legacy-server" -f ~/.ssh/id_rsa_legacy

Copying the Key to the Server

ssh-copy-id is the standard way to add a public key to a remote server:

ssh-copy-id -i ~/.ssh/id_ed25519.pub deployer@YOUR_SERVER_IP

The command adds the contents of the .pub file to ~/.ssh/authorized_keys on the server. If ssh-copy-id isn't available (Windows without WSL), do it manually:

# From your local machine
cat ~/.ssh/id_ed25519.pub | ssh deployer@YOUR_SERVER_IP "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

After copying, verify key-based login in a new terminal without closing the current session:

ssh deployer@YOUR_SERVER_IP

If login succeeds without a password prompt (or with a passphrase prompt for the key) — the key works.

File Permissions — Common Cause of "Key Doesn't Work"

OpenSSH refuses to use keys if file permissions are too broad. This protects against other system users reading your keys. Correct values:

# On local machine
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
chmod 644 ~/.ssh/config

# On server
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

If after ssh-copy-id the connection still asks for a password — in 80% of cases the issue is permissions. Check on server:

ls -la ~/.ssh/

The owner of all files in ~/.ssh/ must match the user you're logging in as. If files belong to root but you're logging in as deployer — SSH will silently refuse.

Disabling Password Authentication

After the key works and is verified, password login must be disabled — otherwise keys don't protect against brute force:

sudo vim /etc/ssh/sshd_config

Three directives:

PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
sudo sshd -t           # check config for errors
sudo systemctl restart sshd

sshd -t is the equivalent of nginx -t — checks config syntax before restart. Few people pay attention to this command, but it saves you from locking yourself out of the server.

Do not close the current SSH session until you verify a new connection. If the config has an error — the current session stays active and lets you roll back changes.

~/.ssh/config — Managing Connections

SSH config on the local machine lets you replace long commands with short aliases. Instead of:

ssh -i ~/.ssh/id_ed25519 -p 2222 deployer@185.120.33.47

Just:

ssh myproject

File ~/.ssh/config:

Host myproject
    HostName 185.120.33.47
    User deployer
    Port 22
    IdentityFile ~/.ssh/id_ed25519

Host staging
    HostName 185.120.33.48
    User deployer
    Port 22
    IdentityFile ~/.ssh/id_ed25519

Host legacy
    HostName 10.0.0.5
    User admin
    IdentityFile ~/.ssh/id_rsa_legacy
    # Old server — doesn't support ed25519

The config works not just for ssh — it's picked up by scp, rsync, git, ssh-copy-id, and other tools using OpenSSH.

Wildcard Rules

Common parameters for all servers:

Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3
    AddKeysToAgent yes

ServerAliveInterval 60 sends a keepalive packet every 60 seconds — prevents SSH session drops during inactivity. In practice it's simpler than it sounds: add three lines once — forget about "Connection reset by peer".

SSH-agent — Skip Passphrase Every Time

SSH-agent stores decrypted keys in memory for the duration of the session. Without it, every connection prompts for the passphrase:

# Start agent (usually starts automatically)
eval "$(ssh-agent -s)"

# Add key
ssh-add ~/.ssh/id_ed25519

On Ubuntu with a graphical environment ssh-agent usually runs through GNOME Keyring and starts automatically. On a bare server — add to ~/.bashrc:

if [ -z "$SSH_AUTH_SOCK" ]; then
    eval "$(ssh-agent -s)" > /dev/null
    ssh-add ~/.ssh/id_ed25519 2>/dev/null
fi

Agent Forwarding — Keys Through an Intermediate Server

If you need to connect from a work server to another one (for example, pull from a private Git repository) — agent forwarding passes keys from your local machine:

ssh -A deployer@server1
# On server1 your local keys are now available
git clone git@github.com:yourorg/private-repo.git

In ~/.ssh/config:

Host server1
    HostName 185.120.33.47
    User deployer
    ForwardAgent yes

Agent forwarding is safe as long as you trust the server. If the server is compromised — an attacker can use the forwarded key to connect to other servers while your session is active. Don't enable forwarding for servers you don't fully trust.

fail2ban — Brute Force Protection

fail2ban analyzes logs and blocks IP addresses after a series of failed login attempts. Even with password authentication disabled, fail2ban is useful: it reduces load from bots and cuts noise in logs.

sudo apt install -y fail2ban

Create a local config (don't edit jail.conf — it gets overwritten on updates):

sudo vim /etc/fail2ban/jail.local
[DEFAULT]
bantime  = 3600
findtime = 600
maxretry = 5

[sshd]
enabled = true
port    = ssh
filter  = sshd
logpath = /var/log/auth.log

Settings: 5 failed attempts in 10 minutes → banned for 1 hour. For production servers it's worth noting: you can increase bantime to 86400 (24 hours) and lower maxretry to 3.

sudo systemctl enable fail2ban
sudo systemctl start fail2ban

# Check status
sudo fail2ban-client status sshd

Output shows the number of current bans and the list of blocked IPs. On a fresh VPS within a couple of hours you'll already see entries — bots scan SSH ports constantly.

Changing the Default SSH Port

Changing port 22 to a non-standard one isn't protection against a targeted attack (nmap will scan all ports in minutes), but it blocks 95% of automated bots that only knock on port 22:

sudo vim /etc/ssh/sshd_config
Port 2222
sudo sshd -t
sudo systemctl restart sshd

Before restarting — open the new port in the firewall:

sudo ufw allow 2222/tcp comment 'SSH custom'
sudo ufw delete allow 22/tcp

The order is critical: first open the new port, then restart sshd, then verify the connection, and only after that close the old port 22.

Update ~/.ssh/config on your local machine:

Host myproject
    HostName 185.120.33.47
    Port 2222
    ...

FAQ

Which SSH key algorithm to choose in 2026?

Ed25519 — for all new servers. The key is 20–30x faster than RSA with equivalent cryptographic strength, takes 68 characters instead of 700+. RSA-4096 — only for legacy systems that don't support Ed25519 (OpenSSH < 6.5).

Can I use one SSH key for all servers?

Technically — yes. Practically — not recommended. If the key leaks, all servers are compromised. Minimum hygiene: separate key for production, separate for staging, separate for Git. In ~/.ssh/config you specify which key for which host.

Forgot passphrase — how to recover?

You can't. The passphrase is not stored and cannot be recovered. The only option — generate a new key (ssh-keygen -t ed25519) and replace the public key on all servers where the old one was used. This is exactly why the key passphrase should be stored in a password manager.

Is fail2ban needed if passwords are disabled?

Yes. Disabled passwords protect against break-ins but not against load. A bot trying to connect 10,000 times per minute loads sshd and clutters /var/log/auth.log. fail2ban blocks the IP after the first attempts, reducing noise to zero.

How to forward a port through SSH for pgAdmin access?

For secure access to services that shouldn't be open to the internet (pgAdmin, Grafana, RabbitMQ), use SSH tunneling. Detailed guide with examples — in the article on SSH Port Forwarding.