Save 15% on All Hosting Services

Test your skills and get Discount on any hosting plan

Use code: Skills Get Started
FAQ’s Sections
Administration Virtual Servers

How to Audit a Linux VPS with vps-audit—and Read the Results Correctly

A Fast VPS Audit Is a Starting Point, Not a Verdict

Your website loads and SSH answers, but that does not reveal pending updates, permissive SSH settings, or unexpected listeners. A first-pass audit surfaces those questions.

vps-audit is a Bash checklist for Debian and Ubuntu that turns local configuration, maintenance, listener, and resource signals into a color-coded report. Like a vehicle dashboard, it points to areas that need inspection without diagnosing every cause.

This guide safely runs pinned vps-audit v0.2.0, checks important results with native tools, and turns them into priorities. The example uses one AlexHost Ubuntu VPS running Ubuntu 24.04 LTS. Other provider images may differ, and the unmanaged guest OS remains the operator’s responsibility.

administrator reviewing a secure server environment

What vps-audit Checks—and What It Cannot Prove

vps-audit checks local indicators without examining any area exhaustively. This table shows what each result can—and cannot—tell you.

DomainQuestion the script asksWhat the result cannot prove
🔐 Remote accessDo parsed SSH settings, Fail2ban/CrowdSec state, jail-port alignment, and failed authentication counts match its rules?That every authentication path is hardened or that attempts represent a breach.
🌐 Network exposureWhat host-firewall frontend and local listening ports can the script detect?Which services are reachable from the internet through every firewall and NAT layer.
🔄 MaintenanceIs a restart pending, does cached package data show upgrades, and is unattended-upgrades installed?That every security update is installed or automatic updates run successfully.
🛡️ Privilege and policyDoes it find a dedicated sudo logfile, one password-length value, and unusual SUID files?That privilege controls are complete or that an SUID file is malicious.
📊 Operational snapshotHow many services run, and what do disk, memory, CPU, load, OS, kernel, and uptime look like now?Long-term capacity, availability, or performance trends.

SUID lets a program run with the file owner’s effective privileges. Legitimate system programs use it, so investigate an unexpected SUID file rather than deleting it. Fail2ban and CrowdSec can block hostile traffic, but installation or active status alone does not prove they protect the intended service.

The script applies generic thresholds to resource use, services, failed logins, and listeners. These are not workload-aware risk scores; different VPS roles can reach the same color for different reasons.

The tool does not inspect malware, known vulnerabilities, applications, containers, provider firewalls, compliance, or trends. Although its README mentions “Active Internet Connections,” v0.2.0 only fetches the public IP and inventories local listeners. Because it needs privileged visibility, first control which file receives sudo access.

Before You Give a Downloaded Script sudo

This Debian/Ubuntu workflow requires SSH access, sudo, and the standard tools used below. Work in a disposable directory. If a tool is missing, stop rather than change the baseline by installing it.

The example uses vps-audit v0.2.0, published on 10 August 2026 and still latest when checked on 8 September 2026.

Its tag points to commit 57c323d46b48026740f0b35b9bad6cd6127c757b. Pinning avoids a later change from mutable main.

Create a dedicated directory and download that exact tagged script over HTTPS:

mkdir -p "$HOME/vps-audit-test"
cd "$HOME/vps-audit-test"
curl -fL --proto '=https' --tlsv1.2 \
  -o vps-audit.sh \
  https://raw.githubusercontent.com/nuver-labs/vps-audit/v0.2.0/vps-audit.sh

curl

This saves vps-audit.sh in the new directory. The -f option fails on HTTP errors, while -L follows redirects.

Next, record a local SHA-256 fingerprint and display the settings relevant to this walkthrough:

sha256sum vps-audit.sh
grep -nE '^(VPS_AUDIT_VERSION|RESOURCE_(WARN|FAIL)|SERVICES_(WARN|FAIL)|LOGINS_(WARN|FAIL)|OPEN_PORTS_(WARN|FAIL)|PASSWORD_MINLEN|DEFAULT_REPORT_DIR|ENABLE_CHOWN)=|api\.ipify\.org' vps-audit.sh

