SSH Configuration

Secure shell configuration and advanced usage.

SSH Key Management

# Generate Ed25519 key (recommended)
ssh-keygen -t ed25519 -C "user@host"                 # Interactive
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_vault -N "" -C "vault-ssh-ca"  # Non-interactive

# Generate RSA key (for legacy systems)
ssh-keygen -t rsa -b 4096 -C "user@host"

# Generate key for specific purpose
ssh-keygen -t ed25519 -f ~/.ssh/id_github -C "github-personal"
ssh-keygen -t ed25519 -f ~/.ssh/id_work -C "work-servers"

# Hardware security keys (FIDO2/YubiKey)
ssh-keygen -t ed25519-sk -C "yubikey-resident"       # Requires touch
ssh-keygen -t ecdsa-sk -O resident -C "yubikey"      # Resident key (stored on device)
ssh-keygen -t ed25519-sk -O verify-required -C "key" # PIN required

# View key fingerprint
ssh-keygen -l -f ~/.ssh/id_ed25519.pub               # SHA256 fingerprint
ssh-keygen -l -f ~/.ssh/id_ed25519.pub -E md5        # MD5 fingerprint (legacy)
ssh-keygen -lv -f ~/.ssh/id_ed25519.pub              # With randomart

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

# Convert key formats
ssh-keygen -e -f ~/.ssh/id_ed25519.pub > key.pem     # OpenSSH to PEM
ssh-keygen -i -f key.pem > key.pub                   # PEM to OpenSSH

# Extract public key from private
ssh-keygen -y -f ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.pub

SSH Agent

# Start agent (if not running)
eval $(ssh-agent -s)

# Add key to agent
ssh-add ~/.ssh/id_ed25519
ssh-add                                              # Add default keys
ssh-add -t 3600 ~/.ssh/id_ed25519                    # 1 hour timeout

# List loaded keys
ssh-add -l                                           # Fingerprints
ssh-add -L                                           # Full public keys

# Remove keys
ssh-add -d ~/.ssh/id_ed25519                         # Remove specific
ssh-add -D                                           # Remove all

# Agent forwarding (use carefully!)
ssh -A user@jumphost                                 # Forward agent to remote
# Then from jumphost: ssh user@internal-server (uses forwarded key)

# Check agent socket
echo $SSH_AUTH_SOCK

# Systemd user agent (persistent)
systemctl --user enable --now ssh-agent
# Add to ~/.bashrc or ~/.zshrc:
export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/ssh-agent.socket"

# Forward agent with specific socket
SSH_AUTH_SOCK=/run/user/1000/ssh-agent.socket ssh user@host

# YubiKey agent integration
# Keys are in hardware, no ssh-add needed
# Just configure ~/.ssh/config with IdentityFile

SSH Certificates (Vault SSH CA)

# SSH certificates provide:
# - No need to distribute authorized_keys to every host
# - Automatic expiration (no stale keys)
# - Centralized revocation
# - Audit trail of who signed what

# Generate signing key pair (CA)
ssh-keygen -t ed25519 -f /etc/ssh/ca_user_key -C "user-ca"

# Sign a user key (manual)
ssh-keygen -s /etc/ssh/ca_user_key \
    -I "user-cert-20260227" \
    -n "ansible,evanusmodestus,root" \
    -V +8h \
    ~/.ssh/id_ed25519.pub

# View certificate details
ssh-keygen -L -f ~/.ssh/id_ed25519-cert.pub

# Output shows:
#   Type: ssh-ed25519-cert-v01@openssh.com user certificate
#   Public key: ED25519-CERT SHA256:...
#   Signing CA: ED25519 SHA256:... (using ed25519-sk)
#   Key ID: "user-cert-20260227"
#   Valid: from 2026-02-27T10:00:00 to 2026-02-27T18:00:00
#   Principals:
#         ansible
#         evanusmodestus
#         root
#   Extensions:
#         permit-pty
#         permit-user-rc

# Host trusts CA (add to sshd_config or sshd_config.d/)
# TrustedUserCAKeys /etc/ssh/trusted-user-ca-keys.pub

# The CA public key goes in /etc/ssh/trusted-user-ca-keys.pub
# NOT in authorized_keys

