Senior 8 min · March 06, 2026
SSH and SCP Explained

SSH Permission Denied — chmod 777 Locked Our Deploy Team

All SSH key connections failed from a chmod 777 on ~/.ssh in production.

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.

Follow
Production
production tested
May 23, 2026
last updated
1,554
articles · all by Naren
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • SSH encrypts all traffic using public-key cryptography — your password never crosses the network in plain text.
  • SCP uses the same secure tunnel as SSH to copy files between machines.
  • Key-based authentication eliminates passwords and protects against brute-force attacks.
  • ED25519 keys are ~2x faster than RSA 4096 for authentication and more secure.
  • Biggest mistake: incorrect ~/.ssh permissions silently break key-based login — if you're remote, you're locked out until you fix via console.
✦ Definition~90s read
What is SSH and SCP?

SSH (Secure Shell) and SCP (Secure Copy) are the backbone of secure remote server management and file transfer in production environments. SSH is a cryptographic network protocol that establishes an encrypted tunnel between a client and a server, authenticating via passwords or, more commonly, public-key cryptography.

Imagine your server is a locked house on the other side of the world.

SCP runs over that SSH tunnel to copy files between machines. They exist because everything else—telnet, FTP, rsh—sends credentials and data in plaintext, which is a non-starter for any real-world deployment. When you chmod 777 your entire home directory or .ssh folder, you break SSH's strict permission checks: OpenSSH refuses to use keys that are world-readable, because that would defeat the entire security model.

That's exactly how our deploy team got locked out—the server saw the private key as compromised and rejected it outright, even though the key itself was valid.

Under the hood, SSH uses a client-server handshake where the server presents a host key (its identity), and the client proves its identity via a private key that never leaves the local machine. The server stores the corresponding public key in ~/.ssh/authorized_keys.

This asymmetric setup means you can ditch passwords entirely—no more typing credentials, no more brute-force attacks on login prompts. SCP piggybacks on this same authenticated channel, using the same keys and session encryption. For daily operations, you'll use ssh user@host to log in and scp file user@host:/path/ to transfer files.

But the real power move is the SSH config file (~/.ssh/config), where you define host aliases, default users, ports, and key paths—turning ssh -i ~/.ssh/prod-key.pem -p 2222 deploy@192.168.1.100 into just ssh prod-deploy.

