Why a formalised runbook changes everything for an agency
An individual developer can mentally apply five commands after each delivery. An agency cannot: multiple technicians, dozens of client VPS servers, rotating on-call shifts. When a client reports suspicious access six months after production launch, the question is not "did you secure the server?" — it's "can you prove it?"
Traceability has become a contractual and regulatory requirement. The NIS2 directive (national transposition underway across the EU) and GDPR require service providers handling personal data to document the security measures they have implemented. Without a versioned runbook and auditd logs, your agency has no defensible evidence.
This guide does not re-describe installing fail2ban or UFW — the articles Protecting a VPS with fail2ban and UFW firewall on VPS cover that in detail. It operates at a higher level: how to standardise, delegate and trace these actions across an entire client portfolio.
What this runbook brings to your agency
- Reproducibility: every technician follows exactly the same steps, in the same order, on each new client VPS.
- Traceability:
auditdrecords who did what, when, and which command — preservable proof for incidents or client audits. - Safe delegation: a junior can deliver a hardened server without improvising, because the runbook already contains the decisions.
- Contractual defence: a security clause in your maintenance contract is only enforceable with a dated execution log.
- Reduced attack surface: the checklist eliminates classic omissions — root SSH left open, auditd not activated — the most commonly exploited entry points.
- Cross-client consistency: same secure baseline for all clients, differences only where the client's specification requires them.
Prerequisites and scope of the checklist
This checklist targets Ubuntu 24.04 LTS and Debian 12 (Bookworm), the two most common distributions on VPS servers in 2026. Commands have been verified on these systems; on AlmaLinux or Rocky Linux, package names and service paths differ.
Minimum VPS requirements: 1 vCPU, 1 GB RAM (2 GB recommended when fail2ban and auditd run together), 20 GB SSD. ServOrbit delivers each VPS with immediate root SSH access and an included KVM console, allowing your agency to apply this runbook from the very first minute — without going through an intermediate access request to the hosting provider.
The checklist is organised into seven blocks, in application order. It stops at the basic system scope: application security (exposed backoffice, application attack surfaces) is covered separately in Exposed backoffice: overlooked attack surfaces on VPS.
Block 1 — Updates and initial inventory
System update and inventory
The first action after root login is to bring the system to its current patch level.
apt update && apt upgrade -y
apt install -y auditd audispd-plugins curl gnupg2 ufw fail2banRecord in your runbook: the date, kernel version (uname -r), and the list of installed packages (dpkg -l > /root/inventory-$(date +%F).txt). This inventory file constitutes the server's baseline at delivery — it is the evidence you produce if a client disputes the initial state of their machine.
CIS Benchmark reference: CIS control 1.1 (Ubuntu Linux 24.04 LTS Benchmark, "Initial Setup" section).
Enable automatic security updates
apt install -y unattended-upgrades
dpkg-reconfigure --priority=low unattended-upgradesVerify that the Unattended-Upgrade::Allowed-Origins line includes ${distro_id}:${distro_codename}-security. For a client portfolio, enable automatic updates and schedule a monthly review of non-security updates — those require human validation.
CIS reference: CIS control 1.9 (Ensure updates, patches, and additional security software are installed).
Block 2 — Sudo user and root lockout
Create a dedicated administration account
Never use root for day-to-day tasks. Create a named account per technician or an agency service account:
useradd -m -s /bin/bash adminagency
usermod -aG sudo adminagency
passwd adminagencyAgency tip: use an identifiable account name in logs (john.doe or agency-admin), not a generic admin. When auditd traces an action, the executing account is recorded — a generic name makes the audit unusable.
Disable root SSH login
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
grep PermitRootLogin /etc/ssh/sshd_configCaution: only reload sshd after verifying that your sudo user can connect and run sudo su. Otherwise you will lock yourself out of the server.
CIS reference: CIS control 5.2.8 (Ensure SSH root login is disabled).
Block 3 — SSH key authentication
Deploy the agency public key
mkdir -p /home/adminagency/.ssh
chmod 700 /home/adminagency/.ssh
echo "<AGENCY_PUBLIC_KEY>" >> /home/adminagency/.ssh/authorized_keys
chmod 600 /home/adminagency/.ssh/authorized_keys
chown -R adminagency:adminagency /home/adminagency/.sshAgency tip: maintain a versioned public key repository (one file per technician, annual rotation). Every key deployed on a client VPS must be referenced in this repository — that is what allows you to revoke access when a team member leaves.
Disable password authentication
Once the key is verified (test from another terminal before confirming):
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/^#\?ChallengeResponseAuthentication.*/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config
systemctl reload sshdCIS reference: CIS control 5.2.11 (Ensure only approved MAC algorithms are used) and CIS 5.2.19 (Ensure SSH PasswordAuthentication is disabled).
Block 4 — UFW firewall
Configure UFW with a default-deny policy
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp comment 'Agency SSH'
ufw allow 80/tcp comment 'HTTP'
ufw allow 443/tcp comment 'HTTPS'
ufw --force enable
ufw status verboseOnly open ports actually used by the client project. If the client application does not use a direct mail port, do not open it.
For fail2ban installation and advanced UFW options, see the dedicated guides: Protecting a VPS with fail2ban and UFW firewall on VPS.
CIS reference: CIS control 3.5.1 (Ensure a firewall package is installed).
Block 5 — fail2ban
Enable fail2ban with the SSH jail
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.localEdit /etc/fail2ban/jail.local and adjust the [sshd] section:
[sshd]
enabled = true
maxretry = 5
findtime = 600
bantime = 3600systemctl enable fail2ban
systemctl start fail2ban
fail2ban-client status sshdFor a client portfolio, consider CrowdSec as a replacement or complement: its community blocklist pools intelligence from thousands of servers.
Block 6 — auditd: the traceability log
Enable auditd and configure minimum rules
auditd is the component agencies most often forget — yet it is the one that provides defensible proof in the event of an incident.
systemctl enable auditd
systemctl start auditdCreate an agency rules file at /etc/audit/rules.d/agency.rules:
# Trace all privilege escalations
-w /etc/sudoers -p wa -k sudoers_changes
-w /etc/sudoers.d/ -p wa -k sudoers_changes
# Trace SSH connections
-w /var/log/auth.log -p wa -k auth_log
# Trace changes to system configuration files
-w /etc/ssh/sshd_config -p wa -k sshd_config
-w /etc/passwd -p wa -k passwd_changes
-w /etc/shadow -p wa -k shadow_changes
# Trace commands executed as root
-a always,exit -F arch=b64 -S execve -F euid=0 -k root_commandsaugenrules --load
auditctl -lCIS reference: CIS control 4.1.1 (Ensure auditing is enabled) and CIS 4.1.3 (Ensure events that modify date and time information are collected).
Export and retain auditd logs
auditd logs must be retained off the server to be defensible. Configure an export to your centralised system (rsyslog, Loki, or a simple daily rsync to agency storage):
# Example: rsyslog export to agency collector
echo ':programname, isequal, "auditd" @<AGENCY_COLLECTOR_IP>:514' \
>> /etc/rsyslog.d/99-auditd-remote.conf
systemctl restart rsyslogRetain at least 90 days of logs. GDPR does not set a minimum retention period for security logs, but frameworks like CIS Controls v8 (control 8.3) recommend at least 90 days on-site and 1 year in cold archive.
Block 7 — Final verification and delivery record
Once the previous six blocks are complete, audit the server state before handing access to the client:
# Check active services
systemctl is-active ufw fail2ban auditd sshd
# Confirm root cannot connect via SSH
grep PermitRootLogin /etc/ssh/sshd_config
# Check UFW rules
ufw status verbose
# Check loaded auditd rules
auditctl -l
# Check recent logins
last -n 10Produce a dated and signed delivery record (even by email) listing the measures applied, the versions of installed security packages, and the location of the log collector. This is the document your client signs and which constitutes contractual proof of the initial configuration.
The delivery record is not a formality: a client who suffers an intrusion two years after go-live will ask your agency to justify the server's state at delivery. Without this document, the burden of proof shifts.
Agency runbook vs. ad-hoc configuration: what changes
| Criterion | Ad-hoc configuration | Standardised runbook |
|---|---|---|
| Reproducibility | Depends on the technician present | Identical at every delivery |
| auditd traceability | Rarely enabled, variable config | Enabled and configured systematically |
| Proof in case of incident | No defensible evidence | Dated delivery record + centralised logs |
| Delegation to a junior | Risky (possible omissions) | Possible with the runbook as guide |
| SSH key rotation | Manual and forgotten | Written procedure, versioned repository |
| NIS2 / GDPR compliance | Undocumented | Documented and reproducible |
Monthly audit tip. Once a month, re-run the final checks from Block 7 on each active client VPS and archive the output. A diff between two consecutive audits immediately reveals an unauthorised change: an extra open port, a stopped fail2ban service, a missing auditd rule. This audit takes less than five minutes per server when the commands are in a script.
Troubleshooting: common errors when applying the runbook
Error 1 — sshd refuses to start after modifying sshd_config
Message: sshd: /etc/ssh/sshd_config line 42: unsupported option
Cause: a deprecated or malformed directive. Verify with sshd -t before reloading the service. On Ubuntu 24.04, ChallengeResponseAuthentication is replaced by KbdInteractiveAuthentication.
sshd -t && systemctl reload sshdError 2 — UFW blocks your own SSH connection
Message: the SSH session freezes after ufw enable.
Cause: the SSH rule was not added before activation. Use the ServOrbit KVM console to access the server without SSH, then run ufw allow 22/tcp and ufw reload.
Error 3 — auditd starts but auditctl -l returns an empty list
Message: List of rules: followed by nothing.
Cause: the rules file is not loaded. Verify your .rules file is in /etc/audit/rules.d/ and run augenrules --load.
Error 4 — fail2ban does not ban despite multiple failed attempts
Message: fail2ban-client status sshd shows Currently banned: 0 after 10 attempts.
Cause: the SSH journal path has changed. On Debian 12 and Ubuntu 24.04 with journald, the fail2ban backend must be systemd in jail.local: backend = systemd.
Error 5 — unattended-upgrades restarts production services
Cause: the default configuration automatically restarts services after updates. For a production client VPS, disable automatic reboot and schedule a maintenance window:
# In /etc/apt/apt.conf.d/50unattended-upgrades
Unattended-Upgrade::Automatic-Reboot "false";Going further: bastioning and continuous monitoring
This checklist covers level 1 of system hardening. For a client portfolio requiring a higher security level:
- SSH bastion: centralise all access through a single hardened entry point — see Bastion Host on VPS.
- CrowdSec: behavioural detection and community blocklist as a complement or replacement for fail2ban — see Securing your VPS with CrowdSec.
- Backups: security configuration does not protect against data loss — see Backups with BorgBackup on VPS.
- Application patches: system hardening is useless if self-hosted applications are not maintained — see Patch routine for self-hosted apps.
- SSL certificates: HTTPS is required before any public exposure — see SSL certificates with Let's Encrypt on VPS.