SSH Hardening
SSH Hardening
Migration in progress.
api.legendary-arena.comand its PostgreSQL are moving off Render onto a self-hosted DigitalOcean Ubuntu droplet fronted by Cloudflare, perdocs/PLAN.md. Until decommission, Render stays warm as the rollback target.
Summary
SSH Hardening locks remote access to the droplet down to key-only
login for a non-root operator account, disabling root SSH and
password authentication. It is the first hardening step after
first-boot provisioning and gates every later phase — nothing else is
safe to run until remote access is both locked and verified.
Mechanics
The entity is owned by
infra/scripts/05-user-and-ssh.ps1
,
an idempotent PowerShell provisioner that must run as root. It does
three things:
Ensures the operator account. Creates the user named by
OPERATOR_USER(defaultoperator) if absent (useradd -m -s /bin/bash) and adds it to thesudogroup. Re-running is safe — an existing user is left in place and re-added tosudoidempotently.Writes a managed drop-in, not an inline edit of the distro default. The file
/etc/ssh/sshd_config.d/99-la-hardening.confholds the full hardened directive set:PermitRootLogin no PasswordAuthentication no ChallengeResponseAuthentication no KbdInteractiveAuthentication no UsePAM yes PubkeyAuthentication yesUsing a numbered drop-in (
99-) keeps the change explicit, auditable, and re-appliable without touching/etc/ssh/sshd_config.Validates and reloads. Runs
sshd -tto reject a broken config before reloading, then reloads whichever unit exists (ssh.serviceorsshd.service). The script’s final log line is the safety instruction: open a new SSH session and verify login before closing existing ones.
The operator’s public key arrives earlier, at first boot, via
infra/cloud-init.yaml
(the
ssh_authorized_keys block on the operator user) — see
Provision Droplet
. This page assumes that key
is already present.
Interactions
- Provision Droplet
. Cloud-init creates
the
operatoruser and installs its authorized key at first boot; this script hardens the daemon that key authenticates against. Theoperatoraccount is the shared identity both steps configure. - UFW Firewall
. Runs next. UFW’s
allow OpenSSH/allow 22/tcprule must be in place before default-deny is enabled, or the hardened SSH path is firewalled off. The two steps are a pair: harden the daemon, then fence the port. - Render-to-DigitalOcean Migration . Access hardening is Phase 1 of the migration sequence; its exit criterion is “operator access verified from a clean shell.”
Edge Cases
- Lockout is the primary hazard. Disabling
PasswordAuthenticationandPermitRootLoginwhile your only session is a password/root session can strand you. The runbook’s safety sequence — keep two sessions open, run in one, verify a third fresh login before closing any — exists to prevent exactly this. See PLAN.md §5.1 D . - Missing authorized key. If cloud-init’s key placeholder was
never replaced, key auth cannot succeed and disabling passwords
locks everyone out. Verify
~operator/.ssh/authorized_keysis populated before running this script. - Service unit name varies. Ubuntu ships the SSH unit as
ssh.service; some images usesshd.service. The script probes for both and throws if neither is found rather than silently skipping the reload. - Non-standard SSH port. This script hardens auth but does not
change the port. If you move SSH off 22, the matching allow rule
lives in UFW Firewall
via its
SSH_PORToverride — keep the two in sync.
Execute
Run as root on the droplet:
sudo pwsh -File infra/scripts/05-user-and-ssh.ps1
Optional operator-username override:
sudo OPERATOR_USER=operator pwsh -File infra/scripts/05-user-and-ssh.ps1
The copy-paste operator runbook is
content/foundations/ssh-hardening.md
.
Verify
# confirm the hardened directives are in place
sudo grep -E '^(PermitRootLogin|PasswordAuthentication)' \
/etc/ssh/sshd_config.d/99-la-hardening.conf
# config must parse clean
sudo sshd -t
# service must be healthy
sudo systemctl status ssh --no-pager
Expected: PermitRootLogin no, PasswordAuthentication no, sshd -t
exits zero, and a new SSH session as operator logs in by key.
References
- infra/scripts/05-user-and-ssh.ps1 — the owning provisioner
- docs/PLAN.md §4.1
— the
ssh-hardeningpage stub and hardening block - docs/PLAN.md §5.1 D — the access-hardening safety sequence
- content/foundations/ssh-hardening.md — the operator runbook
- infra/cloud-init.yaml — where the operator user and authorized key are first created