sha

The output fingerprints the file and shows its report path, thresholds, and request to api.ipify.org. The complete tagged source also reads local state, simulates apt-get -s upgrade, and searches recursively for SUID files. It is not read-only: it writes a report, may create its directory, and contacts an external service. Review the source before granting elevated privileges if you can read shell code.

Resource thresholds are 50% for WARN and 80% for FAIL. Running services use 20/40, failed logins 10/50, listeners nominally 10/20, and password length 12. The limitations section explains why listener status does not follow those variables.

⚠️ Warning: Pinning, hashing, targeted inspection, and syntax checking improve reproducibility but do not establish trust. The commit is unsigned, and the release provides no checksum or signature asset.

Keep the hash with the audit notes. Before a run, compare it with the file. A match shows that copies contain the same bytes. A difference may come from another release, a changed download, or a local edit. Recording the tag and hash links each report to the script that produced it.

Finally, parse the file without running its normal commands, then add execute permission only if parsing succeeds:

bash -n vps-audit.sh \
  && chmod +x vps-audit.sh \
  && printf 'Syntax check: PASS; execute permission added\n'

bash

This confirms only that Bash can parse the file and that execute permission was added. The pinned script is now ready for one unchanged run.

Run vps-audit and Locate the Report

The script prints system details and color statuses, then writes a plain-text report. Its recursive SUID search makes runtime variable, so measure it.

Run the pinned file once and preserve the shell process’s exit status:

printf 'Audit started: '
date -u '+%Y-%m-%d %H:%M:%S UTC'
TIMEFORMAT=$'Elapsed real: %3R seconds\nUser CPU: %3U seconds\nSystem CPU: %3S seconds'
time sudo ./vps-audit.sh
AUDIT_STATUS=$?
printf 'Audit exit status: %s\n' "$AUDIT_STATUS"

On the tested VPS, the audit started at 13:12:10 UTC on 14 September 2026 and finished in 58.412 seconds. It returned 0 and saved ./vps-audit-report-20260914_131210.txt.

vps-audit v0.2.0 starting with a recorded UTC timestamp

Selected PASS, WARN, and FAIL results followed by the report path, runtime, and exit status

Elapsed real is wall-clock time; user and system values measure CPU time. Exit status 0 means the process completed, not that every check passed. v0.2.0 returns 0 even with FAIL results.

Select the new report, inspect its metadata, and extract counts and examples without displaying the sensitive file in full.

REPORT=$(ls -1t ./vps-audit-report-*.txt 2>/dev/null | head -n 1)
if [ -z "$REPORT" ]; then
    printf 'No vps-audit report found in the current directory.\n' >&2
    exit 1
fi
printf 'Report selected: %s\n' "$REPORT"
sudo stat --format='Owner: %U:%G | Mode: %A (%a) | Size: %s bytes | Modified: %y' "$REPORT"

for status in PASS WARN FAIL; do
    count=$(sudo grep -c "^\\[$status\\]" "$REPORT" || true)
    printf '%s: %s\n' "$status" "$count"
done

sudo grep -E '^\[(PASS|WARN|FAIL)\] (Running Services|Disk Usage|Password Policy)' "$REPORT"

Selected report metadata, PASS-WARN-FAIL totals, and one observed example of each state

The report’s 13:13:08 UTC modification time matched the run. It contained 17 results: six PASS, three WARN, and eight FAIL. These are classifications, not a security score.

Status totals are useful when comparing runs of the same version, but always inspect the lines behind a change. A lower FAIL count may come from different input or parser behavior rather than an improvement. An unchanged total can also hide one resolved problem and one new one.

The 2,665-byte report belonged to root:root with mode 644 (-rw-r--r--), as expected with sudo and stock ENABLE_CHOWN=false. Group and other users can read that mode if directory permissions let them reach the file. Root ownership alone does not make it private.

Important: The report contains the hostname, public IP, system details, and findings. Keep it private, and redact identifiers, prompts, and sensitive service information before sharing.

