Gendi Notes

SSH

SSH is how you reach every server you will ever manage — and once `~/.ssh/config` is set up, it becomes one short word instead of a long command.

Level 1 guide sshlinuxsecurity

What is it?#

SSH (Secure Shell) gives you an encrypted terminal on a remote machine. It also carries file transfers (scp, rsync), Git over SSH, and tunnels.

ssh ubuntu@203.0.113.10

How authentication works#

There are two ways in, and only one of them is acceptable on a server.

PASSWORD KEY PAIR ← use this ──────── ──────── you type a secret you hold a PRIVATE key (never leaves your machine) over the network server holds your PUBLIC key │ guessable, brute-forceable server sends a challenge shared, reusable you sign it with the private key server verifies with the public key │ the private key is never transmitted

The private key never travels. That is the whole point — nothing worth stealing crosses the network.

What it is made of#

PieceWherePurpose
Private key~/.ssh/id_ed25519 on your machineProves who you are. Never share it
Public key~/.ssh/id_ed25519.pubSafe to share. Goes on servers
authorized_keys~/.ssh/authorized_keys on the serverList of public keys allowed in
known_hosts~/.ssh/known_hosts on your machineServer fingerprints you have accepted
config~/.ssh/config on your machinePer-host settings. The quality-of-life file
AgentRunning in your sessionHolds unlocked keys so you type the passphrase once

How to use it#

1. Create a key#

Once per machine
# ed25519 — modern, short, fast. Use this unless something demands RSA.
# -C is just a comment so you can identify the key later.
ssh-keygen -t ed25519 -C "you@laptop"

# Accept the default path. Set a PASSPHRASE — it encrypts the key on disk,
# so a stolen laptop does not hand over your servers.

You now have two files. id_ed25519 is secret; id_ed25519.pub is not.

2. Put the public key on the server#

# The easy way — appends it to authorized_keys with correct permissions
ssh-copy-id ubuntu@203.0.113.10

# Manually, if ssh-copy-id is unavailable
cat ~/.ssh/id_ed25519.pub | ssh ubuntu@203.0.113.10 \
  "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

3. Set up ~/.ssh/config — do this early#

This is the difference between remembering long commands and typing one word.

~/.ssh/config
Host prod
    HostName 203.0.113.10
    User ubuntu
    IdentityFile ~/.ssh/prod_key
    # Offer ONLY that key. Without this, ssh presents every key in ~/.ssh in
    # turn and the server can refuse for too many auth attempts before
    # reaching the right one.
    IdentitiesOnly yes
    # Keep the connection alive through NAT/firewall idle timeouts
    ServerAliveInterval 60

Host github.com
    User git
    IdentityFile ~/.ssh/github_key
    IdentitiesOnly yes

# Reach a private machine THROUGH a bastion, in one command
Host db-private
    HostName 10.0.1.50
    User ubuntu
    ProxyJump prod

Now ssh prod is the whole command. And scp file prod:/tmp/ works too.

4. Permissions matter, and SSH enforces them#

chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519      # private key: owner only
chmod 644 ~/.ssh/id_ed25519.pub
chmod 600 ~/.ssh/authorized_keys # on the server

The agent — type your passphrase once#

eval "$(ssh-agent -s)"          # start it (most desktops do this already)
ssh-add ~/.ssh/id_ed25519       # unlock the key; enter the passphrase once
ssh-add -l                      # which keys are loaded

# Forward the agent so a remote machine can use YOUR keys (e.g. to git clone)
ssh -A prod

Copying files#

scp file.txt prod:/tmp/                 # to the server
scp prod:/var/log/app.log ./            # from the server
scp -r ./dist prod:/var/www/            # a directory

# rsync is better for anything repeated: only transfers differences
rsync -avz ./dist/ prod:/var/www/
rsync -avz --delete ./dist/ prod:/var/www/   # mirror exactly (removes extras)

Note the trailing slash on ./dist/ — with it you copy the contents, without it you copy the directory itself into the destination. This trips everyone up at least once.

Tunnels#

Reach a service that is not exposed to the internet:

# LOCAL forward: my localhost:5432 → the DB, as seen from the server
# Use it to point a local GUI at a private database.
ssh -L 5432:10.0.1.50:5432 prod

# Now: psql -h localhost -p 5432