Vault SSH CA Integration

# Vault provides automatic SSH certificate signing
# No manual CA key management needed

# Prerequisites
export VAULT_ADDR="https://vault-01.inside.domusdigitalis.dev:8200"
export VAULT_TOKEN="$(cat ~/.vault-token)"

# Sign public key via Vault CLI
vault write -field=signed_key ssh/sign/domus-client \
    public_key=@$HOME/.ssh/id_ed25519_vault.pub \
    valid_principals="ansible,evanusmodestus,root,adminerosado" \
    >| ~/.ssh/id_ed25519_vault-cert.pub

# Sign via curl (API)
curl -sk -X POST -H "X-Vault-Token: $VAULT_TOKEN" \
    -H "Content-Type: application/json" \
    -d "{
        \"public_key\": \"$(cat ~/.ssh/id_ed25519_vault.pub)\",
        \"valid_principals\": \"ansible,evanusmodestus,root\"
    }" \
    "$VAULT_ADDR/v1/ssh/sign/domus-client" | jq -r '.data.signed_key' \
    > ~/.ssh/id_ed25519_vault-cert.pub

# Automated signing script (vault-ssh-sign)
#!/bin/bash
set -euo pipefail

KEY_PATH="${1:-$HOME/.ssh/id_ed25519_vault}"
PRINCIPALS="ansible,evanusmodestus,root,adminerosado,admin"

vault write -field=signed_key ssh/sign/domus-client \
    public_key=@"${KEY_PATH}.pub" \
    valid_principals="$PRINCIPALS" \
    >| "${KEY_PATH}-cert.pub"

# Reload SSH agent
ssh-add -d "${KEY_PATH}" 2>/dev/null || true
ssh-add "${KEY_PATH}"

echo "Certificate valid until:"
ssh-keygen -L -f "${KEY_PATH}-cert.pub" | grep Valid

# Host configuration (on each server)
# /etc/ssh/sshd_config.d/vault-ca.conf:
# TrustedUserCAKeys /etc/ssh/vault-ca.pub

# Get CA public key from Vault
curl -sk "$VAULT_ADDR/v1/ssh/public_key" | sudo tee /etc/ssh/vault-ca.pub
sudo systemctl reload sshd

SSH Config Patterns

# ~/.ssh/config

# Defaults for all hosts
Host *
    # Security settings
    AddKeysToAgent yes
    IdentitiesOnly yes

    # Connection settings
    ServerAliveInterval 60
    ServerAliveCountMax 3
    TCPKeepAlive yes

    # Compression (good for slow links)
    Compression yes

# Vault SSH CA hosts (use certificate)
Host vault-* ise-* bind-* kvm-* nas-* k3s-* home-dc01
    HostName %h.inside.domusdigitalis.dev
    User evanusmodestus
    IdentityFile ~/.ssh/id_ed25519_vault
    CertificateFile ~/.ssh/id_ed25519_vault-cert.pub

# FIDO2 fallback (if cert expired)
Host vault-* ise-* bind-* kvm-* nas-*
    IdentityFile ~/.ssh/id_ed25519_sk
    IdentityFile ~/.ssh/id_ecdsa_sk

# Specific host overrides
Host nas-01
    HostName nas-01.inside.domusdigitalis.dev
    User admin
    IdentityFile ~/.ssh/id_ed25519_vault

Host home-dc01
    HostName home-dc01.inside.domusdigitalis.dev
    User administrator@inside.domusdigitalis.dev

# Jump host (bastion)
Host internal-*
    ProxyJump bastion.example.com
    User admin

# GitHub
Host github.com
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_github

# Wildcards
Host *.dev
    User developer
    IdentityFile ~/.ssh/id_dev

# Pattern matching
Host 10.50.1.*
    User admin
    IdentityFile ~/.ssh/id_ed25519_vault

SSH Tunneling

# Local port forward (access remote service locally)
# -L localport:remotehost:remoteport
ssh -L 8080:localhost:80 user@server
# Access server's port 80 via localhost:8080

ssh -L 9060:ise-01.inside.domusdigitalis.dev:9060 user@jumphost
# Access ISE ERS via localhost:9060

