Tutorial

Monthly client VPS security audit: a 45-minute checklist

Security & Monitoring10 min read13 steps

A client does not ask whether their VPS is secure, they ask how long you have known. An agency without a routine can answer neither on the day a vulnerability lands nor at contract renewal. This checklist runs seven read-only checks per VPS in 45 minutes: pending updates, kernel, SSH accounts and keys, open ports, certificates, restored backups, authentication logs. It ends with a dated one-page report that you hand to the client and compare from one month to the next.

Contents· Verify a state, do not rebuild it1/14
  1. 01Verify a state, do not rebuild it
  2. 02Set the scope before the first audit
  3. 03To prepare once, before auditing
  4. 04The seven checks, with their time budget
  5. 05The 45-minute budget per VPS
  6. 06A collection script instead of seven manual sessions
  7. 07Archive and compare: that is where drift shows
  8. 08The one-page report handed to the client
  9. 09Record the deviation, do not fix it on the spot
  10. 10When an output does not say what it seems to say
  11. 11The time objection: 45 minutes per client, every month
  12. 12When the portfolio grows
  13. 13Going further
  14. 14The official documentation

Verify a state, do not rebuild it

Initial VPS hardening happens once, at delivery: SSH keys, firewall, fail2ban, automatic updates. Our hardening checklist already covers it. Here we assume all of that exists and ask whether it still holds. A package left pending, an account created for a contractor and never removed, a port opened for a test, a certificate whose renewal fails silently: none of it shows unless you look. The monthly audit is therefore a verification routine, not a configuration one. Each check produces an output that you archive, and a green, orange or red status in the client's report. A deviation is recorded, never fixed without agreement.

Set the scope before the first audit

Before the first command, write down who is responsible for what. Does the VPS belong to the client or to the agency? Who holds the root account, who receives alerts, who decides on a reboot? Without answers, the first deviation you find becomes a dispute: was the agency allowed to fix it, had the client been told? Take five points into your contract or order form: the VPS covered, the audit frequency, the read-only access granted, the response time for a red deviation, and the list of actions the agency may take alone, such as applying a security update, as opposed to those that need client approval, such as a reboot or an account removal.

To prepare once, before auditing

  • A dedicated audit account — a named user with sudo limited to reading logs and inspecting the network, rather than a shared root.
  • An expected list per VPS — ports that should listen, authorized accounts, domains served, backup tool and repository. Without a reference, a deviation cannot be told apart from normal behaviour.
  • A read-only collection script — it chains the commands of the checklist and writes everything to a dated file, so each command is typed only once.
  • An archive folder per client — one subfolder per month, kept outside the audited VPS, so months can be compared.
  • A report template — the same layout for every client, filled in from the collected outputs.
  • A calendar — a fixed day of the month per client, noted in the team agenda, otherwise the audit is skipped in the first busy month.

The seven checks, with their time budget

  1. Pending updates (6 min)

    sudo apt update
    apt list --upgradable 2>/dev/null
    apt list --upgradable 2>/dev/null | grep -i security
    Isolate packages whose origin ends in -security: they go first. The others go through human validation, because a major version jump can break an application. On AlmaLinux or RHEL, sudo dnf check-update --security plays the same role. Even with automatic updates enabled, this check shows what remains blocked.

  2. Kernel and pending reboot (4 min)

    uname -r
    ls /boot/vmlinuz-*
    ls /var/run/reboot-required
    sudo needrestart -b -r l
    If the newest installed kernel differs from uname -r, or if /var/run/reboot-required exists, a reboot is due. In the needrestart output, the NEEDRESTART-KSTA line tells whether the running kernel is current, and the list shows services still using old libraries. The reboot is never done alone: it is scheduled with the client.

  3. Accounts and SSH keys (8 min)

    awk -F: '$3>=1000' /etc/passwd
    getent group sudo
    sudo ssh-keygen -lf /home/<account>/.ssh/authorized_keys
    lastlog | grep -v 'Never logged in'
    last -n 20
    Every human account must match a person known to the client or the agency. Every key fingerprint must belong to someone still working on the project. An account that has not logged in for months, or an unknown key, turns orange and goes to the client for validation.

  4. Open ports (5 min)

    sudo ss -tlnp
    sudo ss -ulnp
    sudo ufw status verbose
    Compare the result with the list expected for this VPS. A database listening on all interfaces instead of the loopback, a forgotten test port, an unknown service: each deviation is recorded with the process that opens it, without closing it immediately, because a service may depend on that port.

  5. Certificates (4 min)

    openssl s_client -connect your-domain.com:443 -servername your-domain.com </dev/null 2>/dev/null | openssl x509 -noout -enddate
    Read the expiry date on the service actually exposed, not just the presence of a renewal timer: an active timer can fail every night. If the domain goes through a CDN, this command reads the CDN certificate. For the origin, replace the host in -connect with the VPS IP address and keep -servername.

  6. Restored backups (10 min)

    restic snapshots
    restic restore latest --target /tmp/restore-test --include /etc/nginx
    Note the date of the latest restore point, then actually restore a file or a database into a temporary directory and open it. A backup that was never restored is not proof, it is a hypothesis. Delete the temporary directory afterwards. If the client uses another tool, the logic is the same: list the points, partial restore, check the content.

  7. Authentication logs (8 min)

    sudo journalctl -u ssh --since '30 days ago' --no-pager | grep -c 'Failed password'
    sudo journalctl -u ssh --since '30 days ago' --no-pager | grep 'Accepted'
    sudo journalctl _COMM=sudo --since '30 days ago' --no-pager
    Count the failures, list the successful logins and look for those from an unknown address. Review sudo usage. On RHEL and derivatives, the unit is called sshd, not ssh.

