Fully manual installation: from a freshly purchased VPS to a working dashboard, step by step.
Server preparation, site creation, the database, config.php and SSL are covered here.
Commands for each security tool and for cron are in the
FAQ reference, linked along the way.
The golden rule: when changing SSH or the firewall, do not close your current connection until you have tested the new one in a separate window. If you do lose access, almost every host provides an emergency console (VNC/Recovery) in the control panel.
01. Bought a VPS with Ubuntu/Debian — where to start
After purchase, your host sends you: an IP address, a username (usually root) and a password (or an SSH key). That's enough to log in. Steps to follow (each step is a section below):
Connect to the server over SSH;
Update the system, set the hostname and timezone;
Create a regular user with sudo rights (don't work as root);
Set up SSH-key login and disable password login;
Enable the firewall and auto-protection;
(optional) install the HestiaCP control panel — web server, database and mail out of the box.
02. First SSH connection
SSH is a secure terminal to your server. Replace 203.0.113.10 with your own IP.
203.0.113.10 is an example, a non-existent address (reserved for documentation). Don't use it as-is — replace it with your server's real IP from your host's email. Otherwise the connection will fail.
Windows 10/11: open PowerShell or "Terminal" and use the built-in ssh (or clients such as PuTTY / MobaXterm). macOS / Linux: open "Terminal".
# Log in as root (your host emailed you the password):
ssh root@203.0.113.10
# If your host gave you a key file instead of a password:
ssh -i path/to/key root@203.0.113.10
On the first connection, SSH will ask about the "authenticity of host" — type yes. The password is not shown as you type it (this is normal). If your host gave you a temporary password, change it with the passwd command.
03. System update and basic setup
First things first — update all packages and set the hostname and timezone.
The list of timezones is timedatectl list-timezones. If a blue "Daemons using outdated libraries" window appears at the end of the update, select all services (Space) and press OK — it's safe.
04. Creating a user with sudo
Working under root all the time is unsafe. Create a regular user and grant them sudo rights (running admin commands when needed). Replace deploy with any name.
# Create the user (it sets a password and asks for details — you can press Enter):
adduser deploy
# Add to the sudo group:
usermod -aG sudo deploy
# Check (as root):
su - deploy
sudo whoami # should output: root
exit
From now on, log in to the server as this user: ssh deploy@203.0.113.10, and run admin commands with the sudo prefix.
05. SSH keys and disabling password login
Key-based login is more secure than a password: a password can be guessed, a key practically cannot. First we generate the key on your own computer, copy it to the server, verify the login — and only then disable the password.
Step 1. Generate a key on your own computer (Windows PowerShell / macOS / Linux):
ssh-keygen -t ed25519 -C "my-laptop"
# Enter for every prompt (the key lands in ~/.ssh/id_ed25519)
Step 3. Test the key-based login in a new window — it should let you in without a password:
ssh deploy@203.0.113.10
Do not run Option B until key-based login has been tested and works (Steps 1–3), and do not close your working session. It disables password login for all users, including root. Without a working key you will lose access to the server entirely — you'll only be able to get it back through the host's console. No key — use Option A.
Step 4. Harden SSH access. Put the settings in a separate file, without touching the main config. Choose the option that fits your situation:
Option A — only lock out root, keep the password. No key needed, you won't lose access:
Option B — full hardening. Disable password login and allow root only via a key. Run it only after confirming that key-based login works:
sudo tee /etc/ssh/sshd_config.d/00-hardening.conf >/dev/null <<'EOF'
PubkeyAuthentication yes
PasswordAuthentication no
PermitRootLogin prohibit-password
KbdInteractiveAuthentication no
EOF
sudo systemctl restart ssh
In both options root password login is closed. PermitRootLogin no forbids root entirely, prohibit-password allows login only via a key (for administration, log in as deploy and use sudo). If you want to change the SSH port, add the line Port 2222, but first open the new port in the firewall (next section) and verify the login, otherwise you will lock yourself out.
06. Basic firewall and auto-protection
Close everything unnecessary with the firewall and enable fail2ban (bans SSH password brute-forcing). First allow SSH, otherwise you'll lose access after enabling UFW.
# Allow SSH (or your port, if changed) and web:
sudo ufw allow OpenSSH
sudo ufw allow 80,443/tcp
# Enable the firewall:
sudo ufw enable
sudo ufw status verbose
# fail2ban — protects SSH from brute-forcing (basic profile active immediately):
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
This is the minimum. Working fail2ban settings, the ipsum block list, advanced UFW and the other tools are in the "Security tools" group of the reference. The Arcivéo Monitor panel itself will clearly show the status of all this.
07. Installing the HestiaCP panel (optional)
HestiaCP is a free hosting control panel: it installs and configures a web server (nginx + apache), PHP, a database (MariaDB), mail, DNS and SSL certificates, and gives you a web interface for your sites. Handy if you'd rather not set everything up by hand and plan to host sites (including the Arcivéo Monitor panel itself).
Install HestiaCP on a clean server (a fresh supported Ubuntu/Debian, at least ~1–2 GB RAM), before installing other web servers and databases — otherwise there will be conflicts. Installation takes 10–20 minutes and reboots the server.
# Download the installer and run it:
wget https://raw.githubusercontent.com/hestiacp/hestiacp/release/install/hst-install.sh
sudo bash hst-install.sh
The installer will ask for an email and hostname, then install the whole stack. After the reboot the panel is available at https://YOUR_IP:8083 (the installer shows the username and password at the end).
HestiaCP manages UFW and fail2ban itself — there's no need to configure them separately, it will take over. Still set up SSH keys and disable password login (see the previous section).
08. System requirements and ionCube
The dashboard is a PHP application on a typical LAMP/LEMP stack:
OS: Linux (Ubuntu/Debian recommended);
Web server: nginx or Apache with PHP-FPM;
PHP 8.0+ with extensions: pdo_mysql, openssl, curl, json, mbstring;
ionCube Loader — a PHP extension required for the dashboard to run;
Database: MySQL 5.7+ or MariaDB 10.3+;
HTTPS — required (login and WebAuthn work over https only);
sudo for the web server user (a narrow set — step 13).
# Check the PHP version and extensions:
php -v
php -m | grep -iE 'pdo_mysql|openssl|curl|mbstring|ioncube'
Installing ionCube Loader (if not present yet). On hosting with a control panel (HestiaCP, cPanel) ionCube is enabled with a checkbox in the PHP settings. Manually on Ubuntu/Debian:
# Find the PHP version and extensions directory:
php -v
EXTDIR=$(php -r 'echo ini_get("extension_dir");'); PHPVER=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')
# Download and unpack the loaders (64-bit):
cd /tmp
wget -q https://downloads.ioncube.com/loader_downloads/ioncube_loaders_lin_x86-64.tar.gz
tar xzf ioncube_loaders_lin_x86-64.tar.gz
# Copy the loader matching your PHP version to the extensions directory:
sudo cp ioncube/ioncube_loader_lin_${PHPVER}.so "$EXTDIR"/
# Enable it (CLI + PHP-FPM) and restart:
echo "zend_extension=ioncube_loader_lin_${PHPVER}.so" | sudo tee /etc/php/${PHPVER}/mods-available/ioncube.ini
sudo phpenmod ioncube
sudo systemctl restart php${PHPVER}-fpm
# Check — the output will show the line "with the ionCube PHP Loader":
php -v
The loader version must match the PHP version (for example ioncube_loader_lin_8.1.so for PHP 8.1). If you use several PHP versions, enable a loader for each one.
09. Domain and DNS
To open the dashboard at an address like monitor.example.com and get a free SSL, you need a domain pointing to your server. In your DNS control panel, create an A record:
Type: A
Name: monitor (subdomain → monitor.example.com)
or @ (root domain → example.com)
Value: 203.0.113.10 ← your server's IP
TTL: 3600
After a few minutes, check that the domain points to the server:
dig +short monitor.example.com # should return your IP
# or, if dig is unavailable:
getent hosts monitor.example.com
A Let's Encrypt SSL certificate is issued only for a domain — DNS must point to the server before the certificate is issued.
10. Create the site and upload the panel files
Apache: point DocumentRoot at the panel root, NOT at public/. Styles (CSS/JS), sw.js and manifest.json live in assets/ next to public/ and are requested from the site root. The root .htaccess is the front controller. Under Apache, if you set DocumentRoot to public/, the panel will open without styles. For plain nginx it's the opposite: public/ is used as the root, and assets/ are served by a separate rule (see the nginx block below).
The panel files (the distribution archive) are downloaded after purchase from your account at my.arciveo.com → "Downloads". Unpack the archive before uploading.
1) Create the panel directory and upload the contents of the distribution into it (so that public/, assets/, config.php, etc. end up inside):
sudo mkdir -p /var/www/monitor
# then upload the distribution files to /var/www/monitor (FileZilla / WinSCP / scp)
2) Configure the web server.Apache: point DocumentRoot at the panel root (NOT at /public); AllowOverride All is required. The PHP-FPM socket path is detected automatically. Paste the block into the terminal in full:
PHPSOCK=$(ls -1 /run/php/php*-fpm.sock 2>/dev/null | head -1) # auto-detect the PHP-FPM socket
sudo tee /etc/apache2/sites-available/monitor.conf > /dev/null <<'EOF'
<VirtualHost *:80>
ServerName monitor.example.com
DocumentRoot /var/www/monitor
<Directory /var/www/monitor>
AllowOverride All
Require all granted
</Directory>
<FilesMatch \.php$>
SetHandler "proxy:unix:__PHPSOCK__|fcgi://localhost"
</FilesMatch>
</VirtualHost>
EOF
sudo sed -i "s#__PHPSOCK__#${PHPSOCK}#" /etc/apache2/sites-available/monitor.conf
sudo a2dissite 000-default.conf
sudo a2ensite monitor.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
nginx: nginx has no .htaccess, so we use public/ as the root and serve assets/, sw.js and manifest.json (one level up) via a separate rule:
PHPSOCK=$(ls -1 /run/php/php*-fpm.sock 2>/dev/null | head -1) # auto-detect the PHP-FPM socket
sudo tee /etc/nginx/sites-available/monitor.conf > /dev/null <<'EOF'
server {
listen 80;
server_name monitor.example.com;
root /var/www/monitor/public;
index index.php;
# assets, service worker and manifest live one level above public/
location ~ ^/(assets/|sw\.js|manifest\.json) { root /var/www/monitor; }
location / { try_files $uri $uri/ /index.php?$query_string; }
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:__PHPSOCK__;
}
}
EOF
sudo sed -i "s#__PHPSOCK__#${PHPSOCK}#" /etc/nginx/sites-available/monitor.conf
sudo ln -s /etc/nginx/sites-available/monitor.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Uploading files — SFTP/SCP (FileZilla, WinSCP) or scp:
# Example via scp from your local computer:
scp -r ./monitor/* deploy@203.0.113.10:/var/www/monitor/
Set the file permissions — this is a mandatory step. If you uploaded as root or over SFTP, the files belong to root and the web server (www-data) won't be able to read them — the panel will open blank or with a 403 error (in the log: .htaccess unreadable / directory not executable). The command below fixes this:
# Normalize permissions for the whole webroot: a directory created by root is
# inaccessible to the web server (www-data) — without this the panel serves a blank page or 403.
# Apache runs as www-data; if you use a different web user, replace it.
cd /var/www/monitor
# Create the working folders BEFORE chown — otherwise new directories stay root:root
# and with chmod 750 the web server (www-data) won't be able to write to them.
sudo mkdir -p data/lynis data/logwatch tmp logs
sudo chown -R www-data:www-data /var/www/monitor
sudo find /var/www/monitor -type d -exec chmod 755 {} \;
sudo find /var/www/monitor -type f -exec chmod 644 {} \;
sudo chmod 640 /var/www/monitor/config.php
sudo chmod 750 data tmp logs
3) Open up SFTP uploads for yourself. After the command above all files belong to www-data, while FileZilla / WinSCP connect as your own user — an upload will fail with SSH_FX_PERMISSION_DENIED (Permission denied). Logging in as root to upload is not an option — root login was disabled in step 05. Choose one of the two options.
Option A — an ACL for your user only (recommended). Write access goes to you alone; the web server still cannot overwrite the panel code:
sudo apt install -y acl
# Write access for your user to the whole panel directory:
sudo setfacl -R -m u:deploy:rwX /var/www/monitor
# The same rule as the default — for files and folders created later:
sudo setfacl -R -d -m u:deploy:rwX /var/www/monitor
Option B — via the www-data group. Simpler, but the web server gets write access to the panel files as well: with a vulnerability in PHP the code could be replaced. The order of the commands matters — config.php and the working folders are locked down last:
sudo usermod -aG www-data deploy
# Group write access + setgid (the 2 bit): files uploaded over SFTP stay
# in the www-data group — otherwise the panel cannot overwrite them.
sudo find /var/www/monitor -type d -exec chmod 2775 {} \;
sudo find /var/www/monitor -type f -exec chmod 664 {} \;
sudo chmod 640 /var/www/monitor/config.php
sudo chmod 2750 /var/www/monitor/data /var/www/monitor/tmp /var/www/monitor/logs
After option B reconnect in FileZilla (Server → Disconnect, then log in again) — the new group only takes effect on a new login, and until then you still have no rights. Check: id deploy — www-data must appear in the group list; ls -ld /var/www/monitor — permissions drwxrwsr-x, where the letter s instead of x means setgid is set.
11. Database
Create the database and user, then import the schema. Paste the block into the terminal as a whole. monitor_db and monitor_user are example names — you can set your own; remember the database name, user and password — you'll enter them in config.php in the next step:
# 1. Database. The password is set ONCE in DBPASS and substituted into every line.
# Paste the block into the terminal AS A WHOLE; sudo mysql logs in as root via the unix socket
# (no root password needed). Do NOT use an interactive `sudo mysql -u root -p`
# with copy-paste — on paste the SQL lines go into the password prompt and are lost.
DBPASS='CHOOSE_A_PASSWORD' # ← change only this line
sudo mysql <<SQL
CREATE DATABASE IF NOT EXISTS monitor_db CHARACTER SET utf8mb4;
CREATE USER IF NOT EXISTS 'monitor_user'@'localhost' IDENTIFIED BY '$DBPASS';
GRANT ALL ON monitor_db.* TO 'monitor_user'@'localhost';
FLUSH PRIVILEGES;
SQL
# Check (should show monitor_db):
mysql -u monitor_user -p"$DBPASS" -e "SHOW DATABASES;"
# Enter this same password in config.php → DB_PASS.
No need to import the schema — the dashboard creates the tables and the admin account itself on the first visit in the browser (from database/db.sql) if the database is empty. Manual schema import is only needed if auto-initialization failed.
If you used the browser installer public/start_db.php — delete it right after installation: it lets anyone recreate the database without authorization. As long as the file sits in the dashboard root or in public/, the dashboard shows a red warning.
12. Configuring config.php
config.php in the dashboard root (/var/www/monitor/config.php) is the only file you need to edit by hand. All dashboard settings are set in it as define() constants. Open it in an editor:
sudo nano /var/www/monitor/config.php
Insert your own values in the highlighted spots; leave the rest as is:
// --- Database (from step 11) ---
define('DB_HOST', 'localhost'); // leave as is
define('DB_NAME', 'db_name'); // what you created in step 11
define('DB_USER', 'user'); // what you created in step 11
define('DB_PASS', 'db_password'); // what you set in step 11
define('DB_CHARSET', 'utf8mb4'); // leave as is
// --- Application ---
define('APP_URL', 'https://monitor.example.com'); // dashboard address, no trailing slash
define('TIMEZONE', 'Europe/London'); // your timezone
// --- Session time ---
define('SESSION_LIFETIME', 28800); // idle time before re-login, sec (28800 = 8 h)
What to change:
DB_NAME, DB_USER, DB_PASS — exactly the same database name, user and password you set when creating the DB in step 11 (if you kept the examples — monitor_db / monitor_user). Do not touch DB_HOST and DB_CHARSET.
APP_URL — the full dashboard address with https://, no trailing slash and no www. It must match the domain you activate the license on (step 16), otherwise the key will be rejected.
TIMEZONE — your timezone (list — timedatectl list-timezones). It only affects how the dashboard shows dates; it does not affect when cron jobs run (there the system timezone applies).
SESSION_LIFETIME — after how many seconds of inactivity the dashboard asks you to log in again (8 hours by default). E.g. 3600 = 1 hour, 86400 = one day.
The error-logging block (display_errors, log_errors, error_log) — leave at the defaults.
Save the file (Ctrl+O, Enter, then Ctrl+X) and restart PHP-FPM — otherwise OPcache will prevent the changes from applying:
sudo systemctl restart php*-fpm
config.php is a secret file (it holds the DB password). It sits in the dashboard root, which is also the web root, but it is protected: permissions 640 (set in step 10) and an explicit deny in the root .htaccess. Do not publish it to public repositories or send it to support with the real password.
PHP runs as the web-server user, which has no rights to system commands. Access is granted narrowly: targeted sudo for specific utilities and log reading via groups (no sudo). A breach of the web layer does not give root.
In the examples, www-data is the standard Apache user. If yours differs (some panels run PHP under a separate user), replace it everywhere. To find out: ps -o user= -C php-fpm | sort -u.
1. Create /etc/sudoers.d/monitor via sudo visudo -f /etc/sudoers.d/monitor and paste (remove the lines for modules you don't use):
# UFW — status and rules ("Firewall" page)
www-data ALL=(ALL) NOPASSWD: /usr/sbin/ufw status, /usr/sbin/ufw status verbose, /usr/sbin/ufw status numbered
www-data ALL=(ALL) NOPASSWD: /usr/sbin/ufw allow [0-9]*, /usr/sbin/ufw deny [0-9]*, /usr/sbin/ufw --force delete [0-9]*
# Fail2ban — status, ban and unban (banned returns bans of all jails in one command;
# ban/unban are needed by the panel buttons)
www-data ALL=(ALL) NOPASSWD: /usr/bin/fail2ban-client status, /usr/bin/fail2ban-client status *, /usr/bin/fail2ban-client banned, /usr/bin/fail2ban-client set * banip *, /usr/bin/fail2ban-client set * unbanip *
# Security updates ("Updates" card). Read-only, but specifically as root:
# the apt cache (~70 MB) is only accessible to root, non-root rebuilds it on every call
# (4.2 s CPU vs 0.01 s). No wildcard — exactly this one command, installs nothing.
www-data ALL=(ALL) NOPASSWD: /usr/bin/apt list --upgradable
# IPset (attack map, dashboard)
www-data ALL=(ALL) NOPASSWD: /usr/sbin/ipset list -t ipsum
# CrowdSec
www-data ALL=(ALL) NOPASSWD: /usr/bin/cscli decisions list *, /usr/bin/cscli alerts list *, /usr/bin/cscli bouncers list *, /usr/bin/cscli scenarios list *
# Auditd — event search + reading the last lines of the log (exact path)
www-data ALL=(ALL) NOPASSWD: /usr/sbin/ausearch -m *
www-data ALL=(ALL) NOPASSWD: /usr/bin/tail -n 300 /var/log/audit/audit.log
# Monit / ModSecurity / AppArmor / PSAD
www-data ALL=(ALL) NOPASSWD: /usr/bin/monit status
www-data ALL=(ALL) NOPASSWD: /usr/sbin/apache2ctl -M
www-data ALL=(ALL) NOPASSWD: /usr/local/bin/monitor-modsec
www-data ALL=(ALL) NOPASSWD: /usr/sbin/aa-status
www-data ALL=(ALL) NOPASSWD: /usr/sbin/psad --Status
# Open ports (kernel/SSH/Falco logs are read WITHOUT sudo — via the
# systemd-journal group, see step 2; sudo for journalctl is NOT needed and unsafe)
www-data ALL=(ALL) NOPASSWD: /usr/bin/ss -tuln, /usr/sbin/ss -tuln, /bin/ss -tuln
# PostgreSQL (only if you use it) — fixed read-only script,
# create it per the FAQ "PostgreSQL is not shown"; without it, delete the line
www-data ALL=(ALL) NOPASSWD: /usr/local/bin/monitor-pgstat
sudo chmod 440 /etc/sudoers.d/monitor
sudo visudo -c # should be "parsed OK"
2. Access to logs and the systemd journal. Modules read /var/log/fail2ban.log, auth.log, ufw.log, apache2/*, aide directly (on Debian/Ubuntu these logs are in the adm group). Kernel, SSH and Falco events are taken from journald with the journalctl command without sudo, via the systemd-journal group. Add the web user to both groups and restart PHP-FPM:
sudo usermod -aG adm,systemd-journal www-data
sudo systemctl restart php*-fpm # required, otherwise the groups won't apply
3. If ClamAV or Suricata write logs not to the adm group (sometimes root:root) — grant access via ACL:
4. ModSecurity wrapper. The WAF audit log (/var/log/apache2/modsec_audit.log) is owned by root with 640 permissions, so the web user can't read it directly. The ModSecurity page gets the engine mode, events and the list of active rules via a fixed read-only script — that's the one allowed in sudoers by the line above:
Without the /etc/modsecurity/modsecurity.conf file the WAF itself doesn't work: the package only drops modsecurity.conf-recommended, and the rule engine stays disabled — for how to enable it, see FAQ → "Installing ModSecurity".
The user in all sudoers lines must match the FPM pool user: on plain Apache/Debian this is www-data, in HestiaCP the site pool runs as the site owner (for example, admin) — check grep -E '^user' /etc/php/*/fpm/pool.d/monitor.example.com.conf.
5. If Nginx sits in front of Apache (HestiaCP, ISPmanager and other panels — there Nginx proxies PHP to Apache and serves static files itself). Service directories are protected by .htaccess files, but Nginx doesn't read them: it will serve any static file (.json, .txt, .log, .dat) directly, bypassing Apache. Panel caches and data will leak out — for example tmp/modsec_cache.json with WAF events and attacker IP addresses. Add a deny rule to the Nginx site config:
The ^~ prefix is required: it is selected before the regex rule for static files inside location /, otherwise the deny won't work.
In HestiaCP put this in a separate file /home/<user>/conf/web/<domain>/nginx.ssl.conf_deny (and nginx.conf_deny for HTTP) — the site config includes nginx.ssl.conf_* and does not overwrite such files on rebuild. To apply: sudo nginx -t && sudo systemctl reload nginx.
Check: curl -s -o /dev/null -w '%{http_code}\n' https://monitor.example.com/tmp/modsec_cache.json — should be 403. If Apache runs without Nginx (listening on 80/443 itself), nothing needs to be added — .htaccess is enough.
Check the binary paths with which (for example which ufw cscli ausearch ss). Edit sudoers only through visudo. Listing all MySQL databases is enabled with a separate GRANT (FAQ → "Only one database is visible").
14. Restricting access by IP
Restrict access to the Monitor by IP address — even if the URL becomes known, the login page won't open. You can do this at the web-server level (nginx example below) or in the panel itself ("Settings" → "IP access restriction"). If you use Apache, use the restriction in the panel.
If the nginx site is already configured per step 10, don't add a secondlocation / — put the allow/deny lines into the existing block. Two identical location / blocks in one server { } is a config error and nginx won't restart.
# In the nginx config (inside server { }):
# Keep the Let's Encrypt ACME path open, bypassing the IP restriction —
# so SSL issuance and auto-renewal (step 15) don't depend on the IP filter.
location ^~ /.well-known/acme-challenge/ { allow all; }
location / {
allow 203.0.113.10; # ← enter your IP
allow 10.0.0.0/8; # local network (if needed)
deny all;
try_files $uri $uri/ /index.php?$query_string;
}
# Reload nginx:
sudo nginx -t && sudo systemctl reload nginx
15. Issue an SSL certificate (HTTPS)
The dashboard works over HTTPS only. The login session uses a secure cookie, and WebAuthn (2FA) works only over HTTPS. You cannot log in over http://.
The certificate is free (Let's Encrypt). The domain's DNS must already point to the server. The command depends on your web server:
# Apache:
sudo certbot --apache -d monitor.example.com
# nginx — ONLY if you actually run nginx. Do NOT run this on Apache:
# apt will pull in nginx and grab port 80, conflicting with Apache.
# sudo apt install python3-certbot-nginx
# sudo certbot --nginx -d monitor.example.com
# certbot writes HTTPS into the config and sets up auto-renewal itself
What certbot will ask: e-mail → agreement to the Terms (Y) → sharing your e-mail with the EFF (up to you). Then it issues the certificate itself, writes <VirtualHost *:443>, and sets up the http→https redirect and auto-renewal.
DNS must point to the server BEFORE running certbot (ownership check over port 80). Check: dig +short monitor.example.com → server IP. Ports 80/443 open: sudo ufw allow 80,443/tcp.
After issuing: https://monitor.example.com opens with a padlock, and http:// redirects to https:// (APP_URL in config.php was already set in step 12).
16. Login and initial setup
Open https://monitor.example.com, log in with admin / useradmin and go through the checklist:
Change the admin password — the "Users" section in the menu.
Enable WebAuthn (2FA) — "WebAuthn keys" → register a key/passkey (requires HTTPS). Register two at once: if you lose your only key, you will not be able to log in with it. Learn more.
Restrict access by IP — "Settings" → "IP access restriction" (add your own IP address before enabling it, or you will lock yourself out).
Enter the license — activate the ARCIVEO-… code from your account for your domain and paste the key into "Settings" → "License". Learn more.
Set up notifications — Telegram and/or Email in "Settings". Learn more.
Delete the installerpublic/start_db.php if it is still there (step 11).
17. Security tools (optional)
The dashboard is already running. Tools are installed as needed — install what you want, and the dashboard shows their status right away. Installation commands for each are in the reference (separate sections per tool):