Skip to content
Home
SSH Guide: Secure Shell for Remote Access

SSH Guide: Secure Shell for Remote Access

Linux Linux 7 min read 1485 words Beginner ExcellentWiki Editorial Team

SSH (Secure Shell) is the standard protocol for secure remote access to Unix-like systems. Beyond basic remote login, SSH provides encrypted file transfer, port forwarding, tunneling, and a secure channel for automation.

SSH Key-Based Authentication

Password-based SSH is convenient but less secure. Key-based authentication is the standard for production systems.

Generating Keys

# Generate Ed25519 key (recommended — fast, secure)
ssh-keygen -t ed25519 -C "user@hostname"

# Generate RSA key (fallback for older systems)
ssh-keygen -t rsa -b 4096 -C "user@hostname"

# Specify custom filename
ssh-keygen -t ed25519 -f ~/.ssh/github_key -C "github"

# Change passphrase later
ssh-keygen -p -f ~/.ssh/id_ed25519

Guidelines:

  • Use Ed25519 keys when possible (shorter, faster, equally secure)
  • RSA 4096-bit keys are compatible with all SSH implementations
  • Always protect private keys with a passphrase
  • Never share private keys — they are the digital equivalent of a password

Copying Public Key to Server

# Standard method
ssh-copy-id user@server.example.com

# If ssh-copy-id not available
cat ~/.ssh/id_ed25519.pub | ssh user@server "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"

# Manual — append to ~/.ssh/authorized_keys on server
echo "ssh-ed25519 AAAAC3... user@host" >> ~/.ssh/authorized_keys
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

SSH Client Configuration

The SSH Config File (~/.ssh/config)

# Personal configuration
Host myserver
    HostName 192.168.1.100
    User alice
    Port 2222
    IdentityFile ~/.ssh/id_ed25519

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

# Wildcard for section
Host *.example.com
    User admin
    ForwardAgent no
    ServerAliveInterval 60

# Jump host (bastion)
Host internal
    HostName 10.0.1.50
    User alice
    ProxyJump bastion.example.com

# Defaults for all hosts
Host *
    ServerAliveInterval 30
    ServerAliveCountMax 3
    Compression yes
    ExitOnForwardFailure yes

Connecting

# Using config alias
ssh myserver

# Full command
ssh -p 2222 alice@server.example.com

# Verbose (debug connection issues)
ssh -vvv user@server

# Run command and exit
ssh user@server 'df -h'
ssh user@server 'systemctl status nginx'

# Force specific key
ssh -i ~/.ssh/work_key user@server

Server Configuration (/etc/ssh/sshd_config)

# Disable password authentication (key-only)
PasswordAuthentication no

# Disable root login
PermitRootLogin no

# Use only key-based auth
PubkeyAuthentication yes
AuthenticationMethods publickey

# Change default port (reduces automated attacks)
Port 2222

# Limit users who can SSH
AllowUsers alice bob
DenyUsers charlie

# Limit group access
AllowGroups ssh-users

# Disable X11 forwarding if not needed
X11Forwarding no

# Idle timeout
ClientAliveInterval 300
ClientAliveCountMax 2

# Maximum authentication attempts
MaxAuthTries 3

# Restart after changes
sudo systemctl restart sshd

Security Hardening Checklist

  1. Disable password authentication
  2. Disable root login
  3. Use key-only authentication
  4. Change the default port (optional, but reduces log noise)
  5. Allow only specific users
  6. Set idle timeout (prevent orphaned sessions)
  7. Use fail2ban for IP-based rate limiting
  8. Keep SSH updated (CVE patches)
  9. Consider SSH certificate authority for large fleets

SSH Agent and Agent Forwarding

Using ssh-agent

# Start the agent in the background
eval "$(ssh-agent -s)"

# Add keys to agent
ssh-add ~/.ssh/id_ed25519
ssh-add -l                             # list loaded keys
ssh-add -L                             # list public keys
ssh-add -D                             # remove all keys

# Add key with timeout (auto-remove after 1 hour)
ssh-add -t 3600 ~/.ssh/id_ed25519

Agent Forwarding

# Enable forwarding for a session
ssh -A user@server

# In SSH config
Host trustedserver
    ForwardAgent yes

# Security note: only use agent forwarding with trusted servers.
# A compromised server can use your agent to authenticate to other servers.

Port Forwarding (Tunneling)

Local Port Forwarding

Forward a local port to a remote service:

# Forward localhost:8080 to remote server:80
ssh -L 8080:localhost:80 user@server

# Forward through a jump host
ssh -L 9090:internal-service:5432 bastion.example.com

# Multiple forwardings
ssh -L 8080:web:80 -L 9090:db:5432 user@server

Remote Port Forwarding

Expose a local port on a remote server (useful for behind-NAT services):

# Expose local:3000 on remote:9000
ssh -R 9000:localhost:3000 user@server

# Allow remote hosts to connect (GatewayPorts on server)
ssh -R 0.0.0.0:9000:localhost:3000 user@server

Dynamic Port Forwarding (SOCKS Proxy)

# Create SOCKS5 proxy on local port 1080
ssh -D 1080 user@server

# Use with browser/apps
# Configure proxy to localhost:1080, SOCKS5

Secure File Transfer

# SCP (simple copy)
scp file.txt user@server:/path/
scp -r /local/dir user@server:/remote/path
scp user@server:/remote/file.txt /local/path

# SFTP (interactive file transfer)
sftp user@server
sftp> ls
sftp> get remote_file.txt
sftp> put local_file.txt
sftp> get -r /remote/dir         # recursive
sftp> !command                   # run local command