The 45-minute budget per VPS

Scroll the table

CheckKey commandTime
Pending updates`apt list --upgradable`6 min
Kernel and reboot`needrestart -b -r l`4 min
Accounts and SSH keys`ssh-keygen -lf authorized_keys`8 min
Open ports`ss -tlnp`5 min
Certificates`openssl s_client`4 min
Restored backups`restic restore`10 min
Authentication logs`journalctl -u ssh`8 min

A collection script instead of seven manual sessions

Most of the 45 minutes is reading, not typing, provided you do not retype the commands. Write an audit-vps script that runs the commands of checks one to seven and redirects each output to a file named after the client and the date, for example ~/audits/client-a/YYYY-MM/raw-output.txt. It contains only read commands: no apt upgrade, no systemctl restart, no rm. Run it over SSH from your workstation, fetch the file, then read it calmly. The backup restore stays the only manual step, because it requires judging whether the restored content is consistent.

Archive and compare: that is where drift shows

A single audit gives a snapshot, two consecutive audits give a trend. Keep the raw output of each month, then compare it with the previous one using diff -u previous-audit.txt current-audit.txt. A port that appears, an extra account, a number of failed logins multiplied by ten, a restore point that no longer moves: all these signals stand out in a few lines of difference, while they are invisible in an output read alone. Also archive the report handed to the client. The day the client asks since when an account existed or when a fix was applied, the answer is a dated file, not a memory.

The one-page report handed to the client

  1. The header

    Client name, VPS identifier, IP address, audit date, name of the person who ran it.

  2. The table of the seven checks

    One line per check with a status: green if nothing to report, orange if a deviation needs a client decision, red if urgent action is needed.

  3. The deviations found

    Each deviation in one factual sentence, with the evidence: the output line, the port, the account concerned.

  4. The actions taken

    What the agency applied alone, within the agreed scope, for example a security update.

  5. The actions for the client to approve

    Reboot, account removal, closing a port, with the risk of doing nothing.

  6. The next due date

    The date of the next audit and, for each orange or red deviation, a deadline for handling it.

Record the deviation, do not fix it on the spot

The reflex to close the port or delete the suspicious account during the audit is the most expensive one: a client service goes down, and the agency decided it alone. The role of the audit is to make the deviation visible and dated. The fix follows the approval path agreed at the start, even for what looks obvious.

When an output does not say what it seems to say

  • needrestart not found — the package is not installed by default everywhere. Install it once with the client's agreement, or compare uname -r and the kernels in /boot by hand.
  • No SSH failures in the logs — check the unit name before concluding. Depending on the distribution, authentication is logged under ssh, sshd or, on recent OpenSSH versions, sshd-session. Try sudo journalctl _COMM=sshd-session --since '30 days ago'.
  • ss -tlnp does not show processes — without sudo, the process column stays empty for other users' services.
  • A valid certificate while the site shows an error — you read the one from a CDN or another vhost. Point at the VPS address and pass the right -servername.
  • restic asks for a password — the repository is encrypted. Get the secret from the client before the audit, not during it, otherwise the check is skipped.

The time objection: 45 minutes per client, every month

Twenty clients make fifteen hours a month, and the objection is fair. Three levers reduce it. First, time is measured per VPS, not per client: a client with a single server costs 45 minutes, and less from the second month, because the expected references are in place. Second, the script collects everything, you only read, and the report is filled in from a template. Finally, compare with the cost of an undetected incident: a forgotten access or an unusable backup is discovered on the day of the outage, with an unhappy client and a recovery bill. The routine costs less than a single avoidable incident.

When the portfolio grows

At ten VPS, the checklist fits in a day. At fifty, the difficulty becomes organisational: knowing which client has what, which due dates fall this month, who has been audited. The ServOrbit.com agency space gathers each client's services and due dates in a single interface, which avoids maintaining a parallel spreadsheet. If the agency prefers not to carry the system base itself, the VPS administration option exists and is looked at separately. This guide is provided as is, without support: running the audit and the responsibility towards the client remain yours.

Going further

  • Set up the base — /blog/linux-hardening-vps-checklist describes the initial hardening that this audit verifies.
  • Automate fixes — /blog/securite-vps-mises-a-jour-automatiques-debian-ubuntu explains unattended-upgrades, whose leftovers the first check measures.
  • Back up properly — /blog/backup-vps-restic-s3-3-2-1 covers setting up Restic, whose restore the sixth check verifies.
  • Monitor continuously — /blog/alertes-disque-ram-vps-monitoring-proactif complements the monthly audit with alerts between two passes.
  • Inventory PHP versions — /blog/inventaire-versions-php-parc-sites-clients-agence applies the same discipline to the site fleet.
  • Respond to a vulnerability — the fix guides in the Security and monitoring category of the blog show what an up-to-date inventory lets you do on day one.

The official documentation

apt options: https://manpages.debian.org/stable/apt/apt.8.en.html

needrestart project: https://github.com/liske/needrestart

Automatic updates on Debian: https://wiki.debian.org/UnattendedUpgrades

ss command: https://man7.org/linux/man-pages/man8/ss.8.html

openssl s_client command: https://docs.openssl.org/master/man1/openssl-s_client/

journalctl command: https://www.freedesktop.org/software/systemd/man/latest/journalctl.html

sshd daemon: https://man.openbsd.org/sshd.8

last command: https://man7.org/linux/man-pages/man1/last.1.html

A portfolio of VPS readable from one place

Auditing a fleet by hand becomes unmanageable as the portfolio grows. The ServOrbit.com agency space gathers each client's services and due dates in a single interface, and the VPS administration option lets you hand over the system base.

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