Installation
Supported Platforms
| Platform | Web server | Package | Notes |
|---|---|---|---|
| cPanel/WHM on CloudLinux / AlmaLinux / Rocky | Apache (EA4) or LiteSpeed | .rpm | Primary target. Full cPanel account, WordPress, Exim, and WHM plugin coverage. |
| Plain AlmaLinux / Rocky / RHEL 8+ / CentOS Stream 8+ | Apache (httpd) or Nginx | .rpm | Generic Linux + web server checks. cPanel-specific checks are skipped cleanly. |
| Plain Ubuntu 20.04+ / Debian 11+ | Apache (apache2) or Nginx | .deb | Same as above, with debsums/dpkg --verify in place of rpm -V. |
The daemon auto-detects the OS, control panel (cPanel/Plesk/DirectAdmin/none), and web server (Apache/Nginx/LiteSpeed) at startup. The detected platform is logged at startup as:
[2026-04-10 08:13:37] platform: os=ubuntu/24.04 panel=none webserver=nginx
Check it with journalctl -u csm.service | grep platform: after starting the daemon.
APT repository (Debian / Ubuntu) – recommended
The package repository at mirrors.pidginhost.com/csm/ is the preferred install method for Debian and Ubuntu. Updates are available through apt upgrade, and APT verifies the repository’s signed metadata before installing packages.
# 1. Install the signing key
curl -fsSL https://mirrors.pidginhost.com/csm/csm-signing.gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/csm.gpg
# 2. Add the repository
echo "deb [signed-by=/etc/apt/keyrings/csm.gpg] https://mirrors.pidginhost.com/csm/deb stable main" | \
sudo tee /etc/apt/sources.list.d/csm.list
# 3. Install
sudo apt update
sudo apt install csm
Works on Ubuntu 20.04+, Debian 11+, and compatible derivatives. The single stable suite serves every supported release. Production binaries dynamically link glibc with a build floor of 2.28, which is available on all supported distributions; YARA-X itself is linked into the executable.
To upgrade later: sudo apt update && sudo apt upgrade csm.
DNF repository (AlmaLinux / Rocky / RHEL / CloudLinux / cPanel) – recommended
# 1. Import the signing key into the RPM keyring
sudo rpm --import https://mirrors.pidginhost.com/csm/csm-signing.gpg
# 2. Add the repository
sudo tee /etc/yum.repos.d/csm.repo >/dev/null <<'EOF'
[csm]
name=CSM - Continuous Security Monitor
baseurl=https://mirrors.pidginhost.com/csm/rpm/el$releasever/$basearch
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.pidginhost.com/csm/csm-signing.gpg
EOF
# 3. Install (check the fingerprint below before accepting the key)
sudo dnf install csm
rpm --import populates the RPM keyring, which covers gpgcheck – the
signature on the package. It does not populate the separate keyring dnf keeps
for repo_gpgcheck, the signature on the repository metadata. So the first
transaction against a new repository still prompts, even after a successful
import:
Importing GPG key 0x81CD59B1:
Userid : "CSM Package Signing (CSM Repository Metadata Signing Key) <security@pidginhost.com>"
Fingerprint: 3A70 4D78 3CF2 6055 B2AA 8F49 4E0F 27F5 81CD 59B1
Is this ok [y/N]:
Without -y, an unanswered key prompt defaults to no. A rejected key can
produce an error such as:
Error: Failed to download metadata for repo 'csm':
repomd.xml GPG signature verification error: Bad GPG signature
The error alone does not distinguish an untrusted key from damaged or incorrectly signed metadata. Check the fingerprint against the value above, then verify the metadata signature:
base=https://mirrors.pidginhost.com/csm/rpm/el9/x86_64/repodata
curl -fsSLO $base/repomd.xml
curl -fsSLO $base/repomd.xml.asc
curl -fsSL https://mirrors.pidginhost.com/csm/csm-signing.gpg | gpg --import
gpg --verify repomd.xml.asc repomd.xml
# gpg: Good signature from "CSM Package Signing ... <security@pidginhost.com>"
For unattended installs, approve the repository key in provisioning first,
then use sudo dnf -y install csm. DNF’s
assumeyes option
accepts key-import prompts as well as package prompts. Keep both signature
checks enabled; -y is not a substitute for verifying the expected key.
The $releasever variable auto-selects the matching EL major (8, 9, or 10). Both x86_64 and aarch64 are published. Works on AlmaLinux 8+, Rocky 8+, RHEL 8+, CloudLinux 8+, and cPanel-managed hosts.
To upgrade later: sudo dnf upgrade csm.
Online standalone installer
Use the standalone installer when the host has Internet access but cannot use the APT or DNF repository. It downloads the binary and supporting assets from the latest GitHub release, verifies their checksums, requires successful Ed25519 signature verification, and installs outside the package manager.
curl -fsSLo /tmp/csm-install.sh https://raw.githubusercontent.com/pidginhost/csm/main/scripts/install.sh
less /tmp/csm-install.sh
sudo bash /tmp/csm-install.sh
Standalone verification uses OpenSSL 3.0 or newer, an already installed CSM build providing csm verify-release, or python3-cryptography – in that order. EL8 and CloudLinux 8 have the last of these, so the standalone path works there even though their OpenSSL 1.1.1 cannot verify Ed25519. A missing key, missing current-release signature, absent verifier, or failed verification stops the install before executing the binary. See Release signing for the narrowly scoped historical-release exception.
It auto-detects the hostname and alert email, generates a Web UI token, and prompts before applying. Non-interactive mode:
sudo bash /tmp/csm-install.sh --email admin@example.com --non-interactive
This is not an offline or air-gapped installation path. Mirror the signed packages and repository metadata for disconnected environments.
Manual .rpm / .deb download
If you need a specific version or want to install without adding the repository, verify its detached signature before invoking the package manager. Save the trusted public key from Release signing as csm-signing.pub. These commands require OpenSSL 3.0 or newer; older hosts should use the signed repository above. Installing a local package does not by itself establish the repository signature chain:
# RHEL family
curl -LO https://github.com/pidginhost/csm/releases/latest/download/csm-VERSION-1.x86_64.rpm
curl -LO https://github.com/pidginhost/csm/releases/latest/download/csm-VERSION-1.x86_64.rpm.sig
openssl pkeyutl -verify -pubin -inkey csm-signing.pub -rawin \
-sigfile csm-VERSION-1.x86_64.rpm.sig -in csm-VERSION-1.x86_64.rpm && \
sudo dnf install -y ./csm-VERSION-1.x86_64.rpm
# Debian/Ubuntu
curl -LO https://github.com/pidginhost/csm/releases/latest/download/csm_VERSION_amd64.deb
curl -LO https://github.com/pidginhost/csm/releases/latest/download/csm_VERSION_amd64.deb.sig
openssl pkeyutl -verify -pubin -inkey csm-signing.pub -rawin \
-sigfile csm_VERSION_amd64.deb.sig -in csm_VERSION_amd64.deb && \
sudo apt install -y ./csm_VERSION_amd64.deb
Replace VERSION with a published version. Both files are also available from the package mirror if you need to pin a release without adding the repository.
Filesystem layout
The package uses FHS paths for config, state, drop-ins, and shipped profiles. Upgrades keep /opt/csm/csm.yaml as a compatibility link for older scripts:
| Concern | Current path |
|---|---|
| Main config | /etc/csm/csm.yaml |
| Legacy config link | /opt/csm/csm.yaml |
| Drop-in fragments | /etc/csm/conf.d/*.yaml |
| State directory | /var/lib/csm/state/ |
| Shipped profiles | /usr/lib/csm/profiles/ |
| Audit log | /var/log/csm/audit.jsonl |
| Binary | /opt/csm/csm |
| Quarantine | /opt/csm/quarantine/ |
| YARA / signature rules | /opt/csm/rules/ |
The systemd unit declares StateDirectory=csm and ConfigurationDirectory=csm so systemd manages permissions for the FHS directories. On upgrade, the package copies a real legacy main config into /etc/csm/csm.yaml when needed and points /opt/csm/csm.yaml at it. On first start the daemon copies a non-empty legacy /opt/csm/state/ into /var/lib/csm/state/ (only when the new directory is empty), then continues using the FHS state path. See Upgrading - FHS migration for the manual-binary-swap case.
When refreshing the service unit, install and csm rehash create any missing directory the unit requires before replacing it, without changing the mode of directories that already exist. Optional paths and systemd-managed directories are left alone. If a required directory cannot be created, the refresh fails and the previous unit stays in place.
Post-install (all methods)
RPM/DEB packages are the maintained installation path and include package-manager ownership, integration profiles, UI assets, rules, and the prebuilt PAM module. The standalone installer supplies the runtime assets but does not register them with a package manager. In either case, set infrastructure IPs and confirm the alert address before starting the daemon.
sudo vi /etc/csm/csm.yaml # Set hostname, alert email, infra IPs
sudo csm validate # Check config syntax (--deep adds connectivity probes; validates merged conf.d too)
sudo systemctl enable --now csm.service
sudo csm baseline # Record current state as known-good via the daemon (must be running)
Then open the Web UI at https://<server>:9443/login. The baseline is detailed under Baseline Scan.
Rollback to an older version
Both the APT and DNF repositories retain the last 5 tagged releases at any time. To downgrade:
# Debian/Ubuntu
sudo apt-cache policy csm # Show available versions
sudo apt install csm=<version>-1
# RHEL family
sudo dnf --showduplicates list csm # Show available versions
sudo dnf downgrade csm
Verifying platform auto-detection
After systemctl start csm.service, the first line after “CSM daemon starting” reports what CSM detected:
[2026-04-10 08:13:37] CSM daemon starting
[2026-04-10 08:13:37] platform: os=almalinux/10.0 panel=none webserver=apache
[2026-04-10 08:13:37] Watching: /var/log/secure
[2026-04-10 08:13:37] Watching: /var/log/httpd/error_log
[2026-04-10 08:13:37] Watching: /var/log/httpd/access_log
If any field shows none or unknown when you expect something, the auto-detect missed it. File a bug with the output of cat /etc/os-release, systemctl is-active nginx apache2 httpd, and which nginx apache2 httpd.
Optional system dependencies
The daemon runs as one executable plus packaged UI, rule, profile, and PAM assets. Release binaries require glibc 2.28 or newer and the package depends on systemd. A few host packages enable additional checks:
| Package | Platforms | Enables |
|---|---|---|
auditd | All | Shadow file / SSH key tamper detection via auditd |
debsums | Debian/Ubuntu | Cleaner system binary integrity output vs. dpkg --verify fallback |
logrotate | All | Rotation of /var/log/csm/monitor.log, /var/log/csm/audit.jsonl, and the PHP Shield event log |
wp-cli | Optional | WordPress core integrity check |
| ModSecurity | All | WAF enforcement checks (see platform-specific install below) |
Installing ModSecurity
CSM detects ModSecurity but doesn’t install it for you. Platform-specific commands:
# Ubuntu/Debian + Nginx
sudo apt install libnginx-mod-http-modsecurity modsecurity-crs
# Ubuntu/Debian + Apache
sudo apt install libapache2-mod-security2 modsecurity-crs && sudo a2enmod security2
# AlmaLinux/Rocky/RHEL + Apache (requires EPEL)
sudo dnf install -y epel-release
sudo dnf install -y mod_security
sudo systemctl restart httpd
# AlmaLinux/Rocky/RHEL + Nginx (requires EPEL)
sudo dnf install -y epel-release
sudo dnf install -y nginx-mod-http-modsecurity
sudo systemctl restart nginx
After installing ModSecurity, run csm check and the waf_status finding should disappear.
Manual (deploy.sh)
/opt/csm/deploy.sh install
vi /etc/csm/csm.yaml # set hostname, alert email, infra IPs
csm validate
systemctl enable --now csm.service
csm baseline
Baseline Scan
The csm baseline command scans the entire server and records the current state for change tracking. This is required on first install so CSM knows what’s “normal” for your server. Findings that should never be silently trusted, such as non-standard MySQL superusers or WHM root API tokens, can still be reported on this first scan.
What it does:
- Scans all cPanel accounts for malware, permissions, and configuration issues
- Records file hashes, email forwarder hashes, and plugin versions
- Stores everything in the bbolt database (
/var/lib/csm/state/csm.db)
How long it takes: Depends on server size. A server with 100+ cPanel accounts and thousands of WordPress sites can take 5-10 minutes. The daemon must be running because the baseline is coordinated through the control socket.
When to re-run:
- After a fresh install
- After restoring from backup
- After an intentional state reset approved by the operator
- You do NOT need to re-run for normal deploys/upgrades – the daemon handles incremental state
Important: Start csm.service before running csm baseline. If existing history would be cleared, rerun with csm baseline --confirm only after verifying that reset is intended.