If a future run lacks one status, record that absence rather than reconfigure the VPS to manufacture a color.

How to Read PASS, WARN, and FAIL Without Overreacting

The dashboard labels report how each test matched v0.2.0’s rules:

LabelCorrect readingWhat it does not prove
PASSThe observed value matched this rule’s expectation.That the service or VPS is secure.
WARNThe value crossed a review threshold or produced a contextual signal.That a vulnerability exists.
FAILThe rule found a stronger mismatch with its built-in expectation.That compromise occurred or an immediate change is correct.

Separate the observation from the recommendation. In “22 services running,” the count is the observation; “reduce attack surface” is advice based on a generic threshold. Confirm the count, identify the services, and then decide whether that advice fits the server.

Ask three questions of every result: Is the value accurate? Is it intentional? What is the realistic impact? Native commands check the value; workload context determines the rest.

person

The SSH-port WARN is policy-based: v0.2.0 flags port 22. Moving SSH may reduce automated noise but cannot replace strong authentication or access controls. A known service on port 22 may matter less than an unknown wildcard listener.

For a FAIL, inspect the rule before proposing a fix. The root-login test accepts only PermitRootLogin no, so the distinct prohibit-password setting still fails. Check OpenSSH directly before acting.

A PASS also needs context. For unattended-upgrades, the script confirms only that the package exists—not its configuration or run history.

Verify High-Impact Findings with Native Commands

Use read-only native commands to check SSH access, host filtering, and local listeners. First, ask what OpenSSH actually resolves after defaults and included configuration are combined:

sudo sshd -T \
  | grep -E '^(port|listenaddress|permitrootlogin|passwordauthentication|kbdinteractiveauthentication|pubkeyauthentication) '

Effective OpenSSH port, listening addresses, and authentication settings

