SSH Basics
{:.gc-basic}
Basic
SSH (Secure Shell) provides an encrypted channel for remote login, command execution, and file transfer. It replaces the insecure telnet and rsh tools.
Connecting to a Remote Host
ssh user@hostname
ssh user@192.168.1.100
ssh -p 2222 user@hostname # non-default port
ssh -i ~/.ssh/my_key user@host # specify private key
ssh user@host "uname -a" # run a command and exit
ssh -t user@host "htop" # force pseudo-terminal allocation
Key-Based Authentication (Passwordless Login)
Password authentication is weak (brute-force, phishing). Key-based auth uses asymmetric cryptography — you keep the private key, the server gets the public key.
# 1. Generate a key pair (run on YOUR local machine)
ssh-keygen -t ed25519 -C "eslam@workstation"
# Stores: ~/.ssh/id_ed25519 (private key — protect this!)
# ~/.ssh/id_ed25519.pub (public key — safe to share)
# Use RSA if the remote is an older system
ssh-keygen -t rsa -b 4096 -C "eslam@workstation"
# 2. Copy the public key to the remote server
ssh-copy-id user@hostname
# Manual alternative (if ssh-copy-id isn't available)
cat ~/.ssh/id_ed25519.pub | ssh user@host "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
# 3. Test (should NOT ask for password)
ssh user@hostname
# Fix permissions if SSH complains
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/id_ed25519
File Transfers
{:.gc-basic}
scp — Secure Copy
# Local → Remote
scp file.txt user@host:/remote/path/
# Remote → Local
scp user@host:/remote/file.txt /local/path/
# Recursive (directory)
scp -r projects/ user@host:~/
# Non-default port
scp -P 2222 file.txt user@host:/tmp/
rsync — Efficient Sync (Recommended over scp)
rsync only transfers changed bytes, making it much faster for large or repeated transfers.
# Sync local dir to remote
rsync -avz projects/ user@host:~/projects/
# -a = archive (recursive, preserve perms/timestamps/symlinks)
# -v = verbose
# -z = compress during transfer
# Sync remote to local
rsync -avz user@host:~/projects/ ./projects/
# Dry run (preview without making changes)
rsync -avzn projects/ user@host:~/projects/
# Delete files on destination that no longer exist at source
rsync -avz --delete projects/ user@host:~/projects/
# Exclude patterns
rsync -avz --exclude='*.o' --exclude='.git/' projects/ user@host:~/
# Non-default SSH port
rsync -avz -e "ssh -p 2222" projects/ user@host:~/
sftp — Interactive FTP over SSH
sftp user@host
sftp> ls # list remote
sftp> lls # list local
sftp> get remote_file.txt
sftp> put local_file.txt
sftp> mput *.conf # multiple upload
sftp> exit
SSH Config File
{:.gc-mid}
Intermediate
~/.ssh/config defines aliases and per-host settings, making complex SSH commands into simple names.
# ~/.ssh/config
# General defaults
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
AddKeysToAgent yes
# Development server
Host devserver
HostName 192.168.1.50
User eslam
Port 2222
IdentityFile ~/.ssh/dev_key
# Raspberry Pi (embedded board)
Host rpi
HostName 192.168.1.100
User pi
IdentityFile ~/.ssh/rpi_key
# Production via bastion
Host production
HostName 10.0.0.20
User deploy
ProxyJump bastion
IdentityFile ~/.ssh/prod_key
Host bastion
HostName jump.company.com
User eslam
IdentityFile ~/.ssh/bastion_key
Now you can simply type:
ssh devserver
rsync -avz code/ rpi:~/
ssh production
Port Forwarding (SSH Tunneling)
{:.gc-mid}
SSH can tunnel TCP connections through the encrypted channel — essential for accessing services behind firewalls or on isolated networks.
Local Port Forwarding
Map a local port to a remote service — useful when the remote service is not publicly accessible.
# Forward local port 8080 to remote 192.168.1.10:80 via ssh-server
ssh -L 8080:192.168.1.10:80 user@ssh-server
# Now in your browser: http://localhost:8080 reaches the internal web server
Diagram:
[Your PC:8080] --encrypted--> [ssh-server] --> [192.168.1.10:80]
Remote Port Forwarding (Reverse Tunnel)
Expose a local service to the remote server — useful for accessing a device behind NAT from the internet.
# Expose local port 3000 as port 9000 on the remote server
ssh -R 9000:localhost:3000 user@remote-server
# On the remote server: curl http://localhost:9000 reaches your local service
Dynamic Port Forwarding (SOCKS Proxy)
Create a SOCKS5 proxy through the SSH server — routes all traffic through it.
ssh -D 1080 user@ssh-server
# Configure browser/system to use SOCKS5 proxy on localhost:1080
# All traffic is tunnelled through ssh-server
Background (Non-Blocking) Tunnels
# -N = don't execute a remote command (tunnel only)
# -f = go to background before exec
ssh -N -f -L 5432:db-host:5432 user@ssh-server
# Now: psql -h localhost -p 5432 connects to remote Postgres
Advanced: SSH for Embedded Development
{:.gc-adv}
Advanced
Accessing an Embedded Board via ProxyJump
When your target board is on an isolated network accessed through a gateway:
# Direct: gateway → board
ssh -J eslam@gateway.company.com pi@192.168.10.50
# Or in ~/.ssh/config:
Host board
HostName 192.168.10.50
User root
ProxyJump gateway
Host gateway
HostName gateway.company.com
User eslam
IdentityFile ~/.ssh/gateway_key
Remote GDB over SSH Tunnel
Debug a process running on an embedded board from your workstation’s IDE:
# On the board: start gdbserver
gdbserver :3333 ./my_firmware
# On workstation: forward the port
ssh -N -f -L 3333:localhost:3333 root@board
# In GDB / VS Code / CLion: connect to localhost:3333
gdb-multiarch ./my_firmware
(gdb) target remote localhost:3333
Hardening sshd_config
Edit /etc/ssh/sshd_config and restart sshd:
# Disable password authentication (use keys only)
PasswordAuthentication no
ChallengeResponseAuthentication no
# Disable root login
PermitRootLogin no
# Allow only specific users
AllowUsers eslam deploy
# Change default port (security through obscurity, helps reduce noise)
Port 2222
# Disable X11 forwarding if not needed
X11Forwarding no
# Set idle timeout (disconnect idle sessions after 10 minutes)
ClientAliveInterval 300
ClientAliveCountMax 2
# Disable empty passwords
PermitEmptyPasswords no
# Use only strong algorithms
KexAlgorithms curve25519-sha256,diffie-hellman-group14-sha256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com
# Test config before restarting
sudo sshd -t
# Restart sshd
sudo systemctl restart sshd
ssh-agent and Key Management
# Start agent and add keys
eval $(ssh-agent)
ssh-add ~/.ssh/id_ed25519
ssh-add -l # list loaded keys
# Forward agent to remote (use with care — security risk)
ssh -A user@remote
# Or in config:
Host *
ForwardAgent yes # careful: only enable for trusted hosts
SSH Certificate Authority (for Teams)
Instead of distributing individual public keys, use an SSH CA:
# Create a CA key pair (do once, keep private key offline)
ssh-keygen -t ed25519 -f ssh_ca
# Sign a user's public key (valid for 1 day)
ssh-keygen -s ssh_ca -I "eslam@company" -n "eslam" -V +1d id_ed25519.pub
# On servers: trust the CA (add to sshd_config)
# TrustedUserCAKeys /etc/ssh/ca.pub
# Users now present their certificate — no need to copy public keys to each server
ssh -i id_ed25519-cert.pub user@server
Interview Q&A
{:.gc-iq}
Interview Q&A
Q1 — Basic: How does SSH public-key authentication work?
- The client sends the public key fingerprint to the server.
- The server checks if that public key is in
~/.ssh/authorized_keys.- If found, the server generates a random challenge and encrypts it with the public key.
- Only the holder of the corresponding private key can decrypt the challenge.
- The client decrypts and proves possession of the private key — without ever transmitting it.
Q2 — Basic: What is the difference between scp and rsync?
scpalways copies the entire file regardless of changes.rsynccalculates a rolling checksum of file blocks and only transfers the changed blocks, making it much faster for large files or repeated syncs.rsyncalso supports--deletefor mirror-style syncs, dry-run mode, and bandwidth limiting. Usersyncfor almost everything except very simple one-off copies.
Q3 — Intermediate: What is local port forwarding and give a real use case?
Local port forwarding (
ssh -L local_port:target_host:target_port jump_host) creates a tunnel where connections tolocalhost:local_portare forwarded through the SSH connection totarget_host:target_port.Real use case: Your company’s PostgreSQL database runs on an internal server (
db.internal:5432) not exposed to the internet. You can access it from your laptop:ssh -L 5432:db.internal:5432 eslam@bastion.company.com psql -h localhost -p 5432 -U app_user my_db
Q4 — Advanced: What is a ProxyJump and how does it differ from the older ProxyCommand approach?
ProxyJump(SSH 7.3+) is a cleaner replacement for the olderProxyCommandwithssh -W. Both cause the SSH client to first connect to an intermediate host and then open a TCP connection from there to the target.ProxyJumphandles multi-hop chains natively and uses native SSH protocol operations internally, which is safer and more efficient. The old way:ProxyCommand ssh -W %h:%p bastionThe new way (equivalent but cleaner):
ProxyJump bastion
References
{:.gc-ref}
References
| Resource | Link |
|---|---|
man 1 ssh |
SSH client manual |
man 5 ssh_config |
Client config file format |
man 5 sshd_config |
Server config file format |
man 1 rsync |
rsync manual |
| OpenSSH documentation | openssh.com/manual.html |
| SSH Hardening Guide (Mozilla) | infosec.mozilla.org/guidelines/openssh |