← Back to Wiki
Security / SSH

SSH Hardening: Remove Weak MAC Algorithms Across a Whole Fleet

A subnet scan will flag weak SSH MAC algorithms on most of your fleet. Nobody configured that. It is the OS default list.

Here is how to fix it across containers, VMs and vendor NAS units without doing every host by hand.

Share on X

The fix itself

Restrict the MACs directive in sshd_config to SHA-2 algorithms only.

MACs hmac-sha2-256,hmac-sha2-512,[email protected],[email protected]

An idempotent, safe rollout script

Across a fleet, spend the extra few lines. Back up before editing. Validate before restarting. Restore the backup if validation fails.

set -uo pipefail
CONF=/etc/ssh/sshd_config
BAK="$CONF.bak.$(date +%s)"
cp "$CONF" "$BAK"
STRONG='MACs hmac-sha2-256,hmac-sha2-512,[email protected],[email protected]'
if grep -q '^MACs ' "$CONF"; then
  sed -i "s/^MACs .*/$STRONG/" "$CONF"
else
  echo "$STRONG" >> "$CONF"
fi
if sshd -t 2>/tmp/sshd_t_err; then
  systemctl restart ssh 2>/dev/null || systemctl restart sshd 2>/dev/null
  echo HARDEN_OK
else
  cp "$BAK" "$CONF"
  echo "HARDEN_FAIL_SYNTAX: $(cat /tmp/sshd_t_err)"
fi

Reaching containers without per-host SSH keys

Most of your containers probably never got a dedicated SSH keypair. Only the ones that needed direct SSH for something else.

Do not generate and authorize a new keypair on every one of them for a single fleet change.

Use your hypervisor instead if it can run commands inside a container without SSH. On Proxmox that is pct exec. This is the better default for any one-off fleet change, not just this one.

Vendor NAS gotchas

BE WARNED: a trailing Match block breaks a naive append. Some vendor NAS sshd_config files end with Match User blocks. Append a global directive at end-of-file and it lands inside the last Match block. OpenSSH rejects that outright. Global directives have to come before any Match block. Insert before the first Match line instead.
awk -v line="$STRONG" '
  !inserted && /^Match/ { print line; inserted=1 }
  { print }
  END { if (!inserted) print line }
' "$CONF" > "$CONF.new" && mv "$CONF.new" "$CONF"
Some vendor NAS units have no SFTP subsystem, so plain scp silently fails. Modern OpenSSH scp defaults to SFTP. Force the legacy protocol with command scp -O.

Verifying the fix actually works

BE WARNED: a naive test passes even when the restriction is not working. Connect while forcing a weak MAC, expect a rejection, and it connects anyway. That is because client and server negotiate an AEAD cipher like ChaCha20-Poly1305, which has an implicit MAC built in. The MACs directive is only consulted when a non-AEAD cipher is selected. Force one to test for real.
ssh -o Ciphers=aes256-ctr -o MACs=hmac-md5 <host>
# should be correctly rejected:
# "Unable to negotiate with <host> port 22: no matching MAC found"

Handling hosts with no SSH access at all

Occasionally a VM has neither a dedicated SSH key nor a working guest-agent shortcut.

Check whether the default user really is root before calling it unreachable. Password auth may be on for a non-root account nobody thought to try.

Then generate a dedicated keypair, install it once by hand, and treat that host like every other one from then on.

Passwordless sudo for automation, once

Hosts that connect as a non-root user need an interactive sudo password. No automated script can supply that.

Add a standing passwordless-sudo rule once per host instead of doing those by hand every time.

echo '<user> ALL=(ALL) NOPASSWD: ALL' | sudo tee /etc/sudoers.d/090-nopasswd
sudo chmod 0440 /etc/sudoers.d/090-nopasswd
sudo visudo -c

This is a real tradeoff. It is standing root-equivalent access. Weigh it against how often you actually run automated fleet-wide changes.

Always spot-check after restarting sshd fleet-wide. Open a fresh connection to every host and confirm it did not lock itself out. Keep the backup config file. It is your rollback, not cleanup debris.