Skip to content

SSH

SSH is one of those tools you just can't live without. You can transfer files with scp or sftp, tunnel in all sorts of ways, use public/private keys or even hardware tokens, run remote commands, forward your agent, forward X11 apps, mount remote filesystems with SSHFS, and it's all lightweight and encrypted. What's not to love?

Small things first

File Permissions

chmod 700 ~/.ssh                    # Your .ssh directory
chmod 600 ~/.ssh/id_*               # Private keys
chmod 644 ~/.ssh/id_*.pub           # Public keys
chmod 644 ~/.ssh/authorized_keys    # Server: allowed public keys
chmod 644 ~/.ssh/known_hosts        # Known server fingerprints
chmod 600 ~/.ssh/config             # Your SSH config

Copy Public Key to Server

ssh-copy-id user@server             # Easiest way OR do this:
cat ~/.ssh/id_ed25519.pub | ssh user@server "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"

Fetch/Update Keys from GitHub

Easily import SSH public keys from a GitHub profile into your authorized_keys.

Other keys remain unchanged. Updating looks for a specific comment we set on them from the first run.

Fetch/Update Keys from GitLab

Easily import public keys from a GitLab profile into your authorized_keys.

Other keys remain unchanged. Updating looks for a specific comment we set on them from the first run.

authorized_keys Options

You can restrict what authenticated users can do by adding options before their public keys

# Restrict command and source IP
from="192.168.1.0/24",command="/usr/local/bin/backup" ssh-ed25519 AAAAC3...

# No port forwarding, only command execution
no-port-forwarding,no-x11-forwarding,no-agent-forwarding,no-pty,command="/path/to/script.sh" ssh-ed25519 AAAAC3...

# Port forwarding restricted to specific ports
permitlisten="8080,3000" ssh-ed25519 AAAAC3...

# Multiple restrictions combined
from="2001:db8::/64",no-pty,restrict,port-forwarding ssh-ed25519 AAAAC3...

Common options:
from="pattern" - Limit connection source IPs/subnets (comma-separated)
command="cmd" - Force a specific command, ignore what the client asks for
restrict - Enable all restrictions (disables port/X11/agent forwarding, PTY)
no-port-forwarding - Disable -L and -R forwarding
no-x11-forwarding - Disable X11 forwarding
no-agent-forwarding - Disable ssh-agent forwarding
no-pty - Disable pseudo-terminal allocation
permitlisten="port" - Limit which ports -R can bind to (with restrict)
permitopen="host:port" - Limit which destinations -L can connect to

SSHFS & SCP

Mount remote filesystems over SSH

sshfs user@server:/remote/path /local/mount/point
sshfs user@server:/remote/path /local/mount/point -o reconnect,ServerAliveInterval=15,ServerAliveCountMax=3
umount /local/mount/point                           # Unmount when done

Useful options: -o allow_other (let other users access), -o ro (read-only), -o follow_symlinks

Copy files over SSH

scp file.txt user@server:/remote/path/              # Upload file
scp user@server:/remote/file.txt /local/path/       # Download file
scp -r folder/ user@server:/remote/path/            # Upload directory
scp -P 2222 file.txt user@server:/path/             # Custom port
scp -3 user1@host1:/path/file user2@host2:/path/    # Between two remote hosts

For large transfers or resuming, consider rsync -avz --progress instead.

Hardening Security

Keep SSH secure by disabling passwords, root login, and unused auth methods. Limit login attempts and restrict users. Start with these in your sshd_config

# Disable password login (only allow keys)
PasswordAuthentication no

# Enable public key authentication
PubkeyAuthentication yes

# Prevent root login
PermitRootLogin no

# Limit failed login attempts
MaxAuthTries 3

# Disallow empty passwords
PermitEmptyPasswords no

# Disable challenge-response auth
ChallengeResponseAuthentication no

# Only allow listed users
AllowUsers youruser

FIDO2

FIDO2 lets you keep your private key in your pocket, on a hardware device, so you can bring it anywhere.

Key Types

Resident keys: everything you need lives ON the device (recommended)
Non-resident keys: you also need to carry a USB flash drive or something with the key file on it

Compatibility Check

First, find your device with fido2-token -L, then check its capabilities with fido2-token -I /dev/hidraw0. Look for maxcredcntlst > 0 and "rk" in options.

