32blogby StudioMitsu

ssh & rsync Practical Guide: Remote Server Essentials

Set up SSH key auth, configure ssh_config, master port forwarding, and use rsync for efficient file sync and backups.

10 min read
On this page

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:

bash
ssh user@192.168.1.100

To specify a port, use -p:

bash
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.

bash
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:

bash
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:

bash
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:

bash
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:

bash
# 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:

bash
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

text
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:

text
Host *.example.com
    User admin
    IdentityFile ~/.ssh/id_ed25519_work

Keep-alive

Prevent idle SSH connections from being dropped:

text
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:

text
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:

text
Host *
    ControlMaster auto
    ControlPath ~/.ssh/sockets/%r@%h-%p
    ControlPersist 600

Create the socket directory first:

bash
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:

bash
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

bash
ssh -L 5433:localhost:5432 user@db-server

In another terminal:

bash
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:

bash
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:

bash
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:

bash
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:

Featurescprsync
Transfer modeFull copy every timeDelta only
Resume after interruptionNoYes, with --partial
CompressionNoYes, with -z
Exclusion patternsNoFlexible --exclude
MirroringNoYes, with --delete

For any non-trivial file transfer, rsync is the clear winner.

Basic syntax:

bash
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:

bash
rsync -avz ./dist/ user@server:/var/www/

Remote to local:

bash
rsync -avz user@server:/var/log/ ./logs/

Always do a dry run first. Just add -n:

bash
rsync -avzn ./dist/ user@server:/var/www/

Exclusion patterns

Exclude specific files or directories from transfer:

bash
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:

text
node_modules
.git
.env
*.log
.DS_Store

Then reference it with --exclude-from:

bash
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:

bash
rsync -avz --delete /data/ /backup/data/

Generational backup saves deleted or modified files to a dated directory:

bash
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:

bash
#!/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.

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:

bash
rsync --version

Firewall and fail2ban

Restrict SSH port access (default: 22) by IP:

bash
# 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:

bash
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. Use ProxyJump for multi-hop connections
  • Port forwarding lets you access remote services from localhost
  • rsync basics: -avz for most operations. Always dry-run with -n first
  • The trailing slash changes rsync behavior entirely — verify it until it's muscle memory
  • On the server side, set PasswordAuthentication no and PermitRootLogin no

Pair ssh with tmux to make remote terminal work significantly more comfortable. For the full CLI toolkit overview, see CLI Toolkit.