Ubuntu loads /etc/ssh/sshd_config.d/*.conf near the start of its main configuration. sshd -T resolves the combined settings, making it stronger evidence than grepping one file.

OpenSSH resolved port 22 on wildcard IPv4 and IPv6 addresses. It also returned permitrootlogin yes, passwordauthentication yes, pubkeyauthentication yes, and kbdinteractiveauthentication no. The resolved configuration confirms the script’s root-login and password-authentication findings, although account state, PAM, and Match rules can still affect a specific login.

Second, ask what UFW itself reports about its state and managed policy:

sudo ufw status verbose

Active UFW status, default policies, and allowed inbound ports

Ubuntu documents UFW as its default firewall frontend. Here it was active with low-level logging and default-deny policies for incoming and routed traffic. Rules allowed inbound ports 22, 80, 443, and 37985 over IPv4 and IPv6. This confirms UFW’s state, not whether each rule is appropriate.

Third, inventory local TCP and UDP listeners, bind addresses, and owning processes:

sudo ss -lntup

TCP and UDP listeners with loopback and wildcard bind addresses

The options select numeric TCP and UDP listeners and request process details. Ports 53, 62789, 8404, and 11111 were loopback-only; ports 22, 80, 2096, 5678, and 37985 used wildcard addresses. No process details appeared, so their owners and purposes remain unknown.

process listener
    → bind address / interface
    → host firewall
    → provider-edge firewall or NAT
    → external network path

Ports 22, 80, and 37985 had both wildcard listeners and UFW allow rules. UFW allowed 443 without a listener, while 2096 and 5678 had listeners without displayed allow rules.

A firewall rule and a listener answer different questions. The rule permits traffic if a service is there to accept it; the listener shows a service waiting, but not whether network traffic can reach it. Reading both together narrows the investigation without claiming external exposure.

📝 Note: ss shows local bind state, and UFW shows one host firewall. Neither proves Internet reachability across provider firewalls or NAT; that requires authorized testing from another system.

v0.2.0 nevertheless labels the same list “Total” and “Public” after discarding bind addresses, even though four of nine TCP ports were loopback-only. Its LISTEN filter also misses UDP rows marked UNCONN. Read this result as a local TCP port count, not public exposure.

Turn Verified Findings into a Practical Action Queue

Set priority by confidence, exposure, impact, and intent. Priority 1 covers confirmed weaknesses that need action. Priority 2 covers important findings that still need investigation, while Priority 3 covers lower-risk or policy-driven items. If a setting is intentional, document why, any compensating control, and when to review it.

The table applies that approach to this run; incomplete evidence keeps a priority tentative:

FindingWhat is knownPriorityNext step
Root login and SSH password authentication enabledConfirmed by sshd -TPriority 1 unless explicitly requiredFollow a separate SSH-hardening procedure with tested recovery access.
Port 37985 may be reachableWildcard listener and UFW rule; owner and external path unknownPriority 2; Priority 1 if unintended and reachableIdentify the service and check provider controls and external reachability.
Ports 2096 and 5678 are unexplainedWildcard listeners; no displayed UFW rules or process detailsPriority 2 until identifiedMap each socket to its service, owner, purpose, and dependencies.
16,051 failed logins reportedLog source, period, and patterns not verifiedPriority 2; escalate evidence of compromiseReview stored authentication logs separately.
12 upgrades and a restart reportedSecurity relevance not verifiedPriority 1–2 based on exposure and impactReview package metadata and plan an application-aware maintenance window.
Sudo logging and password policy failedActual logging and authentication policy not verifiedPriority 3 unless stronger evidence raises riskCheck the real configuration and document any intentional exception.

person choosing among paths at a decision signpost

Priority 2 does not mean harmless; evidence remains incomplete. Give each unresolved item an owner and deadline. Promote it if verification confirms exposure or weakness. If it is intentional and controlled, record the decision clearly.

The report label does not set the order: verification and context do.

⚠️ Warning: Do not change SSH authentication or remote firewall rules from this command sequence. A mistake can lock you out. Before remediation, confirm tested key access and validate the new configuration. Keep a second session open and ensure console or recovery access works.

Handle each remediation as a separate workflow. Map dependencies before stopping services, classify updates before scheduling them, and verify ownership and checksums before changing SUID permissions.

Where vps-audit’s View Stops

vps-audit v0.2.0 is a point-in-time Bash checklist. It cannot establish external exposure, detect vulnerabilities or malware, inspect workloads, analyze stored logs, or provide continuous monitoring. It is not a penetration test or CIS Benchmark assessment.

person completing a checklist for the next audit cycle

The code adds important caveats.

  • Port status becomes PASS below three parsed TCP ports, WARN at three or four, and FAIL at five or more—despite the nominal 10/20 variables.
  • An unattended-upgrades PASS checks only for the package, not its configuration, timer, or run history.
  • The update test uses cached metadata for a general apt-get -s upgrade, then calls every listed package a “security update.”

The sudo-logging test reads only /etc/sudoers, missing /etc/sudoers.d/ and normal journal or syslog records. Issue #33, open when checked on 8 September 2026, documents this false FAIL on Ubuntu 20.04 and 24.04. Parsing can also fail with non-English output, as tracked in open issue #37. Despite its README wording, this release does not list established connections.

Widen the review when needed. Suspicious behavior calls for stored-log and workload analysis. For an important VPS, confirm tested backups and consider authorized external testing. Critical or regulated systems may warrant Lynis, the Ubuntu 24.04 CIS Benchmark, or professional review.

Bottom Line: Verify, Prioritize, and Recheck

person completing a checklist for the next audit cycle

Keep the original report private and maintain a redacted copy. Record the script’s SHA-256, tag, and commit together with the run time, intended services, verification results, and action queue.

  1. Verify high-impact findings with native commands before changing the server.
  2. Fix confirmed high-risk problems safely, investigate unknowns, and document intentional exceptions.
  3. Rerun the same pinned version after changes or on a schedule, then compare reports manually.

When comparing reports, focus on authentication changes, firewall rules, listeners, and resolved findings. Timestamps and resource readings will move. Note deliberate changes so the next reviewer understands why results differ.

vps-audit has no baseline database, scheduler, trend analysis, or comparison engine. Its value comes from a repeatable habit: run, verify, prioritize, and recheck.