Setup your FIDO2 Security Key

Follow the manufacturers instructions to set a PIN, or use fido2-token -S /dev/hidraw0 to set one, or fido2-token -C /dev/hidraw0 to change it.

Generate the key

ssh-keygen -t ed25519-sk -O resident -O verify-required -O application=ssh:ThisNameWasNotSecretAfterAll

Remove -O resident if your device doesn't support it (then you have to carry the key file around)
Remove -O verify-required if you trust your physical and computer's security (the device can override this)
Remove -O application= if you don't want to easily identify it later.
The application name is stored on the device and used in the key filename. Keep it clean :D

Client Setup

Requirements:
OpenSSH client 8.3+ (ssh -V)
Linux: Most distros include SK support
Windows: OpenSSH 8.9+ "SSH Client" from Optional Features or winget install Microsoft.OpenSSH.Preview
MacOS: Apple wants you to use their keychain instead of FIDO2. brew install openssh to get a real and newer SSH version
libfido2: pacman -S libfido2 / dnf install libfido2 / apt install fido2-tools / brew install libfido2

Test your setup without a security key inserted:
ssh-keygen -t ed25519-sk "Enter PIN" or "device not found" = good

Quick start:
Step 1, extract the identifier:

ssh-keygen -K
chmod 600 id_ed25519_sk_rk_ThisNameWasNotSecretAfterAll
Or simply ssh-add -K if you use ssh-agent :D

Step 2, use it:

ssh user@server -i id_ed25519_sk_rk_ThisNameWasNotSecretAfterAll
Or if you use ssh-agent, just ssh user@server ;)

Step 3, idk, takeover the world or something? eat ice cream?

More permanent use

Simple (1 key):
Rename to ~/.ssh/id_ed25519_sk

Multiple keys or per-host:
Use ~/.ssh/config:

Host *
    IdentityFile ~/.ssh/id_ed25519_sk_rk_MyKeyIsBiggerThanYours
    IdentityFile ~/.ssh/id_ed25519_sk_rk_ThisNameWasNotSecretAfterAll

Host work.mths.io
    User admin
    IdentityFile ~/.ssh/id_ed25519_sk_rk_IHateThisCompanyAndNowTheyGaveMeThisUSBKeyICantSavePornOn

With ssh-agent:
Unfortunately there's no good solution for ssh-agent here.
But you can add AddKeysToAgent in your config if you need it to be available in ssh-agent

Host *  
  AddKeysToAgent yes  
  IdentityFile ~/.ssh/id_ed25519_sk_rk_ThisNameWasNotSecretAfterAll  

Jump Hosts

Jump hosts (bastion hosts) let you access servers that aren't directly reachable from your machine. The jump host acts as an intermediate gateway.

Basic Usage

Command line with -J (ProxyJump):

ssh -J [email protected] user@targetserver        # Single jump
ssh -J [email protected],[email protected] user@target  # Multiple hops

Without leaving your key on the jump host:
The -J flag automatically forwards your authentication through the jump host without copying your private key there. Your key never touches the intermediary server.

SSH Config

For permanent setup, add to ~/.ssh/config:

# Basic jump host setup
Host target
    HostName target.internal.network
    User targetuser
    ProxyJump [email protected]

# Multiple jump hosts
Host deep-target
    HostName 10.0.0.50
    User admin
    ProxyJump [email protected],[email protected]

# Reusable jump host definition
Host jump
    HostName jump.mths.io
    User jumpuser
    IdentityFile ~/.ssh/id_ed25519_jump

Host internal-*
    ProxyJump jump
    User admin

Host internal-db
    HostName db.internal.network

Host internal-web
    HostName web.internal.network

Example: irssi

Connect to irssi running in screen on a remote server behind a jump host:

Host irssi
    HostName irssi.mths.local
    User yourusername
    IdentityFile ~/.ssh/id_ed25519_irssi
    ProxyJump [email protected]
    ServerAliveInterval 1
    ServerAliveCountMax 4
    ConnectTimeout 10
    RequestTTY yes
    RemoteCommand screen -A -rx irssi

Connect with ssh irssi or autossh irssi