# Multiple forwards
ssh -L 8080:localhost:80 -L 8443:localhost:443 user@server

# Remote port forward (expose local service to remote)
# -R remoteport:localhost:localport
ssh -R 8080:localhost:3000 user@server
# Server can access your local port 3000 via its localhost:8080

# Dynamic port forward (SOCKS proxy)
ssh -D 1080 user@server
# Configure browser/apps to use SOCKS5 proxy localhost:1080
# All traffic goes through server

# Background tunnel (no shell)
ssh -fN -L 8080:localhost:80 user@server
# -f = background
# -N = no command (tunnel only)

# Keep tunnel alive
ssh -fN -o ServerAliveInterval=60 -L 8080:localhost:80 user@server

# Tunnel with autossh (auto-reconnect)
autossh -M 0 -fN -o "ServerAliveInterval 30" -o "ServerAliveCountMax 3" \
    -L 8080:localhost:80 user@server

# Jump/proxy through bastion
ssh -J bastion.example.com user@internal-server
# Multi-hop: ssh -J hop1,hop2 user@target

# Tunnel through bastion
ssh -L 8080:internal-server:80 user@bastion
# Or with ProxyJump:
ssh -J bastion -L 8080:localhost:80 user@internal-server

File Transfer (SCP/Rsync)

# SCP - Secure Copy
scp file.txt user@host:/path/                        # Upload
scp user@host:/path/file.txt ./                      # Download
scp -r directory/ user@host:/path/                   # Recursive
scp -P 2222 file.txt user@host:/path/                # Custom port
scp -i ~/.ssh/id_special file.txt user@host:/path/   # Specific key

# SCP through jump host
scp -o ProxyJump=bastion file.txt user@internal:/path/

# SCP preserve attributes
scp -p file.txt user@host:/path/                     # Preserve times/modes

# Rsync (preferred for large transfers)
rsync -av /local/path/ user@host:/remote/path/       # Archive + verbose
rsync -avz /local/path/ user@host:/remote/path/      # With compression
rsync -avz --delete /local/ user@host:/remote/       # Mirror (delete extra files)
rsync -avz --progress /local/ user@host:/remote/     # Show progress
rsync -avz --dry-run /local/ user@host:/remote/      # Preview changes

# Rsync with SSH options
rsync -avz -e "ssh -p 2222" /local/ user@host:/remote/
rsync -avz -e "ssh -i ~/.ssh/id_special" /local/ user@host:/remote/

# Rsync through jump host
rsync -avz -e "ssh -J bastion" /local/ user@internal:/remote/

# Rsync exclude patterns
rsync -avz --exclude='*.log' --exclude='.git' /local/ user@host:/remote/
rsync -avz --exclude-from=exclude.txt /local/ user@host:/remote/

# Rsync resume partial transfer
rsync -avz --partial --progress /local/bigfile user@host:/remote/

# Bandwidth limit
rsync -avz --bwlimit=1000 /local/ user@host:/remote/  # 1000 KB/s

SSHD Server Configuration

# /etc/ssh/sshd_config key settings

# Authentication
PasswordAuthentication no                            # Disable passwords
PubkeyAuthentication yes                             # Enable keys
PermitRootLogin prohibit-password                    # Root with keys only
ChallengeResponseAuthentication no                   # Disable keyboard-interactive

# SSH Certificates (Vault CA)
TrustedUserCAKeys /etc/ssh/vault-ca.pub              # Trust Vault CA

# Restrict users/groups
AllowUsers admin ansible deploy
AllowGroups wheel ssh-users
DenyUsers root guest
DenyGroups nogroup

# Security hardening
Protocol 2                                           # SSH v2 only
MaxAuthTries 3                                       # Limit auth attempts
MaxSessions 10                                       # Limit sessions
LoginGraceTime 30                                    # Timeout for auth
ClientAliveInterval 300                              # Check client every 5min
ClientAliveCountMax 2                                # Disconnect after 2 missed
PermitEmptyPasswords no                              # No empty passwords

# Network settings
Port 22                                              # Or custom port
ListenAddress 0.0.0.0                                # All interfaces
ListenAddress 10.50.1.99                             # Specific IP only

