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 (see our chmod & chown guide for a deeper dive into Linux permissions):
# 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. For more on writing robust shell scripts, check out our shell scripting guide.
Advanced techniques
SSH agent forwarding (ForwardAgent)
When you need to SSH from a bastion host to an internal server, you shouldn't store private keys on the bastion. Agent forwarding lets internal servers use your local key through the tunnel.
# ~/.ssh/config
Host bastion
HostName bastion.example.com
User admin
ForwardAgent yes
Host internal-*
User deploy
ProxyJump bastion
ForwardAgent yes
# Connect through bastion to internal, then git pull works
ssh internal-web
git pull # your local key is forwarded, so authentication succeeds
Security note: Only set ForwardAgent yes for servers you trust. Anyone with root access on the bastion can access your agent socket and use your keys. Never set it globally with Host * — specify it explicitly per host.
rsync resume and progress (-P)
When transferring large files over unreliable connections, -P lets you resume from where you left off.
# -P is shorthand for --partial --progress
rsync -avzP user@server:/data/large-dump.sql.gz ./backups/
--partial keeps partially transferred files instead of deleting them (the default). The next rsync run transfers only the remaining portion. --progress shows real-time transfer progress.
# Sync large files with partial files stored in a hidden directory
rsync -avzP --partial-dir=.rsync-partial user@server:/data/ ./local-data/
--partial-dir stores incomplete files in a separate directory, automatically cleaned up after transfer completes. Keeps your working directory clean.
rsync bandwidth limiting (--bwlimit)
Prevent large transfers from saturating the network on production servers.
# Limit bandwidth to 5MB/s
rsync -avz --bwlimit=5000 ./release/ user@production:/app/
# Go easy during business hours
rsync -avz --bwlimit=1000 ./data/ user@server:/backup/
--bwlimit is in KBytes/s. 5000 = roughly 5MB/s. Adding this to deploy scripts prevents the "the site slowed down after deploy" problem.
Mount remote filesystems with sshfs
Mount a remote server's directory locally and work with files as if they were on your machine. sshfs uses SFTP under the hood.
# Mount remote directory
mkdir -p ~/mnt/server
sshfs user@server:/var/www ~/mnt/server
# Edit files with your local editor
code ~/mnt/server/index.html
# Unmount
fusermount -u ~/mnt/server
Your ~/.ssh/config settings are used automatically, so sshfs myserver:/var/www ~/mnt/server works with shorthand hostnames.
Note that sshfs is directly affected by network latency, so it's not ideal for bulk file operations. Best suited for checking config files or small edits.
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 (
mlkem768x25519-sha256) enabled by default, providing preemptive protection against quantum computing threats
CVE-2025-26465 (MITM attack)
A vulnerability discovered by Qualys TRU in OpenSSH 6.8p1 through 9.9p1 allowed man-in-the-middle attacks when VerifyHostKeyDNS was enabled. Fixed in 9.9p2 (February 2025). Note that VerifyHostKeyDNS is off by default, so the impact is limited to users who explicitly enabled it.
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 six critical CVEs discovered by Google Cloud researchers, including CVE-2024-12084 (heap buffer overflow enabling remote code execution). 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
FAQ
Is SSH key authentication more secure than password authentication?
Yes, significantly. Password authentication is vulnerable to brute-force attacks and phishing. Key authentication uses cryptographic key pairs — without the private key file, login is mathematically impossible. Production servers should always disable password authentication once key auth is configured.
Can I use the same SSH key for multiple servers?
Yes. You copy the same public key (~/.ssh/id_ed25519.pub) to each server's ~/.ssh/authorized_keys. However, some security-conscious teams prefer separate keys per environment (e.g., one for production, another for development) to limit blast radius if a key is compromised.
How do I fix "Permission denied (publickey)" errors?
Check these in order: (1) Ensure ~/.ssh is 700 and ~/.ssh/authorized_keys is 600 on the server. (2) Verify the correct public key is in authorized_keys. (3) Confirm PubkeyAuthentication yes is set in sshd_config. (4) Check ssh -vvv user@server output for the specific rejection reason.
What's the difference between scp and rsync?
scp copies entire files every time, while rsync transfers only the changed portions (delta transfer). rsync also supports resume after interruption (--partial), compression (-z), exclusion patterns (--exclude), and mirroring (--delete). For anything beyond a quick one-off copy, rsync is the better choice.
Does the trailing slash in rsync actually matter?
Yes, and getting it wrong is one of the most common rsync mistakes. rsync -avz src/ dest/ copies the contents of src into dest. But rsync -avz src dest/ copies src as a directory into dest/src/. Always dry-run with -n if you're not sure.
Can rsync work over a non-standard SSH port?
Yes. Use the -e flag: rsync -avz -e 'ssh -p 2222' ./files/ user@server:/data/. If you've already configured the port in ~/.ssh/config, rsync picks it up automatically when you use the hostname alias.
Is it safe to use ForwardAgent?
Only for servers you fully trust. When ForwardAgent yes is set, anyone with root access on that server can use your forwarded SSH key to authenticate as you on other servers. Never set it globally with Host * — specify it per host, and only for bastion hosts or CI servers under your control.
Related Articles
- SSH Security Guide: From Key Auth to VPN-Only Access — in-depth SSH hardening beyond the basics in this article
- curl Complete Guide — you'll often hit APIs on remote servers with curl
- tmux Practical Guide — essential for maintaining sessions during SSH connections
- chmod & chown Practical Guide — understanding Linux permissions that SSH enforces
- Shell Scripting Guide — write deployment scripts that use rsync
- Dotfiles and Environment Variables — manage your
~/.ssh/configalongside other dotfiles - CLI Toolkit — the big picture of CLI tools and when to use each
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.