# Rsync over SSH (resumable, efficient)
rsync -avz -e ssh /local/dir/ user@server:/remote/dir/
rsync -avz --delete -e ssh user@server:/remote/dir/ /local/dir/

Troubleshooting

# Verbose connection (add -v, -vv, -vvv)
ssh -vvv user@server

# Check if SSH is listening on server
nc -zv server.example.com 22

# Test key authentication
ssh -i ~/.ssh/id_ed25519 -o "PubkeyAuthentication=yes" -o "PasswordAuthentication=no" user@server

# Check server's host key
ssh-keyscan -H server.example.com >> ~/.ssh/known_hosts

# Known hosts
cat ~/.ssh/known_hosts

# Check SSH daemon status
systemctl status sshd

# Check authentication logs
sudo tail -f /var/log/auth.log        # Debian/Ubuntu
sudo tail -f /var/log/secure           # RHEL/CentOS

# Common error: WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED
# The server has been reinstalled or MITM attack suspected
ssh-keygen -R server.example.com       # remove old key
ssh user@server                        # accept new key

Real-World Implementation Tips

Production Considerations

When moving from development to production, several factors become critical. Error handling should be comprehensive — every external call (database, API, file system) should have proper error checking, logging, and retry logic where appropriate. Performance monitoring through metrics and structured logging helps identify bottlenecks before they affect users.

Testing Strategy

A thorough testing approach combines multiple levels:

  • Unit tests verify individual functions and methods in isolation
  • Integration tests validate that components work together correctly
  • Edge case tests cover boundary conditions, empty inputs, and error states
  • Performance tests ensure the system meets latency and throughput requirements

Test data should be realistic but controlled. Mock external dependencies to make tests fast and deterministic. Aim for tests that are independent, repeatable, and fast enough to run on every commit.

Documentation

Good documentation is essential for maintainable code. Follow these principles:

  • Document the “why” not just the “what” — explain design decisions
  • Keep examples up to date with the code
  • Include usage examples for public APIs
  • Document configuration options and their defaults
  • Explain error conditions and recovery strategies

Security Best Practices

Security should be considered throughout development:

  • Validate all inputs at system boundaries
  • Use parameterized queries for database access
  • Store secrets in environment variables or secret managers
  • Keep dependencies updated to patch vulnerabilities
  • Apply the principle of least privilege

Performance Optimization

Optimize based on measured data, not assumptions:

  1. Profile before optimizing — identify actual bottlenecks
  2. Measure the impact of each change
  3. Consider the trade-off between speed and readability
  4. Cache expensive operations with appropriate invalidation
  5. Use connection pooling for database and network resources

Monitoring and Observability

Production systems need visibility:

  • Structured logging with correlation IDs for request tracking
  • Metrics for latency, throughput, error rates, and resource usage
  • Health check endpoints for load balancers and orchestration
  • Distributed tracing for request flows across services
  • Alerts for anomaly detection based on baselines

These patterns apply across all programming languages and frameworks. The specific implementation varies, but the principles remain consistent.

FAQ

Q: What is the difference between ssh-agent and ssh forwarding? A: ssh-agent holds your private keys in memory. Agent forwarding (-A) lets remote servers use your local agent to authenticate to other servers. Use forwarding carefully — a compromised server can use your keys.

Q: How do I prevent SSH timeout? A: Add to ~/.ssh/config: ServerAliveInterval 60 (sends keepalive every 60 seconds) or configure ClientAliveInterval on the server.

Q: What is the purpose of ~/.ssh/known_hosts? A: It records the host keys of servers you have connected to, preventing man-in-the-middle attacks. SSH warns if a server’s key changes.

Q: How do I copy files between two remote servers? A: From your local machine: scp user1@server1:file.txt user2@server2:. Or SSH to one server and SCP from there.

Q: What permissions should SSH files have? A: ~/.ssh/ directory: 700. ~/.ssh/id_ed25519 (private key): 600. ~/.ssh/id_ed25519.pub: 644. ~/.ssh/authorized_keys: 600. ~/.ssh/config: 600.

Q: How do I set up SSH for automated scripts without a passphrase? A: Use ssh-agent with ssh-add in the parent process, or create a key without a passphrase (less secure). For CI/CD, use deployment keys or short-lived certificates.

SSH Advanced Configuration

Key-Based Authentication

# Generate Ed25519 key (preferred over RSA)
ssh-keygen -t ed25519 -a 100 -C "your@email.com"

# Copy key to remote host
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@example.com

# Test key authentication
ssh -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes user@example.com

SSH Config File (~/.ssh/config)

Host web
    HostName example.com
    User deploy
    Port 2222
    IdentityFile ~/.ssh/deploy_ed25519
    LocalForward 3000 localhost:3000

Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3
    Compression yes

Tunneling and Port Forwarding

# Local port forwarding — access remote service locally
ssh -L 8080:localhost:80 user@remote

# Remote port forwarding — expose local service remotely
ssh -R 9090:localhost:3000 user@remote

# Dynamic port forwarding (SOCKS proxy)
ssh -D 1080 user@remote

# Jump host (bastion)
ssh -J jump.example.com target.internal

# Keep tunnel alive
autossh -M 0 -o "ServerAliveInterval 30" -o "ServerAliveCountMax 3" -L 8080:localhost:80 user@remote

Security Hardening

Add to /etc/ssh/sshd_config:

Port 2222                    # Non-standard port
PermitRootLogin prohibit-password
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers alice bob
Protocol 2

For a comprehensive overview, read our article on Bash Scripting Basics.

See also: Linux Text Processing: grep, sed, awk Guide.

Section: Linux 1485 words 7 min read Beginner 756 articles in section Report inaccuracy Back to top