Security & Monitoring11 min read

Linux VPS Hardening Checklist for Agencies

Your agency delivers a VPS and each technician applies "roughly the same thing" — without a written runbook, without a trace. When an incident occurs, you cannot prove that the agreed security configuration was actually applied. This guide picks up where the individual tutorial leaves off: standardisation, delegation and traceability for a portfolio of client servers.

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: auditd records 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

01

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 fail2ban

Record 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).

02

Enable automatic security updates

apt install -y unattended-upgrades
dpkg-reconfigure --priority=low unattended-upgrades

Verify 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

01

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 adminagency

Agency 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.

02

Disable root SSH login

sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
grep PermitRootLogin /etc/ssh/sshd_config

Caution: 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

01

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/.ssh

Agency 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.

02

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 sshd

CIS 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

01

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 verbose

Only 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

01

Enable fail2ban with the SSH jail

cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Edit /etc/fail2ban/jail.local and adjust the [sshd] section:

[sshd]
enabled = true
maxretry = 5
findtime = 600
bantime = 3600
systemctl enable fail2ban
systemctl start fail2ban
fail2ban-client status sshd

For 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

01

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 auditd

Create 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_commands
augenrules --load
auditctl -l

CIS 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).

02

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 rsyslog

Retain 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 10

Produce 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

CriterionAd-hoc configurationStandardised runbook
ReproducibilityDepends on the technician presentIdentical at every delivery
auditd traceabilityRarely enabled, variable configEnabled and configured systematically
Proof in case of incidentNo defensible evidenceDated delivery record + centralised logs
Delegation to a juniorRisky (possible omissions)Possible with the runbook as guide
SSH key rotationManual and forgottenWritten procedure, versioned repository
NIS2 / GDPR complianceUndocumentedDocumented 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 sshd

Error 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.

ServOrbit delivers each VPS ready for this runbook

Immediate root SSH access and an included KVM console at delivery: your agency applies this runbook from the very first connection, for every client, without going through an intermediate access request to the hosting provider. Manage your entire client VPS portfolio from a centralised agency dashboard.

Need help?

Browse our help center and FAQ, or reach our team — callback, WhatsApp or email. Support in French, English and Arabic.