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-add -K if you use ssh-agent :D
Step 2, use it:
Or if you use ssh-agent, justssh 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
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.
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:
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..
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]
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)
firefox or gedit and they appear locally.
LLM were used for corrections, grammar and spellchecking on this page