This is a guide to installing, configuring and maintaining Arcivéo Monitor. The sections are grouped: overview, deploying the dashboard, connecting security tools, built-in modules and diagnostics. Commands can be copied with the button on the right.
Panel installation is covered on separate step-by-step pages. Choose a method:
Arcivéo Monitor is a server-security dashboard. It collects data from installed tools (Fail2ban, UFW, Lynis, ModSecurity, AIDE, ClamAV, Auditd, CrowdSec, Suricata, Falco, and more) and shows it in a single interface with a dashboard, an attack map, and detailed pages for each tool.
Monitor is not an active protection tool — it does not block attacks on its own. Its job is to aggregate information from already-running tools and present it in a convenient form.
The monitor runs locally only — it must be installed on the same server it monitors. There is no SSH or remote API.
The dashboard runs all commands (fail2ban-client, ufw status, ipset list, etc.) as the web-server user (usually www-data, or the site account on hosting panels) with a narrow set of sudo permissions — limited to specific utilities, without general root access. The results are parsed and shown in the browser.
The score starts at maximum and drops for each issue found:
PermitRootLogin yes) — −20Result: 80+ = Protected, 60–79 = Attention, <60 = At risk.
WebAuthn is a passwordless authentication standard using a hardware key. It supports YubiKey, Touch ID, Face ID, Windows Hello, Passkey.
After you log in with a password, the system requests confirmation via a registered key. Even if the password leaks, no one can log in without the physical key or biometrics.
To set it up, open WebAuthn keys in the side menu and click “Register key”. Register two keys at once: if your only key is lost or breaks, you won't be able to log in to the dashboard with it.
The dashboard can send a security report to Telegram and email (on demand and on a schedule). Configured under "Settings".
Telegram. You need a bot token and a chat id:
@BotFather → /newbot → get a token like 123456:ABC....@userinfobot, or open https://api.telegram.org/bot<TOKEN>/getUpdates and locate "chat":{"id":...}.Email. Two options to choose from under "Settings" → Email:
re_...) and a verified sender domain.Report status: "ATTENTION" or "OK". The header turns to "ATTENTION" only for a real problem or a pending action: a ClamAV threat found, file changes in AIDE, critical Falco events (Emergency/Alert/Critical in the last 24 h), a failed service in Monit, a required reboot, an expiring SSL certificate (≤14 days), or pending security updates. Background noise — SSH brute-force bots, IP addresses banned by fail2ban, Suricata alerts, Lynis warnings, and already-blocked ModSecurity requests — does not raise the status, so such numbers in the report do not mean "ATTENTION" on their own.
Detailed monitoring modules (Lynis, UFW, ModSecurity, attack map, AIDE, ClamAV, and others) unlock with a valid license. Without one, the dashboard, settings, and account still work, but the modules show a "License required" card.
After purchasing in your account, you have an activation code in the form ARCIVEO-XXXX-XXXX-XXXX-XXXX. It needs to be "activated" for your panel's domain — this turns the code into a signed license file (the [license] block) that you paste into the panel.
How to activate (3 steps):
my.arciveo.com → the "Licenses" / "License activation" section — copy the ARCIVEO-… code.monitor.example.com). Click activate — the system generates a license file bound to that domain and shows it in a field with a "Copy" button.The panel verifies the key cryptographically: the signature, the domain binding, and the expiry date.
APP_URL constant in config.php and enter only the hostname — without https:// and without the www prefix. Activation is one-time: the code turns into a license for the entered domain and cannot be activated again — if the domain is wrong, the key won't fit your panel and the code will be spent. So enter the domain carefully.
All the main dashboard parameters are defined in a single file config.php at the root (next to the public/ folder) as ordinary define() constants. The file is created during installation; you rarely need to edit it by hand — mainly when changing the domain, migrating, or connecting to a different database. After any edit, restart PHP-FPM (otherwise the changes won't take effect because of OPcache).
Substitute your own values in the highlighted spots; leave the rest as is:
Database. MySQL/MariaDB connection credentials:
DB_HOST — the DBMS host, almost always localhost;DB_NAME — the dashboard's database name;DB_USER — the DB user (access to its own database only);DB_PASS — that user's password;DB_CHARSET — the connection encoding, leave it as utf8mb4.Application.
APP_URL — the full dashboard address (e.g. https://monitor.example.com). It must match the domain the license is activated for — otherwise the key will be rejected (see the "License" section);TIMEZONE — the PHP timezone: it only affects how the dashboard shows dates and times. It does not affect when cron jobs run — those use the system timezone (see "All cron jobs").Session lifetime. SESSION_LIFETIME — the session idle timeout in seconds (sliding: refreshed on activity). The default 28800 = 8 hours; after that much inactivity the dashboard will ask you to log in again. For example, 3600 = 1 hour, 86400 = one day.
Error logging. Errors are never shown to visitors; they are written to logs/php_errors.log — you can view them on the "Application logs" page. These lines (display_errors=0, log_errors=1, the error_log path) usually don't need changing — the settings are defined right in the file and don't depend on php.ini.
public/), and this dashboard's web root (DocumentRoot) is exactly the dashboard root, not public/. The file doesn't "leak" on its own: the root .htaccess explicitly denies it (Require all denied) — the server returns 403. Even without that rule the source wouldn't leak: this is PHP — the server executes it rather than serving it as text. Just in case: don't post it to public repositories and don't send it to support with the real password. File permissions — 640.
UFW (Uncomplicated Firewall) is a simple front-end for nftables/iptables. It closes all incoming ports except those explicitly allowed. The "UFW firewall" page shows the status and rules.
ufw enable, be sure to allow SSH (ufw allow OpenSSH), otherwise you will lose access to the server.
deny rule is not counted as reachable from outside.
Skipping adding existing rule is not an error. This is how UFW reports that the exact same rule already exists and won't add it again. When you re-run auto-configuration (it's idempotent), this is a normal message — no action needed.
Automatically bans an IP after too many failed login attempts. Analyzes logs from SSH, nginx, Apache and other services.
The basic install is above. Here is a working configuration that yields dozens of active jails and thousands of bans: global settings, key jails and auto-banning of malicious IPs from the ipsum list.
The /etc/fail2ban/jail.local file — global settings and the most important jails:
ignoreip, otherwise you may ban yourself. After editing: sudo fail2ban-client reload.
Auto-loading of the ipsum block list — in the root cron (sudo crontab -e): level 1 (100,000+ IPs) is loaded into the ipsum set, which is dropped at the firewall (more in the "IPset block list" section):
ipsum — that is exactly what the dashboard reads (the "IPset ipsum" card). Levels: levels/1.txt — maximum coverage, levels/3.txt — more precise (3+ sources).
Why the "Security Monitor" is split into two zones. Protection works on two levels, and the dashboard does not mix them:
sshd, apache-*, nginx-* jails, etc.) and persistent recidivists (the recidive jail — those already banned several times). These are IPs that really hit you — they appear on the attack map and the "Timeline".ipset ipsum, dropped at the firewall by a DROP rule. Most of these addresses never even touched your server — they are cut off in advance; the "IPset ipsum" counter shows how many were blocked preventively.The difference is simple: reactive means "these attacked and got banned", preventive means "these were blocked before they even tried". Previously, ipsum list-3 was artificially forced into recidive (hence the old "list recidive" split); now recidive holds only real recidivists, and prevention is entirely on the firewall.
ipsum is a public list of malicious IPs, updated daily. The Monitor shows the number of loaded addresses on the dashboard and attack map, and factors it into the Security Score (−10 if the set is not loaded).
The minimal option without fail2ban is a separate ipsum set with blocking via iptables:
@reboot. The create … -exist command also sets the limit maxelem 300000 (the default 65536 won't fit level 1, you'll get "Hash is full"):
ipsum set and, if the firewall is managed by the installer (fresh VPS — "Full"/"Lite" profiles), attaches the set to UFW with a DROP rule — traffic from these IPs is actually blocked. The rule sits after ESTABLISHED,RELATED, so current connections (including your SSH) aren't dropped — only new connections from the list are cut. The set is restored when the ipsum-load.service service loads before the firewall (otherwise UFW wouldn't come up), and is refreshed by cron at 04:00. On an already-configured server (panel, own firewall) the installer doesn't touch the firewall — there ipsum stays a list for the dashboard and attack map, and the DROP rule can be added manually if desired (the minimal option with iptables … --match-set ipsum … -j DROP is above). With auto-install nothing needs to be done manually.
A modern replacement for Fail2ban with collective threat intelligence: community-sourced bans plus your own rules. Requires a separate bouncer to apply bans to the firewall.
systemctl is-active crowdsec). Start it: sudo systemctl enable --now crowdsec; if it crashes, check sudo journalctl -u crowdsec -n 30. The same applies to any service showing "Not running" (Suricata, Falco, Monit, MySQL).
stream halted / bans are not applied. This is an orphaned API key: the bouncer was removed from cscli bouncers list, but its old key remains in /etc/crowdsec/bouncers/*.yaml. Re-register the bouncer and set the new key:
AIDE (Advanced Intrusion Detection Environment) takes a snapshot of the file system and, on every check, reports changes in /etc, /bin, /usr. After installation, initializing the database (aideinit) is mandatory.
aideinit the terminal stays on the line Running aide --init... for 5–15 minutes — this is normal (hashing the entire filesystem, heavy disk load). Do not interrupt with Ctrl+C. If the process seems to "hang" but writes nothing — it may be waiting for a hidden prompt Overwrite existing aide.db.new [Yn]? (press Y). Check activity from another session: pgrep -af aide.
aideinit error: "21_aide_spamassassin … printf: invalid number" (return code 20) — a known bug in the AIDE config snippet on Ubuntu 22.04. The database is not created. Move the broken snippet aside and retry:
aideinit was never run, or (Ubuntu 24.04) the /var/lib/aide directory was created in mode 700 and is inaccessible to www-data — fixed with sudo chmod 755 /var/lib/aide (see the block above). "No checks yet" = the database exists but no check has run yet — this is not an error. The monitor reads results from /var/log/aide/aide.log.
/etc/cron.daily/aide on newer Ubuntu/Debian may not write /var/log/aide/aide.log in the required form (and aide.wrapper is no longer present there). It is more reliable to add your own cron job with an explicit --config — it writes the log as root in mode 644, and the monitor reads it without extra groups:
chmod 755 /var/lib/aide and a check cron job at 02:00 — nothing needs to be done manually.
sudo aideinit && sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db.
Antivirus scanner for Linux. Especially useful for scanning /var/www for PHP shells and malicious code.
enable --now? Three common causes:
1. The config still has an Example line — clamd refuses to start while it's there:
2. The signature database isn't downloaded — clamd won't start without it:
3. It's simply loading — clamd loads ~8M signatures into memory in 30–60 sec. Wait and check: systemctl is-active clamav-daemon (status activating → still loading).
sudo journalctl -u clamav-daemon -n 30 --no-pager.
clamd daemon only keeps signatures in memory; it doesn't scan anything on a schedule by itself. The dashboard shows scheduled-scan results, so you need a cron job that scans and writes a log. Auto-install adds the wrapper /usr/local/bin/clamav-scan.sh and a cron job at 01:30 — after the first run, "Files scanned" and "Last scan" will populate. Run it right away without waiting for the schedule: sudo /usr/local/bin/clamav-scan.sh.
Linux Malware Detect (LMD) — a malware scanner for web threats: PHP shells, web backdoors, droppers. It uses the ClamAV engine and supplements it with its own signatures.
maldet --report.
update-rc.d: error: unable to read /etc/init.d/maldet may appear — it is harmless. maldet does not use init.d; signature updates and scans run via /etc/cron.daily/maldet. If installation completed appears below, everything was installed.
apt but into /usr/local/maldetect, and when open_basedir is enabled its presence is checked through the shell — see the section "The page is empty even though there is data on the server".
A network intrusion detection system: it analyzes traffic at the packet level and knows thousands of attack signatures. It complements ModSecurity (which works at the HTTP level, while Suricata works at the TCP/IP level).
/var/log/suricata/eve.json as root with mode 750 on the directory, and the web server (www-data) can't read it. Open the directory for traversal — the files inside stay protected:
Intercepts system calls via eBPF/kernel module and detects anomalies in real time: a shell spawned from nginx, a web process reading /etc/passwd, writes to /bin, etc.
journalctl -u falco (without sudo — through the systemd-journal group). Make sure www-data is in this group — see "Configuring sudo" (step 2) on the manual installation page.
/etc/passwd, writes to system directories). Zero critical events in a day on a quiet server is a healthy state.
journalctl requires journal permissions; so the panel sees events reliably, auto-install enables Falco's file_output → /var/log/falco/falco.log and sets UMask=0022 on the service (the log is readable by the web server). On a fresh install there's no need to configure this manually.
ModSecurity is a web firewall (WAF) for Apache or Nginx. It blocks application-layer attacks: SQL injection, XSS, path traversal, scanners.
IncludeOptional /etc/modsecurity/*.conf, but the package only ships modsecurity.conf-recommended — which does not match the *.conf pattern. If you don't copy it to modsecurity.conf, SecRuleEngine stays Off: the module is loaded, the CRS rules are loaded, but traffic isn't inspected and no audit log is created. The intermediate DetectionOnly mode only logs events without blocking requests — the dashboard shows it in yellow.
Dashboard access to the audit log. The log /var/log/apache2/modsec_audit.log is owned by root (permissions 640), so the web user can't read it. The dashboard reads data through a wrapper — create it:
SecRuleEngine directive: indented lines are inside <LocationMatch>/<Directory> blocks (for example, disabling the WAF for phpMyAdmin) and don't define the global mode.www-data, while in HestiaCP the site pool runs as the site owner (for example, admin) — check with grep -E '^user' /etc/php/*/fpm/pool.d/monitor.example.com.conf.X-Forwarded-For header. Only transactions with a triggered rule appear in the statistics: the SecAuditLogRelevantStatus directive writes any 4xx/5xx responses to the audit log, so ordinary 403/500 responses end up there too — the dashboard does not count them as WAF events.---RULES--- block is needed by the "All active rules" section — the dashboard shows not only triggered rules but every loaded CRS rule plus custom ones. The three paths in the for f in … loop are the typical locations for CRS rules and local additions; if your layout differs (the package installs files in its own directory, or your custom rules aren't in /etc/modsecurity/custom-rules.conf), find the real paths with sudo grep -rl 'IncludeOptional\|^Include ' /etc/apache2/mods-enabled/security2.conf /etc/apache2/conf-enabled/*.conf 2>/dev/null and add them to the list. If the wrapper is old (without this section) the section simply shows an "unavailable" warning, and the rest of the page works as before.
Auditd (Linux Audit Daemon) records system calls at the kernel level: logins and logouts, sudo commands, failed authentication attempts, file changes. The Monitor shows today's logins, failed attempts and sudo commands.
ausearch (/usr/sbin/ausearch) and, when needed, from /var/log/audit/audit.log using the tail command. Both must be in sudoers.
Watches services (nginx, php-fpm, mysql, etc.) and restarts them if they go down. Can send alerts by email.
monit status. The HTTP interface must be enabled in /etc/monit/monitrc (the set httpd block with allow localhost), otherwise monit status returns an error.
set httpd line in monitrc is commented out (it ships as # set httpd port 2812 … by default). Uncomment the block and allow localhost. (2) An enabled httpd by itself monitors nothing — Monit counts only what is described by check stanzas; without them the list is empty even with a working interface. A minimal working config:
conf.d with httpd on 2812 and a set of checks — on a fresh install there is nothing to configure by hand.
PSAD analyzes the iptables log and detects port scans and network attacks, assigning each source a threat level (1–5). It complements fail2ban and Suricata.
psad --Status (required in sudoers). Without iptables logging the page will be empty — this is normal until a scan occurs.
Mandatory Access Control restricts which files and resources a program can access, even if it has been compromised. Ubuntu/Debian use AppArmor by default (usually already installed and active).
aa-status (needs to be in sudoers). It shows the number of profiles in enforce/complain mode and processes without a profile.
"Profiles loaded" being higher than enforce + complain is normal. AppArmor 4.x (Ubuntu 24.04 and later) introduced the unconfined mode: the profile is loaded into the kernel but restricts nothing. Ubuntu flags dozens of profiles this way for programs that use user namespaces (browsers, torrent clients and the like). When such profiles exist, the "Profiles loaded" card turns amber and shows their count — for example unconfined: 90 with 120 loaded and 26 in enforce. Only profiles in enforce actually provide protection; on Ubuntu 22.04 (AppArmor 3.x) this mode does not exist and the numbers always add up.
unconfined should only be done knowingly: they are disabled not by mistake, but because otherwise the programs themselves break. Profiles in complain are a different matter: the rules are already written there and simply not enforced.
debsums verifies that installed package files match the checksums from the repository — it detects tampered system binaries (complementing AIDE). A full scan takes 1–2 minutes, so it runs via cron, and the dashboard reads the result from data/debsums/debsums.log and sorts it into categories itself (only binaries and libraries matter).
The job goes in the root crontab (sudo crontab -e). The ready-made wrapper debsums-scan.sh is placed in /usr/local/bin/ (chmod +x; see the cron job summary) and writes its report to the dashboard's data/debsums/ itself.
The debsums-scan.sh wrapper finds the dashboard's data/ itself — no need to specify the path.
/etc/ (configs) and /usr/share/ (resources) on the server are usually normal — the dashboard marks them in a separate color. Changes to binaries and libraries are the alarming ones (/bin, /sbin, /usr/lib, etc.) — the "Binaries / libraries" card shows exactly those.
Lynis runs manually or via cron. The report must be saved to the project's data/lynis/ folder — the monitor reads the lynis-report.dat file.
lynis-scan.sh in the background right from the dashboard (without waiting for cron): it shows "Scanning…" and refreshes the report itself when done. For this the web user needs a sudoers line to run the script — the installer adds it automatically to /etc/sudoers.d/monitor. If the dashboard was installed manually/earlier, add it with the same user already listed in the file:
Logwatch must save daily reports to the project's data/logwatch/ folder in .txt format. The Monitor shows the latest report and the archive.
The network monitor requires no installation — it is a built-in dashboard page. It shows the server's network status from local sources:
/proc/net/dev;ip;ss;journalctl -k.The first three sources work without sudo, so interfaces, traffic, connections and ports are visible right away. The "Kernel events" block uses journalctl -k — it is read via the systemd-journal group ("Configuring sudo", step 2), no sudo needed. Check that everything is accessible to the web user:
UFW BLOCK entries do not appear here — they are on the "UFW Firewall" and "Attack map" pages. An empty block with a green check = no network failures in the last 24 hours.
The built-in page shows three things:
df); the bar turns red at ≥90%;lsblk), real ones only (loop/snap hidden);smartctl).Space and the device list work right away, no setup needed. SMART requires the smartmontools package. The web process has no direct access to disk devices, so SMART is read via cron into the file data/disk/smart.txt, and the dashboard reads it from there.
The job goes in the root crontab (sudo crontab -e). A ready-made wrapper smart-scan.sh is placed in /usr/local/bin/ (chmod +x; see the cron job summary) and writes to the dashboard's data/disk/ itself.
The smart-scan.sh wrapper finds the dashboard's data/ on its own — no need to specify a path. Inside, lsblk -e7,11 excludes loop/cdrom.
The page shows the server load history for the last 24 hours — Load Average, CPU usage and I/O wait, RAM/Swap, network traffic (receive/transmit), disk I/O (read/write), disk and inode usage, open file descriptors and MySQL connections, plus the current number of TCP connections and processes.
The data is collected by cron/collect_metrics.php — every 5 minutes it writes one "raw" snapshot of counters (/proc/loadavg, /proc/meminfo, /proc/stat, /proc/net/dev, /proc/diskstats, df/df -i, /proc/sys/fs/file-nr, SHOW GLOBAL STATUS LIKE 'Threads_connected') into the system_metrics database table; the page computes percentages and rates itself from the difference between adjacent snapshots (disk/inode/descriptor usage and MySQL connections are instantaneous values, with no recalculation). Sudo is not required — the sources are read without root privileges. Points older than 24 hours are deleted automatically on every write.
The collect-metrics-all.sh wrapper (see the cron jobs summary) finds all installed panel instances on the server itself and runs each one's cron/collect_metrics.php as the site owner.
Load alerts (the "Settings" → "Load alerts" section) — when a CPU/RAM/disk/inode threshold is exceeded, the panel sends a notification to Telegram/Email (the same channels as the daily report — there's no need to enable them separately for alerts), and one more when the metric returns to normal. It won't spam again while the threshold is held: the next notification arrives only after a "recovered → exceeded again" cycle.
collect_metrics.php on every run (every 5 minutes) — no separate cron is needed. The "already notified / not yet" state is stored in data/alerts_state.json, and the thresholds are in the panel settings.
The "Attack map" page determines the country by IP address using the geoiplookup command. Without the GeoIP package, countries won't be resolved and no points will appear on the map:
/usr/share/GeoIP/GeoIP.dat database is readable by everyone, and results are cached in tmp/geoip_cache.json. The map itself (Leaflet + OpenStreetMap tiles) loads in the browser — you need internet on the computer where the dashboard is open.
Two built-in dashboard cards that show not a tool's "on/off" state but the server's real security posture. They need no setup and are read locally without sudo.
External exposure — how many services listen on all interfaces (0.0.0.0/[::]) and are reachable from outside. It flags red if a database or cache is exposed (MySQL, PostgreSQL, Redis, MongoDB, Memcached, Elasticsearch) — a direct hole (−10 to the Security Score). Source: ss -tuln.
127.0.0.1 (bind-address in the MySQL/PostgreSQL config, bind 127.0.0.1 in Redis) or close the port in UFW.
127.0.0.1 (loopback) is visible only to the server itself — it cannot be reached from outside, even if the port is "open". That's why Postfix on port 25 bound to loopback is safe: auto-setup sets inet_interfaces = loopback-only (plus a neutral smtpd_banner — clearing the Lynis MAIL-8818 warning about version disclosure). The "External exposure" card counts as exposed only what listens on 0.0.0.0/[::]; loopback services don't count.
/etc/postfix/main.cf set smtpd_banner = $myhostname ESMTP (no version or OS) and inet_interfaces = loopback-only, then sudo systemctl restart postfix.
Security updates — how many security patches are pending and whether a reboot is required after a kernel update (−5 to the Security Score if patches exist). Source: /usr/lib/update-notifier/apt-check, file /var/run/reboot-required. Detailed list — on the "Security updates" page.
update-notifier-common). If apt-check is missing, the monitor counts patches via apt-get -s upgrade.
Automatic security updates (unattended-upgrades) — on the "Security updates" page a separate card shows whether automatic installation of security patches is enabled and when it last ran. No sudo needed — the status is read via apt-config dump.
A backup is your main insurance: losing data is worse than any breach. You need two things — a server/site backup and a separate backup of the panel database (it holds users, WebAuthn keys, settings, and the license).
Option A — HestiaCP: the Backup tab under the user → the create-backup button (or on a schedule in the server settings). The backup includes sites and their databases.
Option B — manually (cron): a database dump + an archive of the panel's data/ directory:
Updating to a new version. First make a backup. Then re-upload the code files, keeping your data:
public/, includes/, assets/, cron/, database/, plus the root .htaccess (front controller — routing must not be left over from the old version), manifest.json, sw.js;config.php (database data), data/ (reports), logs/, tmp/ (sessions and cache).SSH_FX_PERMISSION_DENIED — Permission denied. The panel files belong to www-data (that is how they were set during installation), while the SFTP client connects as your own user, which has no write access. Handing the whole panel to www-data “to make it work” is exactly what leads to this error; below are three ways, any of which solves the problem.
data/ (reports), tmp/ (sessions and cache), logs/; they stay owned by www-data. Everything else is code, and the web server only needs read access to it, which the www-data group provides with 644 permissions. A side benefit: with a PHP vulnerability the panel files can no longer be rewritten. On hosting panels (HestiaCP and similar) option A is not needed: there the site files already belong to the account you log in with over SFTP, and the web server reads them through the group.
chmod on the files resets the ACL mask, and access silently disappears. If after “tidying up the permissions” the upload again hits Permission denied — rerun both setfacl commands.
2 bit in option C is setgid: files uploaded over SFTP stay in the www-data group, otherwise the panel cannot overwrite them. After option C reconnect in FileZilla — the new group only takes effect on a new login. Check: id deploy (the www-data group must appear) and ls -ld /path/to/monitor (drwxrwsr-x — the letter s means setgid is set).
Migrating to another server:
config.php, data/.mysqldump on the old one → import on the new one; update the database data in config.php.adm group, cron jobs.If you can't log in, everything can be fixed directly in the database from the server. Open the database (its name is in config.php):
Lost WebAuthn key (second factor fails) — disable 2FA, log in with your password, and register a new key:
Forgot your password — set a new hash (generate it on the server and paste it in):
Locked yourself out with the IP filter — disable the restriction:
sudo mysql on the server, or phpMyAdmin / the database section in your hosting panel. After recovery, re-enable WebAuthn and the IP filter.
A summary of jobs lives in the server's root crontab (added via sudo crontab -e). Keep only the lines for the tools you use; adjust the paths to your server.
sudo crontab -l and that the cron service is active.
TIMEZONE from config.php. The TIMEZONE constant only affects PHP (how the dashboard shows dates), but the cron daemon runs jobs by the OS system time. If the server's timezone doesn't match yours, the "08:00" report will arrive at the wrong time. Example: the server is in another timezone (UTC, UTC+0) while you're in London (UTC+1) → the "08:00" report arrives at 09:00 your time. Check and, if needed, set the system timezone to your own:
0 8 * * * will fire at 08:00 local time. Otherwise you'd have to shift cron itself, but at each daylight-saving change the offset would drift again — so setting the system timezone is the right approach.
crontab.txt) sit in the system/ folder next to the project, outside public_html. This is not part of the site — don't upload it to the web root; place it on the server at the system paths (as in the crontab above):
lynis-scan.sh → /usr/local/bin/ (chmod +x) — runs lynis audit system, sets the flag /tmp/lynis-running during the scan, and copies lynis-report.dat into the dashboard's data/lynis/;logwatch_daily.sh → /usr/local/bin/ (chmod +x) — builds the daily Logwatch report (sshd, fail2ban, sudo, postfix) into data/logwatch/;smart-scan.sh → /usr/local/bin/ (chmod +x) — captures disk status (smartctl) into data/disk/;debsums-scan.sh → /usr/local/bin/ (chmod +x) — checks package integrity (debsums) into data/debsums/;clamav-scan.sh → /usr/local/bin/ (chmod +x) — ClamAV antivirus scan over risky paths (web, home, temp); writes a summary to /var/log/clamav/scan.log, which the ClamAV page reads (the 01:30 line in the crontab above);load-ipsum.sh → /usr/local/bin/ (chmod +x) — updates the ipset set ipsum (level 1) in place, without breaking the active firewall rules (the 04:00 line in the crontab above);daily-report-all.sh → /usr/local/bin/ (chmod +x) — runs the dashboard's report cron/daily_report.php (the 08:00 line in the crontab above);daily_report.php — already included in the dashboard (cron/daily_report.php), run via daily-report-all.sh, no separate install needed;collect-metrics-all.sh → /usr/local/bin/ (chmod +x) — runs the dashboard's cron/collect_metrics.php (the "Performance" page, the */5 line in the crontab above); collect_metrics.php is already included in the dashboard, no separate install needed;crontab.txt (system/cron/) — a sample of jobs; add the lines you need via sudo crontab -e./usr/local/bin/. You can't write there directly from FileZilla — the directory belongs to root, and the SFTP client will get SSH_FX_PERMISSION_DENIED. The order is: first upload the file to /tmp (everyone can write there), then move it into place with a single command:
/tmp at the server root — not /var/tmp and not tmp/ inside the dashboard itself (the latter belongs to www-data and is closed off from your user). In the FileZilla tree, /tmp is a top-level branch, next to var, not inside it.
/usr/local/bin/, log — /var/log/arciveo-cron.log) — nothing to do by hand.
/home/*/web/*/public_html and /var/www/*, and place reports in their data/. If the dashboard is at a different path, add it to the for app in … line inside the scripts, otherwise the Lynis/SMART/debsums/Logwatch reports won't reach the dashboard.
logs/cron.log is first created by the root crontab — it will belong to root, and the "Cron log" tab in the dashboard won't be able to read or clear it. Create the file in advance as the web user (owner of the site directory; on HestiaCP that's an account such as admin) — then the root cron will only append, without changing the owner:
stat -c %U /path/to/monitor.
sudo crontab -e), otherwise it will run twice.
sudo crontab (that would be a direct escalation to root by anyone who gains access to the dashboard session), but a narrow script with two commands (list/set) that touches only its own block between the service comments. Install it once:
www-data — check which user the site's PHP-FPM pool runs as (ps -o user= -C php-fpm) and substitute it in the sudoers line.
public/crontab_monitor.php was uploaded via FTP/SFTP under a different system user (for example, root) than the rest of the site files, the web server won't be able to read it. Compare the owner and permissions with a neighboring file and bring them in line:
Monitor detects tools through dpkg-query — the APT package database. If a tool was not installed through apt (manually, from snap, or from source), dpkg does not see it.
Error 500 — check the PHP, nginx and Monitor logs:
www-data but under a user account (e.g. admin — the owner of the site directory). All sudo rules and group memberships (adm, systemd-journal) must be assigned to that user, otherwise the modules will show "Inactive / 0" while the services are running. To find the real PHP user: ps -o user= -C php-fpm | sort -u or the owner of the site directory stat -c '%U' /path/to/monitor. In all commands below, substitute it for www-data. Auto-install detects the web user itself and writes sudoers for it.
No data shown — almost always missing sudo permissions. Check the specific command as the web user (replace www-data with your own). The -n flag = no password, just like PHP — if it asks for a password, the rule is missing from sudoers:
sudo aa-status in the terminal lists profiles, but the "AppArmor" page says "Inactive"). Cause: the web user has no sudo permission for that module's command. Check it against the list above: if it asks for a password, add the missing line to /etc/sudoers.d/monitor ("Configuring sudo"). Common "new" commands: /usr/sbin/aa-status (MAC), /usr/sbin/psad --Status (PSAD).
apache2ctl, ausearch, aa-status or ss is not allowed, or the web user is not in the adm/systemd-journal groups (the fail2ban/auth/modsec logs and journalctl — Falco and kernel events — are read from there).
Symptom: the data is present on the server (visible via shell), but the page shows "no data" or the wrong status — for example, AIDE reports "Not initialized" even though the database has been created.
The cause is open_basedir: many panels and hosts restrict the PHP-FPM pool to the domain's directory, so the PHP functions file_exists(), file_get_contents(), filemtime() are blocked for system paths (/var/lib/aide, /var/log, /proc…). The Monitor works around this by reading such paths with standard system commands (cat, test, stat).
open_basedir. The correct fix is reading via system commands (already done for AIDE and the Network Monitor). Extending open_basedir to /var, /proc is unnecessary and less secure.
The Monitor checks certificates by connecting to domains directly over port 443. If a domain is unreachable from the server itself or the port is blocked by a firewall, the check will fail.
/etc/nginx/sites-enabled/, /etc/nginx/conf.d/) and Apache (/etc/apache2/sites-enabled/), plus the current host from HTTP_HOST.
The Monitor connects to MySQL as the user from config.php, which only has access to its own database. MySQL lists only databases you have privileges on in information_schema — that's why the others aren't visible.
To let the Monitor see all databases, grant this user read-only access (once, as root; substitute the username from config.php):
sudo mysql at all: the database list comes through its own PDO connection.
PostgreSQL requires postgres user-level access, which the panel's web user does not have. Exposing a broad sudo psql from PHP is unsafe — instead, the panel calls a narrow parameterless wrapper that only prints the version, the number of connections and the list of databases with their sizes. Create it:
monitor-pgstat line from sudoers (step 13 of manual installation) and do not create the script itself: the PostgreSQL card will simply stay inactive.
The dashboard shows what is happening; below is what to do in common situations. General rule: don't panic, cross-check against legitimate activity (your actions, updates, backups), and respond according to severity.
ignoreip./etc, outside /usr/share) — potentially tampered. Verify the package: debsums PACKAGE_NAME, and if in doubt reinstall it (apt install --reinstall).127.0.0.1 or close the port in UFW. This is a real hole.certbot renew or the settings in the panel).sudo apt update && sudo apt upgrade; after a kernel update, reboot the server.