Tired of typing passwords every time you SSH into a server? Need to push files to production but scp feels painfully slow? Want to access a remote dev server from your local machine?
The answer is ssh and rsync. ssh provides encrypted remote connections, and rsync handles efficient file synchronization by transferring only what's changed. Together, they form the foundation of server management and deployment workflows.
This guide walks you through key authentication setup, config file tuning, port forwarding, and rsync backup patterns — all with ready-to-run commands.
What is SSH?
ssh (Secure Shell) is a protocol and command for securely operating remote machines over a network. All communication is encrypted, so passwords and commands never travel in plaintext.
The predecessor, telnet, sent everything unencrypted. ssh replaced it in 1995 and has become the de facto standard for server administration.
ssh does far more than just remote login:
- Remote command execution: run one-liners like
ssh user@server "ls -la /var/log" - File transfer: encrypted transfers via scp and sftp
- Port forwarding: create tunnels between local and remote ports
- Tunneling: use it as a SOCKS proxy for VPN-like functionality
Basic connection is straightforward:
ssh user@192.168.1.100
To specify a port, use -p:
ssh -p 2222 user@192.168.1.100
Setting up key authentication
ssh supports two main authentication methods: password-based and key-based.
Password authentication requires entering a password for every connection. It's simple but weak — vulnerable to brute-force attacks, and a leaked password means game over.
Key authentication uses a public-private key pair. Without the private key, login is impossible, making brute-force attacks ineffective. For production servers, key auth is essentially mandatory.
Generating keys
The 2026 standard is Ed25519. It provides equal or better security than RSA with shorter key lengths, and faster signing and verification.
ssh-keygen -t ed25519 -C "your_email@example.com"
You'll be prompted for a save location and passphrase. Setting a passphrase is strongly recommended — it's the last line of defense if your private key is ever compromised.
If you need RSA for compatibility with older servers, use at least 4096 bits:
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
Copying your public key to the server
The easiest method is ssh-copy-id:
ssh-copy-id user@server
This appends your local public key (~/.ssh/id_ed25519.pub) to the server's ~/.ssh/authorized_keys.
If ssh-copy-id isn't available, do it manually:
cat ~/.ssh/id_ed25519.pub | ssh user@server "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
Setting permissions
ssh is strict about permissions. Key auth may be rejected if these aren't set correctly:
# Server side
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
# Client side
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
Managing keys with ssh-agent
If you set a passphrase on your key, entering it for every connection gets tedious. Register your key with ssh-agent to skip passphrase prompts for the duration of your session:
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
The ssh config file
~/.ssh/config stores connection parameters so you don't have to type them every time.
Basic syntax
Host myserver
HostName 192.168.1.100
User deploy
Port 2222
IdentityFile ~/.ssh/id_ed25519
Now ssh myserver is equivalent to ssh -p 2222 -i ~/.ssh/id_ed25519 deploy@192.168.1.100.
Wildcards
Apply common settings to multiple servers:
Host *.example.com
User admin
IdentityFile ~/.ssh/id_ed25519_work
Keep-alive
Prevent idle SSH connections from being dropped:
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
This sends a keep-alive signal every 60 seconds. If the server fails to respond 3 times, the connection is terminated.
Multi-hop SSH (ProxyJump)
Connecting through a bastion host to reach internal servers is a common pattern. ProxyJump handles it in a single command:
Host bastion
HostName bastion.example.com
User admin
Host internal
HostName 10.0.0.5
User deploy
ProxyJump bastion
ssh internal automatically routes through the bastion to reach the internal server.
Connection multiplexing (ControlMaster)
Speed up subsequent connections to the same server. The first connection creates a socket, and later connections reuse it:
Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
Create the socket directory first:
mkdir -p ~/.ssh/sockets
ControlPersist 600 keeps the socket alive for 600 seconds after the last connection closes. This makes a noticeable difference in deployment scripts that connect to the same server repeatedly.
Port forwarding
SSH port forwarding creates encrypted tunnels to relay traffic between ports. It's invaluable for accessing services behind firewalls or exposing local services externally.
Local forward (-L)
Forward a remote port to your local machine. This is the most common pattern:
ssh -L 8080:localhost:3000 user@server
Access localhost:8080 on your machine, and traffic is forwarded to localhost:3000 on the remote server. Use this when you need to reach a remote dev server or admin dashboard from your local browser.
Practical example: connecting to a remote PostgreSQL instance
ssh -L 5433:localhost:5432 user@db-server
In another terminal:
psql -h localhost -p 5433 -U myuser mydb
You can connect to the remote PostgreSQL as if it were running locally. Combine with tmux for managing multiple terminals.
Remote forward (-R)
Expose a local port through the remote server:
ssh -R 8080:localhost:3000 user@server
Access localhost:8080 on the remote server, and traffic is forwarded to localhost:3000 on your machine. Useful for showing a local development app to your team.
Dynamic forward (-D)
Create a SOCKS proxy:
ssh -D 1080 user@server
Set localhost:1080 as your browser's SOCKS proxy, and all traffic routes through the SSH tunnel. Handy for encrypting traffic on public Wi-Fi or accessing region-restricted services.
Running tunnels in the background
If you only need the forwarding and don't need a shell, use -fN:
ssh -fNL 8080:localhost:3000 user@server
-f runs ssh in the background, -N tells it not to execute any remote commands.
What is rsync?
rsync is a file synchronization tool specialized in delta transfers. It detects which parts of files have changed and transfers only those parts, making subsequent syncs dramatically faster.
Here's how it compares to scp:
| Feature | scp | rsync |
|---|---|---|
| Transfer mode | Full copy every time | Delta only |
| Resume after interruption | No | Yes, with --partial |
| Compression | No | Yes, with -z |
| Exclusion patterns | No | Flexible --exclude |
| Mirroring | No | Yes, with --delete |
For any non-trivial file transfer, rsync is the clear winner.
Basic syntax:
rsync [options] source destination
Commonly used options:
-a(archive): recursive copy preserving permissions, timestamps, and ownership-v(verbose): show file names during transfer-z(compress): compress transfer data-n(dry-run): show what would happen without actually transferring--progress: display transfer progress
rsync in practice
Basic file sync
Local to remote:
rsync -avz ./dist/ user@server:/var/www/
Remote to local:
rsync -avz user@server:/var/log/ ./logs/
Always do a dry run first. Just add -n:
rsync -avzn ./dist/ user@server:/var/www/
Exclusion patterns
Exclude specific files or directories from transfer:
rsync -avz --exclude='node_modules' --exclude='.git' --exclude='.env' ./project/ user@server:/app/
When you have many exclusions, put them in a file. Create .rsyncignore:
node_modules
.git
.env
*.log
.DS_Store
Then reference it with --exclude-from:
rsync -avz --exclude-from='.rsyncignore' ./project/ user@server:/app/
Backups
Mirror backup keeps the destination an exact copy of the source. --delete removes files from the backup that no longer exist in the source:
rsync -avz --delete /data/ /backup/data/
Generational backup saves deleted or modified files to a dated directory:
rsync -avz --backup --backup-dir="/backup/$(date +%Y%m%d)" --delete /data/ /backup/current/
/backup/current/ always contains the latest state, while deleted and updated files are preserved in directories like /backup/20260308/.
Deployment script
A practical example of automating web app deployment with rsync:
#!/bin/bash
set -euo pipefail
REMOTE_USER="deploy"
REMOTE_HOST="server.example.com"
REMOTE_PATH="/var/www/myapp"
LOCAL_PATH="./dist/"
echo "Starting deployment to ${REMOTE_HOST}..."
rsync -avz --delete \
--exclude='.git' \
--exclude='node_modules' \
--exclude='.env' \
--exclude='*.log' \
"${LOCAL_PATH}" "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/"
echo "Deployment complete."
set -euo pipefail is essential for script error handling — it stops execution as soon as any command fails.
Security considerations
SSH security and server security go hand in hand. Here's the minimum you should be doing.
Key changes in OpenSSH 10.0
OpenSSH 10.0 (April 2025) introduced significant security changes:
- DSA completely removed: DSA keys can no longer be generated or used. Migrate to Ed25519
- ML-KEM support: post-quantum key exchange algorithm (ML-KEM) enabled by default, providing preemptive protection against quantum computing threats
CVE-2025-26465 (MITM attack)
A vulnerability in OpenSSH 9.9p1 and earlier allowed man-in-the-middle attacks when VerifyHostKeyDNS was enabled. Fixed in 9.9p2 and later. If you use VerifyHostKeyDNS, check your version.
Recommended sshd_config settings
Configure /etc/ssh/sshd_config on the server side:
Only set PasswordAuthentication no after key auth is fully working. Doing it before locks you out of the server.
rsync security
rsync 3.4.0 (January 2025) fixed multiple critical CVEs. If you're running 3.3.x or earlier, update immediately. Version 3.4.1 is a regression fix release, so 3.4.1 is the recommended target.
Check your version:
rsync --version
Firewall and fail2ban
Restrict SSH port access (default: 22) by IP:
# Using ufw
sudo ufw allow from 203.0.113.0/24 to any port 22
sudo ufw deny 22
fail2ban detects failed login attempts and automatically blocks offending IPs. It's the standard defense against SSH brute-force attacks:
sudo apt install fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Wrapping Up
ssh and rsync are the foundation of remote server work. Authenticate securely with keys, streamline connections with config files, and sync files efficiently with rsync.
Key takeaways:
- Use Ed25519 keys. DSA was removed in OpenSSH 10.0
- Centralize connection parameters in
~/.ssh/config. UseProxyJumpfor multi-hop connections - Port forwarding lets you access remote services from localhost
- rsync basics:
-avzfor most operations. Always dry-run with-nfirst - The trailing slash changes rsync behavior entirely — verify it until it's muscle memory
- On the server side, set
PasswordAuthentication noandPermitRootLogin no
Pair ssh with tmux to make remote terminal work significantly more comfortable. For the full CLI toolkit overview, see CLI Toolkit.