Tutorial

Linux VPS Hardening Checklist for Agencies

Security & Monitoring11 min read10 steps

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.

Contents· Why a formalised runbook changes everything for an agency1/13
  1. 01Why a formalised runbook changes everything for an agency
  2. 02What this runbook brings to your agency
  3. 03Prerequisites and scope of the checklist
  4. 04Block 1 — Updates and initial inventory
  5. 05Block 2 — Sudo user and root lockout
  6. 06Block 3 — SSH key authentication
  7. 07Block 4 — UFW firewall
  8. 08Block 5 — fail2ban
  9. 09Block 6 — auditd: the traceability log
  10. 10Block 7 — Final verification and delivery record
  11. 11Agency runbook vs. ad-hoc configuration: what changes
  12. 12Troubleshooting: common errors when applying the runbook
  13. 13Going further: bastioning and continuous monitoring

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

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

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

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

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

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

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

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

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

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

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

Scroll the table

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.

Message us on WhatsAppopens in a new tab