Options explained:
ServerAliveInterval 1 - Send keepalive every 1 second
ServerAliveCountMax 4 - Disconnect after 4 failed keepalives
ConnectTimeout 10 - Give up connecting after 10 seconds
RequestTTY yes - Force PTY allocation for interactive screen session
RemoteCommand - Auto-attach to screen session

Regular Tunnels

SOCKS Proxy ("like a VPN")

Route your traffic through a remote server using SSH as a SOCKS5 proxy.

ssh -D 8080 -C user@server    # -C for compression

Set your browser's SOCKS5 proxy to localhost:8080

Local Port Forward (-L)

Access a remote service on your local machine

ssh -L local_port:target:target_port user@server
ssh -L 8080:localhost:1234 user@server          # Remote localhost:1234 → your localhost:8080
ssh -L 8080:internal-server:3306 user@server    # Remote network server → your localhost:8080

Remote Port Forward (-R)

Expose your local service on the remote server

ssh -R remote_port:target:target_port user@server
ssh -R 8080:localhost:1234 user@server          # Your localhost:1234 → remote localhost:8080

Reverse SSH Port Forwarding

Reverse port forwarding lets remote servers that you can't directly access (behind NAT/firewall) connect outward to your jump host, making them accessible through that jump host. This creates persistent tunnels from unreachable networks.

I'll use my domain in the examples to keep it simple.

For your own safety, please use at least OpenSSH 7.8p1, on all your servers (well... use the newest stable you can!)

Jump server

This will be the server all your remote servers connect to, and exposes their SSH on a port on this server.

Due to the security of this subject, you need to be sure you know what you're doing. This could poke holes where holes are not supposed to be.

We'll make a limited user, by not allowing it to login:

useradd -m -s /sbin/nologin tunnel

Then we'll give users connected to SSHd to open ports, open for internet/lan, in /etc/ssh/sshd_config:

GatewayPorts yes # STOP!!! This could be a HUGE security issue
                 # If you're using outdated software, weak passwords 
                 # and it's in general a stupid thing to do.. 
                 # If you're forwarding something that's behind a NAT/firewall, 
                 # it's probably there for a reason. Unless you purposely made it this way.
                 # This is not required

We'll add the SSH client keys, of the remote users into /home/tunnel/.ssh/authorized_keys:

permitlisten="5001,5002",command="/sbin/nologin",restrict,port-forwarding,no-pty ssh-rsa AAAAB3NzaC1yc2..
from="2001:db8::/64",permitlisten="5003,5004",command="/sbin/nologin",restrict,port-forwarding,no-pty ssh-rsa AAAAB3NzaC1yc3..
Note, we can limit the IP addresses and subnets with the from field.

Remote servers/targets

They don't have to be servers, as we're only using the SSH client on these. But for the sake of naming things we'll keep this simple.

We need to generate a strong SSH private key, so we can put the public key onto our jump host:

ssh-keygen -t ed25519 -C "jumpremote1"
ssh-keygen -t ed25519 -C "jumpremote2" -f /config/.ssh/id_ed25519_tunnel2

Then we'll use this command, to initiate the tunnel:

ssh -f -n -N -C -R 5001:127.0.0.1:22 [email protected]
ssh -f -n -N -C -R 5001:127.0.0.1:22 -i /config/.ssh/id_ed25519_tunnel2 [email protected]
You can use autossh instead of ssh for when you need auto recovery. Replace 127.0.0.1 with another IP, if you want to for example share an IoT device on the remote network.

Clients

If you changed GatewayPorts in sshd_config, when you SSH to tunnelserver.mths.io:5001 it translates to target:22, through the jump host. If you didn't, you'll need to use the jump server as a jump host.

For example, I can easily make a SOCKS proxy this way. This allows me to easily access web management pages in my browser, on the remote network.

ssh -D 5555 -C [email protected]:5001 #C for compress
ssh -J [email protected] -D 5555 -C [email protected]:5001 #C for compress, J for jump host

X11 Forwarding

Run graphical applications from a remote server on your local display.

ssh -X user@server                  # Basic X11 forwarding
ssh -Y user@server                  # Trusted X11 (less secure, more compatible)
Then just run GUI apps like firefox or gedit and they appear locally.

LLM were used for corrections, grammar and spellchecking on this page