# Forwarding
AllowTcpForwarding yes
GatewayPorts no
X11Forwarding no
PermitTunnel no

# Subsystems
Subsystem sftp /usr/lib/openssh/sftp-server

# Match blocks (per-user/group/address)
Match User deploy
    PasswordAuthentication no
    AllowTcpForwarding no
    ForceCommand /usr/local/bin/deploy-script

Match Address 10.50.1.0/24
    PasswordAuthentication no
    PubkeyAuthentication yes

Match Group sftp-only
    ChrootDirectory /home/%u
    ForceCommand internal-sftp
    AllowTcpForwarding no

# Test config before restart
sshd -t

# Apply changes
systemctl reload sshd

Authorized Keys Options

# ~/.ssh/authorized_keys format:
# [options] keytype base64key comment

# Basic key
ssh-ed25519 AAAA... user@host

# Restrict to specific command
command="/usr/local/bin/backup-script" ssh-ed25519 AAAA... backup-key

# Restrict source IP
from="10.50.1.0/24" ssh-ed25519 AAAA... admin-key
from="10.50.1.20,10.50.1.60" ssh-ed25519 AAAA... multi-source

# Disable forwarding
no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-ed25519 AAAA... restricted

# Read-only SFTP
command="internal-sftp",no-port-forwarding,no-X11-forwarding ssh-ed25519 AAAA... sftp-key

# Environment variables
environment="GIT_AUTHOR_NAME=Deploy Bot" ssh-ed25519 AAAA... deploy-key

# Certificate principals (not in authorized_keys)
# Instead, TrustedUserCAKeys in sshd_config
# CA signs certs with principals: -n "ansible,deploy"
# sshd accepts if user matches a principal

# Combine options
from="10.50.1.0/24",command="/usr/bin/rsync --server",no-pty ssh-ed25519 AAAA... rsync-key

# Expiry (OpenSSH 8.2+)
expiry-time="20261231" ssh-ed25519 AAAA... temp-key

# Multiple keys per line (NO, one per line)
# Each key on its own line

# Authorized keys with Vault CA
# Don't use authorized_keys for cert users!
# Add CA to: TrustedUserCAKeys /etc/ssh/vault-ca.pub
# Users with valid certs can log in as any matching principal

SSH Troubleshooting

# Verbose connection (client-side)
ssh -v user@host                                     # Verbose
ssh -vv user@host                                    # More verbose
ssh -vvv user@host                                   # Maximum verbosity

# Check what key is being offered
ssh -v user@host 2>&1 | grep "Offering"

# Check certificate validity
ssh-keygen -L -f ~/.ssh/id_ed25519_vault-cert.pub
# Look for:
# - Valid: dates (not expired?)
# - Principals: (includes target user?)
# - Extensions: (includes permit-pty?)

# Common cert errors:
# "no matching key found" - principals don't include target user
# "certificate invalid: not yet valid" - clock skew (check NTP)
# "certificate has expired" - re-sign with vault

# Server-side debugging
sudo /usr/sbin/sshd -d -p 2222                       # Debug mode on alt port
# Connect: ssh -p 2222 -v user@host

# Check sshd logs
journalctl -u sshd -f
journalctl -u sshd --since "5 minutes ago"

# Permission problems
ls -la ~/.ssh/                                       # Should be 700
ls -la ~/.ssh/authorized_keys                        # Should be 600
ls -la ~/.ssh/id_*                                   # Private: 600, Public: 644