# DYNAMIC: a SOCKS proxy through the server, for browsing internal sites
ssh -D 1080 prod
your laptop server private DB localhost:5432 ───ssh tunnel───→ prod ──────────→ 10.0.1.50:5432 │ psql -h localhost

This is the safe way to reach a private database: nothing new is exposed, and the connection is encrypted and authenticated by SSH.

Common problems#

SymptomCauseFix
Permission denied (publickey)Wrong key, or too many keys offeredIdentitiesOnly yes + explicit IdentityFile; check authorized_keys
Permissions 0644 are too openPrivate key readable by otherschmod 600
Connection refusedsshd not running, or wrong portCheck the service; -p 2222 if it is non-standard
Connection times outFirewall — often the cloud oneOpen port 22 in the cloud console and the host firewall
Host key verification failedServer's key changed (rebuild, or an attack)Verify it is expected, then ssh-keygen -R <host>
Connection drops when idleNAT/firewall idle timeoutServerAliveInterval 60
Slow to connect, then fineReverse-DNS lookup on the serverUseDNS no in sshd_config
ssh -v prod       # verbose: shows which keys are offered and what is rejected
ssh -vvv prod     # very verbose, when -v is not enough

ssh -v is the diagnostic command. The line Offering public key: ... followed by rejection tells you the server does not have that key.

Hardening a server#

/etc/ssh/sshd_config
PasswordAuthentication no      # keys only. The single biggest win
PermitRootLogin no             # log in as a user, then sudo
sudo sshd -t                      # TEST the config before applying
sudo systemctl reload sshd

Interview corner · الأسئلة اللي بتتسأل#

How does SSH key authentication work? Why is it better than a password?

The key point is that the private key never crosses the network.

The server holds your public key. On connection it sends a random challenge; you sign it with the private key; the server verifies the signature with the public key. An attacker watching the whole exchange sees a signature over a nonce that will never be reused.

PasswordKey pair
Crosses the networkYes (inside TLS, but it arrives)No
Brute-forceableYesNot realistically
Reused across systemsUsuallyEach server holds only a public key
Revoking one userChange the shared secretDelete one line from authorized_keys
Permission denied (publickey) — but the key is definitely in authorized_keys. What now?

Four causes, and they need different fixes. Start with ssh -v, which prints exactly which keys were offered and what the server did with each.

CauseTellFix
Too many keys offered-v shows 6 keys offered, then denialIdentitiesOnly yes + explicit IdentityFile
Permissions on the serversshd logs "bad ownership or modes"chmod 700 ~/.ssh; chmod 600 ~/.ssh/authorized_keys
Permissions on your keyPermissions 0644 are too openchmod 600
Wrong userYou are trying root@ on a host that forbids itUse the right user; PermitRootLogin no is common
ssh -v prod                          # which keys are offered, and rejected
sudo tail -50 /var/log/auth.log      # on the SERVER — the real reason
What is the security difference between ssh -A and ssh -J?

-A (agent forwarding) lends your credentials. -J (ProxyJump) tunnels the connection. The difference is who can act as you.

ssh -A bastion ssh -J bastion target ────────────── ───────────────────── your agent is reachable the bastion forwards FROM the bastion encrypted bytes only │ │ ↓ ↓ root on the bastion can the bastion cannot read sign challenges AS YOU, the session or use your to anything your keys open keys — end-to-end to target

With -A, anyone with root on the intermediate host can, while you are connected, use your agent to authenticate as you to GitHub, to other servers, to anything your keys open. They never obtain the key itself — they do not need it.

With -J, the bastion is a pipe. The SSH session is negotiated end-to-end with the final host, so the bastion sees only ciphertext.

Use ProxyJump. If you genuinely must forward an agent, scope it per host in ~/.ssh/config (ForwardAgent yes under one Host block only) — never globally.

Key takeaways#

  • Keys, never passwords. The private key never leaves your machine.
  • ~/.ssh/config turns long commands into ssh prod. Set it up early.
  • IdentitiesOnly yes fixes most "Permission denied (publickey)" errors.
  • chmod 600 on private keys — SSH refuses looser permissions.
  • Never keep keys in a cloud-synced folder.
  • ProxyJump, not agent forwarding, to reach machines through a bastion.
  • ssh -L to reach a private database safely.
  • ssh -v when authentication fails; it shows exactly which key was refused.

Last updated 2026-09-08