Prerequisites
Before starting, confirm the following:
- You have root or sudo access to the target Proxmox host, LXC container, or VM
- You have a local SSH key pair generated (
~/.ssh/id_ed25519and~/.ssh/id_ed25519.pub) - You have console access to the target via the Proxmox web UI (fallback if SSH locks you out)
- The target is running a Debian-based distribution (Ubuntu 22.04/24.04 or Debian 12)
- You have tested that your SSH key works before disabling password auth
Warning: Misconfiguring SSH can lock you out of the system. Always keep the Proxmox console session open as a fallback until every step is verified.
Step 1: Create a Service Account
Never use root for routine SSH access. Create a dedicated service account.
# On the target host/container
sudo adduser svc-admin --disabled-password --gecos "Service Admin"
# Add to sudo group
sudo usermod -aG sudo svc-admin
# Verify sudo works
su - svc-admin
sudo whoami
# Should output: root
For LXC containers, if the container was created with root-only access, you may need to run these commands from the Proxmox host:
# From the Proxmox host
pct exec <CTID> -- adduser svc-admin --disabled-password --gecos "Service Admin"
pct exec <CTID> -- usermod -aG sudo svc-admin
Step 2: Configure SSH Key Authentication
# On the target, as the new service account
su - svc-admin
mkdir -p ~/.ssh
chmod 700 ~/.ssh
From your local machine, copy your public key:
# From your local machine
ssh-copy-id -i ~/.ssh/id_ed25519.pub svc-admin@<target-ip>
If ssh-copy-id is not available or the target is an LXC container without direct SSH yet:
# Manually copy the key content
# On your local machine, copy the output of:
cat ~/.ssh/id_ed25519.pub
# On the target, as svc-admin:
echo "ssh-ed25519 AAAA... your-key-comment" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
Verify: Open a new terminal and confirm key-based login works before proceeding.
ssh svc-admin@<target-ip>
# Should log in without a password prompt
Stop here if key-based login does not work. Do not proceed to disable password auth until this is confirmed.
Step 3: Disable Root SSH and Password Authentication
Edit the SSH daemon configuration:
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup-$(date +%Y%m%d)
sudo nano /etc/ssh/sshd_config
Set the following directives (uncomment and modify as needed):
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
ChallengeResponseAuthentication no
UsePAM yes
X11Forwarding no
MaxAuthTries 3
LoginGraceTime 30
AllowUsers svc-admin
If you need to allow additional users, add them to the AllowUsers line separated by spaces.
Validate the configuration before restarting:
sudo sshd -t
# Should produce no output if config is valid
Restart SSH:
sudo systemctl restart sshd
Verify: In a new terminal (do not close your current session):
# Should work
ssh svc-admin@<target-ip>
# Should be rejected
ssh root@<target-ip>
# Expected: Permission denied (publickey)
Step 4: Set Up fail2ban
fail2ban monitors SSH logs and bans IPs after repeated failed attempts.
sudo apt update && sudo apt install -y fail2ban
Create a local configuration (never edit the main config directly):
sudo tee /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
backend = systemd
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
EOF
Start and enable fail2ban:
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Verify:
sudo fail2ban-client status sshd
# Should show the jail is active with 0 currently banned (unless there is existing traffic)
Step 5: Configure Firewall Rules
For Proxmox VMs (using UFW)
sudo apt install -y ufw
# Default deny incoming, allow outgoing
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow SSH from your management network only
sudo ufw allow from 10.0.0.0/24 to any port 22 proto tcp comment "SSH from management network"
# If you need to allow SSH from WireGuard subnet
sudo ufw allow from 10.10.0.0/24 to any port 22 proto tcp comment "SSH from WireGuard"
# Enable the firewall
sudo ufw enable
sudo ufw status verbose
For Proxmox LXC Containers (using Proxmox firewall)
LXC containers can use the Proxmox built-in firewall instead of UFW. Configure via the Proxmox web UI or CLI:
# From the Proxmox host
# Enable firewall on the container
pct set <CTID> -firewall 1
# Add rules via the Proxmox API or web UI:
# Allow SSH from management network
# Direction: IN, Action: ACCEPT, Source: 10.0.0.0/24, Dest port: 22, Protocol: tcp
Alternatively, create the firewall rules file directly:
# On the Proxmox host
cat > /etc/pve/firewall/<CTID>.fw << 'EOF'
[RULES]
IN ACCEPT -source 10.0.0.0/24 -p tcp -dport 22 -log nolog
IN DROP -p tcp -dport 22 -log nolog
EOF
Verify:
# From an allowed network
ssh svc-admin@<target-ip>
# Should succeed
# The firewall should drop connections from non-allowed networks
Step 6: Optional Hardening
Change the SSH port
Not security through obscurity -- it reduces log noise from automated scanners.
# In /etc/ssh/sshd_config
Port 2222
# Update fail2ban jail
# In /etc/fail2ban/jail.local, change port = ssh to port = 2222
# Update firewall rules to reflect the new port
sudo ufw delete allow from 10.0.0.0/24 to any port 22 proto tcp
sudo ufw allow from 10.0.0.0/24 to any port 2222 proto tcp comment "SSH non-standard port"
sudo systemctl restart sshd
sudo systemctl restart fail2ban
Disable SSH agent forwarding
# In /etc/ssh/sshd_config
AllowAgentForwarding no
AllowTcpForwarding no
Rollback
If SSH access is lost:
- Use the Proxmox console -- Access the VM or container via the Proxmox web UI console (no SSH required)
- Restore the SSH config backup:
sudo cp /etc/ssh/sshd_config.backup-YYYYMMDD /etc/ssh/sshd_config
sudo systemctl restart sshd
- If fail2ban banned your IP:
sudo fail2ban-client set sshd unbanip <your-ip>
- If UFW is blocking you:
# From the Proxmox console
sudo ufw disable
# Fix the rules, then re-enable
- If the service account is misconfigured:
# From the Proxmox console, log in as root
# Re-enable root SSH temporarily
sed -i 's/PermitRootLogin no/PermitRootLogin yes/' /etc/ssh/sshd_config
sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config
systemctl restart sshd
# Fix the service account, then re-harden
Maintenance Notes
- Audit
/var/log/auth.logperiodically for failed login patterns - Review
fail2ban-client status sshdweekly for ban activity - Rotate SSH keys annually -- update
authorized_keyson all hardened hosts - When creating new LXC containers or VMs, apply this procedure before any service deployment
- Document each hardened host in the infrastructure inventory with the SSH port, allowed networks, and service account name