A subnet-wide vulnerability scan will very likely flag weak SSH MAC algorithms on most of your fleet — it's an OS-default list, not something anyone configured on purpose. Here's how to fix it across a mix of containers, VMs, and vendor NAS units without a lot of manual per-host work.
Restrict sshd_config's MACs directive to modern, SHA-2-based algorithms only:
MACs hmac-sha2-256,hmac-sha2-512,[email protected],[email protected]
Across a whole fleet, a script that backs up before editing, validates before restarting, and restores on failure is worth the extra few lines:
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
If most of your containers never got a dedicated SSH keypair set up (only the ones that needed direct SSH
for other reasons), don't generate and authorize a new keypair for every single one just for a one-off fleet
change. If your hypervisor supports executing commands directly inside a container without SSH (Proxmox's
pct exec, for example), use that instead — it's the better default for any one-off fleet-wide
change, not just this one.
Match block breaks a naive append. Some vendor NAS
sshd_config files end with Match User blocks. Appending a new global directive at
end-of-file lands it inside the last Match block, which OpenSSH rejects outright —
global directives must 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"
scp silently fails.
Modern OpenSSH's scp defaults to the SFTP protocol. If the target doesn't support it, force the
legacy protocol instead: scp -O.
MACs directive is only consulted when a non-AEAD cipher gets 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"
Occasionally a VM will have neither a dedicated SSH key nor a working guest-agent shortcut from your
hypervisor. Check whether the host's default user really is root before concluding a host is
unreachable — password auth may be enabled with a non-root account nobody thought to try. Generate a
dedicated keypair, get it installed once (manually, if needed), and treat it like every other host from then
on.
For hosts that connect as a non-root user, sudo normally needs an interactive password — which
no automated fleet-wide script can supply. Rather than running the fix by hand on each of these individually,
consider adding a standing passwordless-sudo rule once per host, so future fleet-wide maintenance doesn't
hit the same wall again:
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 security tradeoff (standing root-equivalent access), so weigh it against how often you expect to need automated fleet-wide changes versus doing occasional fixes by hand.
sshd fleet-wide: open a fresh connection to
every host after the restart to confirm it didn't lock itself out, and keep the backup config file around —
it's a rollback point, not cleanup debris.