Security-wise, SSH is only as strong as your key management and server hardening. Best practices include: using ed25519 keys over RSA (they're faster and more secure), disabling password authentication entirely in /etc/ssh/sshd_config (PasswordAuthentication no), setting PermitRootLogin prohibit-password (or no), and running SSH on a non-standard port to reduce log noise from bots.

Never set permissions looser than 600 on private keys or 700 on ~/.ssh—that chmod 777 mistake is a classic footgun that will lock you out instantly. For production deploys, consider using SSH agent forwarding sparingly (it's a security risk if the server is compromised) and prefer jump hosts or SSH tunnels for accessing private subnets.

Tools like ssh-audit can scan your server's SSH configuration for weaknesses, and services like Mozilla's SSH guidelines provide concrete, battle-tested settings.

Plain-English First

Imagine your server is a locked house on the other side of the world. SSH is the secure phone line you use to talk to the person inside and tell them what to do — nobody else can listen in. SCP is the same secure phone line, but instead of giving instructions, you're using it to send or receive packages (files). Both use the same lock-and-key system, so everything stays private and tamper-proof.

Every time a developer deploys code to a production server, backs up a database, or fixes a bug on a remote machine at 2am, there's one tool quietly making it possible: SSH. It's the backbone of modern DevOps, cloud computing, and Linux server management. If you're getting into any of those fields — or just want to stop being afraid of the terminal — SSH is the single most important skill to learn first.

Before SSH existed, people used tools like Telnet to connect to remote machines. The problem? Everything — including your password — was sent across the network as plain text. Anyone sniffing the network traffic could read it. SSH (Secure Shell) solved this by encrypting the entire connection. It's the difference between shouting your bank PIN across a crowded room and whispering it through a private encrypted tunnel that only you and the server can decode. SCP (Secure Copy Protocol) builds on that same tunnel to let you copy files between machines, so you're never transferring sensitive data in the open.

By the end of this article you'll know exactly how SSH works and why it's secure, how to connect to a remote Linux server from your terminal, how to set up SSH key-based authentication so you never type a password again, and how to use SCP to send and receive files like a pro. You'll also avoid the most common beginner mistakes that cause frustrating 'Permission denied' errors.

What SCP and SSH Actually Do — And Why chmod 777 Broke Our Deploy

SSH (Secure Shell) is a cryptographic network protocol for operating network services securely over an unsecured network. SCP (Secure Copy) is a file transfer protocol built on SSH that copies files between hosts using the same authentication and encryption. The core mechanic: SCP opens an SSH session, spawns a remote scp process, and pipes file data through an encrypted tunnel. No separate daemon — it piggybacks on sshd.

SCP works by invoking a remote scp command via SSH, which reads or writes files with the permissions of the authenticated user. The protocol itself has no built-in permission control — it relies entirely on the remote filesystem's Unix permissions. When you run scp file user@host:/path/, the remote scp process writes the file with the umask of the SSH session. If the target directory has 777 permissions, any user on the remote box can overwrite the file — a common source of supply-chain attacks in CI/CD pipelines.

Use SCP when you need a simple, one-off file copy to a server you control, and you trust the network path. Do not use SCP for automated deploys to shared or multi-tenant environments — use rsync over SSH with strict permission checks, or a dedicated deployment tool like Ansible or Fabric. In production, SCP's lack of atomic writes and permission validation makes it a liability for deploy pipelines. Our team learned this the hard way when a 777 directory allowed a compromised container to overwrite our deploy script.

SCP Is Not SFTP
SCP and SFTP are different protocols. SCP is deprecated in OpenSSH 8.8+ and lacks file-listing and resume capabilities — use SFTP or rsync for anything beyond a quick copy.
Production Insight
Production deploy pipeline: a shared /tmp directory with 777 permissions allowed any process on the box to overwrite the deploy artifact before the deploy script executed.
Symptom: intermittent deploy failures with 'Permission denied' on the final artifact, but only when another job ran concurrently.
Rule of thumb: never use 777 for any directory involved in a deploy pipeline — use 755 for directories, 644 for files, and verify with find /deploy -perm /o+w.
Key Takeaway
SCP inherits the remote user's umask and filesystem permissions — it does not enforce its own access control.
Never use SCP for automated deploys to shared or multi-tenant hosts — use rsync with --chmod or a dedicated deploy tool.
A 777 directory on a deploy server is a security hole that will eventually be exploited — audit permissions with find / -type d -perm 777.
SSH & SCP: Secure Access and File Transfer THECODEFORGE.IO SSH & SCP: Secure Access and File Transfer Flow from SSH authentication to SCP transfer with security warnings SSH Key Pair Generation Public/private keys for auth SSH Authentication Server verifies private key SSH Config File Host aliases save time SCP File Transfer Copy files over SSH Bastion Jump Host Proxy through intermediate server ⚠ chmod 777 locks out deploy teams Use 755 for directories, 644 for files THECODEFORGE.IO
thecodeforge.io
SSH & SCP: Secure Access and File Transfer
Ssh Scp Explained

How SSH Works: The Locked Door and the Secret Handshake

SSH uses a concept called public-key cryptography. Think of it like a padlock you can hand out freely to anyone. You keep the key to that padlock completely private. Someone can lock a box using your padlock (encrypt data with your public key), but only you can open it (decrypt it with your private key). This is the core idea behind SSH keys.

When you connect to a server over SSH, here's what actually happens in the background:

  1. Your client and the server agree on an encryption algorithm.
  2. The server proves its identity to you using its own key (this prevents 'man-in-the-middle' attacks where someone pretends to be your server).
  3. A unique session key is created just for this connection.
  4. All traffic from that point on is encrypted with that session key.

You authenticate using either a password (convenient but weaker) or an SSH key pair (a private key on your machine + a public key on the server). Key-based auth is what every professional uses because it's both more secure and more convenient — no typing passwords, and automated scripts can connect without human input.

The default port for SSH is 22. When you hear someone say 'open port 22 in the firewall', this is what they mean.

basic_ssh_connection.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# ─────────────────────────────────────────────────────────────
# BASIC SSH CONNECTION
# Syntax: ssh [username]@[server-address]
# ─────────────────────────────────────────────────────────────

# Connect to a remote server as user 'deploy' at IP 203.0.113.42
# SSH will prompt for the user's password if no key is configured
ssh deploy@203.0.113.42

# ─────────────────────────────────────────────────────────────
# CONNECT ON A NON-STANDARD PORT
# Some servers move SSH off port 22 for security (obscurity).
# Use the -p flag to specify a different port.
# ─────────────────────────────────────────────────────────────
ssh -p 2222 deploy@203.0.113.42

# ─────────────────────────────────────────────────────────────
# RUN A SINGLE COMMAND ON THE REMOTE SERVER WITHOUT STAYING LOGGED IN
# Useful in scripts — connect, run the command, disconnect immediately.
# Here we're checking available disk space on the remote machine.
# ─────────────────────────────────────────────────────────────
ssh deploy@203.0.113.42 'df -h /'

# ─────────────────────────────────────────────────────────────
# VERBOSE MODE — use -v to see exactly what SSH is doing
# Invaluable for debugging connection failures
# ─────────────────────────────────────────────────────────────
ssh -v deploy@203.0.113.42
Output
# After running: ssh deploy@203.0.113.42
The authenticity of host '203.0.113.42 (203.0.113.42)' can't be established.
ED25519 key fingerprint is SHA256:abc123XYZexampleFingerprint.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '203.0.113.42' (ED25519) to the list of known hosts.
deploy@203.0.113.42's password:
Welcome to Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-91-generic x86_64)
deploy@web-server-01:~$
# After running: ssh deploy@203.0.113.42 'df -h /'
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 50G 18G 30G 38% /
Watch Out: That 'Authenticity Can't Be Established' Message
The first time you connect to a server, SSH warns you it doesn't recognise it yet and shows you a fingerprint. Type 'yes' and SSH saves that fingerprint in ~/.ssh/known_hosts. Next time you connect, it checks the fingerprint matches. If it doesn't match on a future connection, SSH screams at you — this is intentional and means something changed on the server side (or someone is intercepting your connection). Never blindly ignore that warning.
Production Insight
If you ever see 'Host key verification failed' and you haven't rebuilt the server, someone could be intercepting your connection. Never bypass this warning by deleting the host key without verifying.
The first connection prompt is your only chance to validate server identity — use ssh-keygen -l -f to compare fingerprints out of band.
Rule: Always verify server fingerprints and never blindly accept changed host keys.
Key Takeaway
SSH uses asymmetric cryptography to establish a secure channel.
The host key verification protects against man-in-the-middle attacks.
Always verify server fingerprints on the first connection.

SSH Key Authentication: Ditch the Password Forever

Password authentication works, but it has real problems. Passwords can be brute-forced, forgotten, or accidentally logged. SSH keys are essentially a 4096-bit random string — impossible to guess. And once they're set up, connecting feels like magic: you just type ssh deploy@yourserver.com and you're in.

Here's how key-based auth works
  • You generate a key pair: a private key (stays on your laptop, never shared) and a public key (copied to the server).
  • When you connect, SSH uses cryptographic math to prove you possess the private key without ever transmitting it.
  • The server confirms the proof matches the public key it has stored for you.

Your private key lives in ~/.ssh/id_ed25519 (or id_rsa for older RSA keys). Your public key is ~/.ssh/id_ed25519.pub. The .pub file is the one you share freely. The private key file is the one you protect like a password — in fact, you can add an extra passphrase to it for a second layer of security.

ED25519 is the modern algorithm to use. It's faster and more secure than the older RSA algorithm. If you see old tutorials using ssh-keygen -t rsa, you can safely prefer ED25519 instead.

ssh_key_setup.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# ─────────────────────────────────────────────────────────────
# STEP 1: GENERATE AN SSH KEY PAIR ON YOUR LOCAL MACHINE
# -t ed25519       : use the modern ED25519 algorithm
# -C               : attach a comment so you know which key is which
# This creates two files:
#   ~/.ssh/id_ed25519      (PRIVATE key — never share this)
#   ~/.ssh/id_ed25519.pub  (PUBLIC key — copy this to servers)
# ─────────────────────────────────────────────────────────────
ssh-keygen -t ed25519 -C "alice@mycompany.com"

# You'll be prompted:
# Enter file in which to save the key (/home/alice/.ssh/id_ed25519): [press Enter]
# Enter passphrase (empty for no passphrase): [type a strong passphrase or press Enter]

# ─────────────────────────────────────────────────────────────
# STEP 2: COPY YOUR PUBLIC KEY TO THE REMOTE SERVER
# ssh-copy-id handles this safely — it appends your public key
# to ~/.ssh/authorized_keys on the server.
# The server will look at authorized_keys to decide who can log in.
# ─────────────────────────────────────────────────────────────
ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@203.0.113.42

# ─────────────────────────────────────────────────────────────
# STEP 3: TEST THE KEY-BASED CONNECTION
# If setup is correct, you'll connect without being asked for a password.
# ─────────────────────────────────────────────────────────────
ssh deploy@203.0.113.42

# ─────────────────────────────────────────────────────────────
# BONUS: IF ssh-copy-id ISN'T AVAILABLE (e.g. on Windows or minimal systems)
# Manually append your public key to the server's authorized_keys file.
# This does the same thing as ssh-copy-id in one piped command.
# ─────────────────────────────────────────────────────────────
cat ~/.ssh/id_ed25519.pub | ssh deploy@203.0.113.42 \
  'mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys'

# ─────────────────────────────────────────────────────────────
# VIEW YOUR PUBLIC KEY (safe to copy-paste anywhere)
# ─────────────────────────────────────────────────────────────
cat ~/.ssh/id_ed25519.pub
Output
# After running ssh-keygen:
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/alice/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/alice/.ssh/id_ed25519
Your public key has been saved in /home/alice/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:Zd8Kp3mNexampleFingerprint alice@mycompany.com
The key's randomart image is:
+--[ED25519 256]--+
| .+o. |
| o+* |
+----[SHA256]-----+
# After running ssh-copy-id:
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/alice/.ssh/id_ed25519.pub"
deploy@203.0.113.42's password:
Number of key(s) added: 1
Now try logging into the machine with: ssh 'deploy@203.0.113.42'
# After running ssh deploy@203.0.113.42 (key-based — no password prompt!):
Welcome to Ubuntu 22.04.3 LTS
deploy@web-server-01:~$
# After running cat ~/.ssh/id_ed25519.pub:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExamplePublicKeyDataHere alice@mycompany.com
Pro Tip: Use the SSH Config File to Save Typing
Instead of typing ssh deploy@203.0.113.42 -p 2222 -i ~/.ssh/id_ed25519 every time, create a shortcut in ~/.ssh/config. Add: Host myserver\n HostName 203.0.113.42\n User deploy\n Port 2222\n IdentityFile ~/.ssh/id_ed25519. Now you just type ssh myserver. This also works with SCP: scp myserver:/var/log/app.log ./.
Production Insight
If you accidentally run chmod -R 777 ~ on your local machine, SSH will refuse to use your private key. You'll be locked out of every server until you fix permissions.
On the server, if authorized_keys has group-writable permissions, SSH silently ignores it — even with the correct key.
Rule: Set ~/.ssh to 700 and authorized_keys to 600, and never change them.
Key Takeaway
Key-based auth is more secure and convenient than passwords.
ED25519 is the recommended key type; use ssh-copy-id to install public keys.
File permission mistakes are the #1 cause of key auth failures.

SCP: Copying Files Over SSH Like a Pro

SCP (Secure Copy Protocol) uses the SSH connection you already understand to copy files between machines. The syntax looks a bit odd at first, but once you see the pattern, it clicks immediately.

The golden rule of SCP syntax: scp [source] [destination]. For remote paths, you prefix them with username@host:. That colon is the signal that says 'this path is on a remote machine'.

So scp alice@server:/var/log/app.log ./ means: copy the file /var/log/app.log from the remote server (as user alice) to my current local directory. And scp ./backup.tar.gz alice@server:/tmp/ means: copy my local backup.tar.gz file up to the /tmp/ directory on the remote server.

SCP preserves file permissions and timestamps by default when you use the -p flag. For entire directories, add -r (recursive) — just like cp -r for local copies.

One thing to know: SCP is being quietly deprecated on some modern systems in favour of rsync or sftp for large transfers, because those have better progress reporting and can resume interrupted transfers. But SCP is still everywhere, still reliable for one-off file transfers, and still the tool you'll use and see most often as a beginner.

scp_file_transfer.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# ─────────────────────────────────────────────────────────────
# SCP SYNTAX PATTERN:
# scp [options] [source] [destination]
# Remote path format: username@hostname:/path/to/file
# ─────────────────────────────────────────────────────────────

# ── UPLOAD: Copy a local file TO a remote server ─────────────
# Copy a compiled app package from your local machine
# up to the /var/www/releases/ directory on the production server
scp ./myapp-v2.1.0.tar.gz deploy@203.0.113.42:/var/www/releases/

# ── DOWNLOAD: Copy a file FROM a remote server to your machine ──
# Pull today's application log file from the server
# into your current local directory (./ means here)
scp deploy@203.0.113.42:/var/log/myapp/app-2024-01-15.log ./

# ── RECURSIVE: Copy an entire directory ──────────────────────
# -r means recursive (copy the folder and everything inside it)
# Upload a whole config directory to the server
scp -r ./nginx-configs/ deploy@203.0.113.42:/etc/nginx/sites-available/

# ── PRESERVE PERMISSIONS AND TIMESTAMPS ──────────────────────
# -p preserves the original file's modification time and permissions
# Useful when timestamps matter (e.g. log rotation scripts)
scp -p ./database-backup.sql deploy@203.0.113.42:/backups/

# ── SPECIFY A CUSTOM SSH KEY ──────────────────────────────────
# If you have multiple keys, tell SCP which identity file to use
# -i points to your private key
scp -i ~/.ssh/id_ed25519 ./config.yaml deploy@203.0.113.42:/app/config/

# ── NON-STANDARD PORT ─────────────────────────────────────────
# NOTE: SCP uses capital -P for port (unlike SSH which uses lowercase -p)
# This is one of the most common beginner typos!
scp -P 2222 ./deploy.sh deploy@203.0.113.42:/opt/scripts/

# ── COPY BETWEEN TWO REMOTE SERVERS (from your local machine) ─
# Your machine acts as the coordinator.
# Copy a file from server-A directly to server-B.
scp deploy@server-a.example.com:/var/exports/data.csv \
    deploy@server-b.example.com:/var/imports/

# ── SHOW PROGRESS FOR LARGE FILES ────────────────────────────
# -v (verbose) shows progress detail
# For a cleaner progress bar, use: rsync --progress (see Pro Tip below)
scp -v ./large-video-export.mp4 deploy@203.0.113.42:/media/uploads/
Output
# After running the upload command:
myapp-v2.1.0.tar.gz 100% 4823KB 3.2MB/s 00:01
# After running the download command:
app-2024-01-15.log 100% 1247KB 2.1MB/s 00:00
# After running the recursive directory copy:
nginx-default.conf 100% 2KB 1.8MB/s 00:00
nginx-ssl.conf 100% 4KB 2.0MB/s 00:00
nginx-proxy.conf 100% 1KB 1.5MB/s 00:00
# After running the non-standard port command:
deploy.sh 100% 512 0.5MB/s 00:00
# Typical error if you forget the capital -P with SCP:
ssh: connect to host 203.0.113.42 port 22: Connection refused
lost connection
Interview Gold: Why Does SCP Use -P (Capital) and SSH Use -p (Lowercase)?
This is a historical inconsistency that's tripped up every developer at least once. SSH uses lowercase -p for port, while SCP uses uppercase -P. The reason is that SCP already uses lowercase -p for 'preserve file attributes' (timestamps and permissions). Since both flags were needed and the tools were designed separately, the port flag ended up capitalised in SCP. Knowing this and explaining it clearly in an interview signals real hands-on experience.
Production Insight
SCP uses capital -P for port. Using lowercase -p on SCP will try port 22 and fail silently if the server uses a non-standard port — you'll get no error when copying to a wrong directory.
Also, SCP doesn't show progress by default; use -v or switch to rsync with --progress for large transfers.
Rule: Double-check the port flag (capital P) and test with a dummy file first.
Key Takeaway
SCP copies files over SSH: scp [source] [destination] with user@host: prefix.
Use -r for directories, -p to preserve attributes, -P (capital) for custom port.
For large transfers, prefer rsync over SCP for resume and progress.

SSH Config File: Save Time with Host Aliases

If you routinely SSH into multiple servers with different usernames, ports, and keys, typing the full command each time becomes tedious. The SSH config file (~/.ssh/config) lets you define named hosts with all those details pre-configured.

Each host block starts with Host somealias, then indented options like: - HostName: the actual server hostname or IP - User: the remote username - Port: non-standard port - IdentityFile: which private key to use - LocalForward: port forwarding (advanced)

Once defined, you connect with just ssh somealias. SCP also respects these aliases: scp somealias:/remote/path ./ works too.

Configuration is read in order; the first matching Host pattern is used. Wildcards like Host *.example.com allow grouping servers.

ssh_config_exampleBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# ─────────────────────────────────────────────────────────────
# EXAMPLE ~/.ssh/config FILE
# Each section starts with 'Host', followed by the alias you type.
# ─────────────────────────────────────────────────────────────

Host prod-web
    HostName 203.0.113.42
    User deploy
    Port 2222
    IdentityFile ~/.ssh/id_ed25519_prod

Host staging-*
    HostName staging.example.com
    User admin
    IdentityFile ~/.ssh/id_ed25519_staging

# Default for all hosts (applied if no specific match)
Host *
    ServerAliveInterval 60
    TCPKeepAlive yes
    StrictHostKeyChecking ask

# After saving, you can run:
#   ssh prod-web
#   scp prod-web:/var/log/app.log ./
#   ssh staging-web1 (matches staging-* pattern)
Production Insight
A misconfigured config file with a missing Host line or wrong IdentityFile can cause SSH to use the wrong key (or none). Run ssh -G hostname to see the effective configuration before connecting.
Also, SSH config is read in order; first matching Host is used — order matters. If you have overlapping patterns, the first match wins.
Config file must have 600 permissions; SSH ignores world-readable configs.
Key Takeaway
~/.ssh/config saves typing: define Host aliases with User, Port, IdentityFile.
Use ssh -G hostname to debug resolved options.
Config file must have 600 permissions; order of host blocks matters.

SSH Security Best Practices: Protecting Your Server and Keys

Once you have SSH access to a server, locking it down is critical. The most important steps:

  1. Disable root login: Prevent attackers from directly SSHing as root. Edit /etc/ssh/sshd_config: PermitRootLogin no. Use a regular user with sudo instead.
  2. Disable password authentication: Rely only on key-based auth. Set PasswordAuthentication no in sshd_config.
  3. Change the default SSH port (optional): Moving from port 22 to something like 2222 reduces automated attack traffic dramatically.
  4. Use ED25519 keys: They are faster and more secure than RSA. Replace old RSA keys.
  5. Install fail2ban: Blocks IPs after repeated failed login attempts, reducing brute-force risk.
  6. Regularly audit authorized_keys: Remove unused public keys.
  7. Use SSH certificates for teams (advanced): Instead of distributing public keys, use a certificate authority to sign temporary host keys.

Always test changes by keeping a second SSH session open so you don't lock yourself out.

SshClient.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package io.thecodeforge.ssh;

import com.jcraft.jsch.*;
import java.io.InputStream;

public class SshClient {
    private final Session session;

    public SshClient(String host, int port, String user, String privateKeyPath) throws JSchException {
        JSch jsch = new JSch();
        jsch.addIdentity(privateKeyPath);
        session = jsch.getSession(user, host, port);
        session.setConfig("StrictHostKeyChecking", "ask");
        session.connect();
    }

    public String executeCommand(String command) throws JSchException, IOException {
        Channel channel = session.openChannel("exec");
        ((ChannelExec) channel).setCommand(command);
        InputStream in = channel.getInputStream();
        channel.connect();
        byte[] tmp = new byte[1024];
        StringBuilder output = new StringBuilder();
        while (true) {
            int i = in.read(tmp, 0, 1024);
            if (i < 0) break;
            output.append(new String(tmp, 0, i));
        }
        channel.disconnect();
        return output.toString();
    }

    public void disconnect() {
        session.disconnect();
    }
}
Production Insight
Disabling password authentication and root login are the first two steps in any server hardening checklist. If you lock yourself out, ensure you have a console or management network access.
Changing the SSH port reduces automated attack noise by ~99% from my production logs, but it's not a security measure in itself — combine with key-only and fail2ban.
Rule: Hardening SSH is the cheapest and most effective server security investment.
Key Takeaway
Hardening: disable root login, disable password auth, use ed25519 keys, change default port, and implement fail2ban.
Always maintain a backup access method (console or management network).
SSH hardening is the first line of defense against server compromise.

SCP Through a Bastion: The Jump Host Dance

You don't SSH directly into production. No one who's survived a breach does. You land on a bastion host — a hardened jump box — and from there you pivot to the real server. SCP doesn't natively support this, so most people copy the file to the bastion first, then scp again. That's slow, writes intermediate garbage, and leaves secrets sitting on the jump box's disk.

Use scp -o ProxyJump instead. It tunnels the file transfer through the bastion in one encrypted hop. No intermediate files. No stale data on the jump host. Your security team won't have to send you a Slack about 'unauthorized file staging' on the bastion.

The syntax is simple: scp -o ProxyJump=bastion-user@bastion-host local-file target-user@target-host:/path/. SSH does the handshake through the bastion automatically. This works because SCP is just SSH under the hood — the same -o flags for ProxyJump that work with ssh work with scp.

If you're running a modern version of OpenSSH, you can also use the -J shorthand: scp -J bastion-user@bastion-host .... Same result, less typing. Your .ssh/config can even define the jump host per server, but that's a power move for later.

JumpHostTransfer.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// io.thecodeforge — devops tutorial

# Transfer config.yaml from local to production via bastion
# Without leaving a temp copy on the jump host
local:
  file: /etc/nginx/conf.d/api-prod.yaml
bastion:
  user: jumpadmin
  host: bastion.acmecorp.net
target:
  user: deployer
  host: 10.0.1.50
  path: /etc/nginx/conf.d/

# Actual command:
scp -o ProxyJump=jumpadmin@bastion.acmecorp.net \
    /etc/nginx/conf.d/api-prod.yaml \
    deployer@10.0.1.50:/etc/nginx/conf.d/
Output
api-prod.yaml 100% 342KB 1.2MB/s 00:00
Production Trap:
If you copy files through a bastion in two steps, the intermediate file inherits the bastion's permissions. That file is now readable by anyone who compromises the jump box. Always use ProxyJump to keep the transfer in-memory and encrypted end-to-end.
Key Takeaway
Never stage files on a bastion — use ProxyJump to keep the transfer encrypted from your laptop straight to the target server.

Copying Directories Without Losing Your Mind

SCP can copy whole directories, but the default behavior will wreck your day. scp -r pulls everything — including broken symlinks, hidden dotfiles you didn't mean to ship, and .git directories that balloon the transfer size. I've seen a junior copy a 2GB node_modules directory across a datacenter because -r doesn't discriminate.

Use -rp for recursive with permission preservation. Then pair it with --exclude if you're using rsync. Wait — SCP doesn't have --exclude. That's why we sometimes drop down to rsync -avz -e ssh for directory transfers. But if you must use SCP, at least structure your source path with a trailing slash: scp -rp ./build/ server:/var/www/. The trailing slash copies the contents, not the folder itself, which is usually what you want.

For selective transfers, pipe through tar and SSH: tar czf - ./build | ssh server 'tar xzf - -C /var/www/'. This compresses on the fly and doesn't write to disk. It's not SCP, but it's the same SSH channel and it's faster for large trees.

Also: never scp -r /etc/ssh from an old server to a new one. You'll copy your host keys and break client trust. Copy configs, not identity.

DirectoryTransfer.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// io.thecodeforge — devops tutorial

# Transfer a production build directory, preserving permissions
# Notice trailing slash on source — copies contents, not folder
source_dir: /home/ci/build-245/
target: deployer@app-server-01.prod.acme.net
path: /var/www/app/

# Good — trailing slash, permissions preserved
scp -rp /home/ci/build-245/ deployer@app-server-01.prod.acme.net:/var/www/app/

# Bad — no trailing slash, copies the folder ITSELF:
# Result: /var/www/app/build-245/
scp -rp /home/ci/build-245 deployer@app-server-01.prod.acme.net:/var/www/app/

# Faster for large trees — tar over SSH (not SCP, but same tunnel)
tar czf - /home/ci/build-245/ | ssh deployer@app-server-01.prod.acme.net 'tar xzf - -C /var/www/app/'
Output
No output for large transfers — trust the exit code. Non-zero means check port 22 connectivity.
Senior Shortcut:
The trailing slash rule: with it, you get contents. Without it, you get the directory itself. Use scp -rp source/ target:path/ and it behaves like cp -r source/. target/. This saves you one recursive delete when you inevitably botch it.
Key Takeaway
Always use trailing slashes on source directories with SCP recursive — or use tar over SSH for selective, compressed directory transfers.
● Production incidentPOST-MORTEMseverity: high

Deploy Team Locked Out After Server Patching

Symptom
All SSH key-based connections failed with 'Permission denied (publickey)'. Password auth was also disabled for security policy.
Assumption
The patching script preserved existing file permissions.
Root cause
The security baseline script ran chmod -R 777 ~/.ssh as a temporary debugging step that was accidentally left in the production playbook.
Fix
Boot server into rescue mode via cloud console, mount the root volume, correct permissions with chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys, and reboot. Add Puppet guard to prevent permission changes on ~/.ssh.
Key lesson
  • Treat ~/.ssh as immutable infrastructure — never allow automated permission changes without explicit review.
  • Always maintain a console or management network access to handle permission lockouts.
  • Use configuration management testing to catch destructive operations before they hit production.
Production debug guideWhat to check when 'ssh' won't connect4 entries
Symptom · 01
Permission denied (publickey)
Fix
Check server-side ~/.ssh/authorized_keys permissions (must be 600) and ~/.ssh/ directory (must be 700). Verify the correct key is offered: ssh -v lists offered keys.
Symptom · 02
Connection timeout
Fix
Verify SSH port (default 22) is open in security group/firewall. Test with nc -zv <host> 22.
Symptom · 03
Host key verification failed
Fix
Remove old host key with ssh-keygen -R <host>, then reconnect after verifying server identity.
Symptom · 04
Permission denied (password)
Fix
Ensure password authentication is enabled in /etc/ssh/sshd_config (PasswordAuthentication yes). Check if you have the correct username.
★ SSH Quick Debug Cheat SheetCommands to diagnose and fix the most common SSH issues in under 60 seconds.
SSH asks for password even after key setup
Immediate action
Run ssh -v to see which keys are being offered and why they're rejected.
Commands
ssh -v user@host 2>&1 | grep -E '(Offering|Authenticated|Permission denied)'
chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys
Fix now
Run permissions fix on the server side (remotely with a separate admin key or VM console).
Connection refused+
Immediate action
Check if SSH service is running and accessible.
Commands
nc -zv host 22
systemctl status sshd
Fix now
If not running: sudo systemctl start sshd; if port filtered: adjust security group/firewall.
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED+
Immediate action
Verify server identity, then remove old key.
Commands
ssh-keygen -R hostname_or_ip
ssh user@host (to accept new key)
Fix now
After verification, accept new key. If still issues, check for MITM attack.
SSH vs SCP Comparison
Feature / AspectSSH (Secure Shell)SCP (Secure Copy Protocol)
Primary purposeInteractive remote terminal accessNon-interactive file transfer between machines
Protocol layerEncrypted tunnel (SSH protocol)Runs on top of the SSH protocol
Authentication methodPassword or SSH key pairSame as SSH — shares its auth mechanism
Port used22 (default)22 (default — same as SSH)
Transfers files?No — use SCP or rsync for filesYes — that is its entire purpose
Runs remote commands?Yes — interactive shell or single commandsNo — file transfer only
Recursive directory copyN/A (not its job)Yes — use the -r flag
Resume interrupted transferN/ANo — use rsync for resumable transfers
Port flagLowercase -p (e.g. -p 2222)Uppercase -P (e.g. -P 2222)
Best used whenYou need a shell session on a remote serverYou need to quickly copy a file to/from a server
Modern alternativeStill the gold standardrsync or sftp for large/resumable transfers

Key takeaways

1
SSH encrypts your entire connection using public-key cryptography
your password or private key is never transmitted across the network in readable form.
2
SSH key pairs work like a padlock you give out freely (public key on the server) and a unique key only you hold (private key on your machine)
the math proves you own the key without revealing it.
3
SCP uses capital -P for port and lowercase -p for preserving file attributes
the exact opposite of SSH, which uses lowercase -p for port. This trips up everyone the first time.
4
Incorrect file permissions on ~/.ssh/ (should be 700) or ~/.ssh/authorized_keys (should be 600) silently break key-based auth
SSH ignores key files that are too permissive as a security measure.
5
Use ~/.ssh/config for host shortcuts and always verify SSH fingerprints on first connection.

Common mistakes to avoid

3 patterns
×

Wrong file permissions on ~/.ssh or authorized_keys

Symptom
SSH falls back to asking for a password even after key setup, or gives 'Permission denied (publickey)'.
Fix
Run chmod 700 ~/.ssh and chmod 600 ~/.ssh/authorized_keys on the server. Also on your local machine: chmod 600 ~/.ssh/id_ed25519. SSH silently ignores key files with overly permissive permissions.
×

Using lowercase -p instead of uppercase -P with SCP

Symptom
SCP ignores the port number and tries port 22, giving 'Connection refused' on servers using a custom SSH port.
Fix
Always use capital -P for port in SCP commands (e.g., scp -P 2222 ...). Remember: SCP uses lowercase -p for 'preserve', so Port had to become uppercase.
×

Forgetting the colon (:) in SCP remote paths

Symptom
Instead of copying from a remote server, SCP creates a local file or directory named literally 'user@host/path/to/file' in your current directory.
Fix
Always include the colon immediately after the hostname: scp deploy@server:/path/file ./. Without colon, SCP treats it as a local path.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
What is the difference between SSH password authentication and SSH key-b...
Q02SENIOR
If you run `ssh-copy-id` to set up key authentication but SSH still asks...
Q03SENIOR
A junior developer accidentally runs `chmod 777 ~/.ssh` on their server ...
Q01 of 03SENIOR

What is the difference between SSH password authentication and SSH key-based authentication, and why would you prefer one over the other in a production environment?

ANSWER
Password authentication transmits a hashed password over the encrypted tunnel but is vulnerable to brute-force attacks if weak passwords are used. Key-based authentication uses a cryptographic pair; the private key never leaves the client, eliminating password transmission and brute-force risk. In production, key-only authentication is preferred because it is stronger and can be automated without storing passwords. Additionally, SSH keys can be revoked by removing the public key from authorized_keys.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
What is the difference between SSH and SCP?
02
Is it safe to share my SSH public key?
03
What is the ~/.ssh/known_hosts file for?
04
Should I use SCP or rsync for file transfers?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.

Follow
Verified
production tested
May 23, 2026
last updated
1,554
articles · all by Naren
🔥

That's Linux. Mark it forged?

8 min read · try the examples if you haven't

Previous
Linux Networking Commands
9 / 12 · Linux
Next
vim Editor Basics