# Fix permissions
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_*
chmod 644 ~/.ssh/*.pub
chmod 600 ~/.ssh/authorized_keys

# Check SELinux (RHEL/Rocky)
ls -Z ~/.ssh/
restorecon -Rv ~/.ssh/                               # Fix SELinux labels

# Test specific key
ssh -i ~/.ssh/id_specific -v user@host

# Test certificate
ssh -i ~/.ssh/id_ed25519_vault \
    -o "CertificateFile=~/.ssh/id_ed25519_vault-cert.pub" \
    -v user@host

# Verify server accepts CA
ssh -v user@host 2>&1 | grep -i "certificate"
# Look for: "Server accepts key: ... cert"

# Check host key changed
ssh-keygen -R hostname                               # Remove old key
ssh-keyscan hostname >> ~/.ssh/known_hosts           # Add new key

# Connection timeouts
ssh -o ConnectTimeout=5 user@host                    # 5 second timeout
ssh -o BatchMode=yes user@host                       # No prompts (for scripts)

Infrastructure SSH Patterns

# Multi-host command execution
HOSTS="vault-01 ise-01 bind-01 kvm-01 nas-01"
for host in $HOSTS; do
    echo "=== $host ==="
    ssh "$host.inside.domusdigitalis.dev" "hostname; uptime"
done

# Parallel execution (with GNU parallel)
echo "vault-01 ise-01 bind-01" | tr ' ' '\n' | \
    parallel -j3 ssh {}.inside.domusdigitalis.dev 'hostname; uptime'

# SSH connectivity matrix
echo "=== SSH Connectivity ==="
printf "%-15s %-8s %-8s\n" "HOST" "SSH" "CERT"
for host in vault-01 ise-01 bind-01 kvm-01 nas-01; do
    fqdn="$host.inside.domusdigitalis.dev"
    ssh_ok=$(timeout 5 ssh -o BatchMode=yes "$fqdn" "exit" 2>/dev/null && echo "✓" || echo "✗")
    cert_valid="✓"
    if ssh -v "$fqdn" exit 2>&1 | grep -q "certificate"; then
        cert_valid="✓ cert"
    else
        cert_valid="✗ key"
    fi
    printf "%-15s %-8s %-8s\n" "$host" "$ssh_ok" "$cert_valid"
done

# Vault SSH cert renewal for all hosts
vault-ssh-sign  # Signs key with all principals

# Test SSH to all infrastructure
~/.local/bin/vault-ssh-test

# Deploy SSH CA to new host
deploy_ssh_ca() {
    local host=$1
    echo "=== Deploying CA to $host ==="
    curl -sk "https://vault-01.inside.domusdigitalis.dev:8200/v1/ssh/public_key" | \
        ssh "$host" "sudo tee /etc/ssh/vault-ca.pub"
    ssh "$host" "echo 'TrustedUserCAKeys /etc/ssh/vault-ca.pub' | sudo tee /etc/ssh/sshd_config.d/vault-ca.conf"
    ssh "$host" "sudo systemctl reload sshd"
}

# Batch file distribution
for host in vault-01 ise-01 bind-01; do
    scp /etc/resolv.conf "$host.inside.domusdigitalis.dev":/tmp/
    ssh "$host.inside.domusdigitalis.dev" "sudo cp /tmp/resolv.conf /etc/"
done

# Run command and log output
infra_cmd() {
    local host=$1
    shift
    local cmd="$*"
    echo "[$(date +%H:%M:%S)] $host: $cmd"
    ssh "$host.inside.domusdigitalis.dev" "$cmd" 2>&1 | sed "s/^/  /"
}

infra_cmd vault-01 "vault status"
infra_cmd ise-01 "show application status ise"

SSH Hardening

# Generate new host keys (after install)
rm -f /etc/ssh/ssh_host_*
ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""
ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N ""

# Restrict ciphers/MACs/KexAlgorithms
# /etc/ssh/sshd_config.d/hardening.conf
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org

# Host key algorithms
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256

# Remove weak moduli (for DH)
awk '$5 >= 3071' /etc/ssh/moduli > /tmp/moduli.safe
mv /tmp/moduli.safe /etc/ssh/moduli

# Audit SSH configuration
sshd -T | grep -E '^(password|permit|allow|deny|pubkey)'

# fail2ban for SSH
cat > /etc/fail2ban/jail.d/sshd.local <<'EOF'
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600
EOF
systemctl restart fail2ban

# Rate limit SSH with firewalld
firewall-cmd --add-rich-rule='rule family="ipv4" service name="ssh" limit value="10/m" accept' --permanent
firewall-cmd --reload

# Port knocking (advanced)
# Requires knockd daemon
# User must "knock" on ports 7000, 8000, 9000 before SSH opens

# Two-factor with Google Authenticator
sudo yum install google-authenticator-libpam
google-authenticator  # As user, setup TOTP
# Edit /etc/pam.d/sshd:
# auth required pam_google_authenticator.so
# Edit sshd_config:
# ChallengeResponseAuthentication yes
# AuthenticationMethods publickey,keyboard-interactive

Common Gotchas

# WRONG: Certificate signed but not loaded in agent
vault write ssh/sign/domus-client ... > ~/.ssh/id_ed25519-cert.pub
ssh host  # Uses old key without cert!

# CORRECT: Reload agent after signing
ssh-add -d ~/.ssh/id_ed25519 2>/dev/null || true
ssh-add ~/.ssh/id_ed25519  # Adds key + finds cert automatically

# WRONG: TrustedUserCAKeys in wrong location (Match block)
# sshd_config:
Match Address 10.50.1.0/24
    TrustedUserCAKeys /etc/ssh/vault-ca.pub  # Inside Match = only for matches!

# CORRECT: TrustedUserCAKeys BEFORE Match blocks (global)
TrustedUserCAKeys /etc/ssh/vault-ca.pub
Match Address 10.50.1.0/24
    PasswordAuthentication no

# WRONG: Principals don't include target user
# Signed with: valid_principals="ansible"
# But logging in as: ssh evanusmodestus@host  # FAIL!

# CORRECT: Include all usernames you'll SSH as
vault write ssh/sign/domus-client \
    valid_principals="ansible,evanusmodestus,root,admin"

# WRONG: Clock skew causes "not yet valid"
# Your machine is ahead of Vault server

# CORRECT: Enable NTP on all hosts
timedatectl set-ntp true
timedatectl status | grep synchronized

# WRONG: Using IdentityFile without IdentitiesOnly
Host server
    IdentityFile ~/.ssh/id_specific
# Agent still tries ALL loaded keys first!

# CORRECT: Use IdentitiesOnly to try only specified keys
Host server
    IdentityFile ~/.ssh/id_specific
    IdentitiesOnly yes

# WRONG: scp to Synology NAS fails
scp file nas-01:/path/  # Blocked by DSM security

# CORRECT: Use cat pipe
cat file | ssh nas-01 "cat > /path/file"

# WRONG: Permission denied after key copy
ssh-copy-id user@host
ssh user@host  # Still asks for password!

# CORRECT: Check permissions on remote
ssh user@host "chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys"
# Also check SELinux: restorecon -Rv ~/.ssh/

Quick Reference

# Keys
ssh-keygen -t ed25519               # Generate key
ssh-keygen -l -f key.pub            # Show fingerprint
ssh-keygen -L -f cert.pub           # Show certificate

# Agent
ssh-add ~/.ssh/id_ed25519           # Add key
ssh-add -l                          # List keys
ssh-add -D                          # Remove all

# Connection
ssh user@host                       # Basic connect
ssh -p 2222 user@host               # Custom port
ssh -i ~/.ssh/key user@host         # Specific key
ssh -J bastion user@internal        # Jump host

# Tunnels
ssh -L 8080:localhost:80 host       # Local forward
ssh -R 8080:localhost:80 host       # Remote forward
ssh -D 1080 host                    # SOCKS proxy
ssh -fN -L 8080:localhost:80 host   # Background tunnel

# File transfer
scp file user@host:/path/           # Upload
scp user@host:/path/file ./         # Download
rsync -avz /local/ user@host:/rem/  # Sync

# Vault SSH CA
vault write ssh/sign/domus-client public_key=@~/.ssh/id.pub valid_principals="user"
ssh-keygen -L -f ~/.ssh/id-cert.pub # Verify cert

# Troubleshooting
ssh -v user@host                    # Verbose
sshd -t                             # Test config
journalctl -u sshd -f               # Watch logs
chmod 700 ~/.ssh                    # Fix permissions
chmod 600 ~/.ssh/id_*               # Private keys
chmod 644 ~/.ssh/*.pub              # Public keys

# Common ports
# 22    SSH (default)
# 2222  Common alternate

# Config files
# ~/.ssh/config              - Client config
# /etc/ssh/sshd_config       - Server config
# ~/.ssh/authorized_keys     - Allowed keys
# ~/.ssh/known_hosts         - Trusted hosts
# /etc/ssh/vault-ca.pub      - Trusted CA (Vault)