FAQ

Este é o guia de instalação, configuração e manutenção do Arcivéo Security Monitor. As secções estão agrupadas: visão geral, implementação do painel, ligação de ferramentas de segurança, módulos integrados e diagnóstico. Pode copiar os comandos com o botão à direita.

Por onde começar

01. Instalação do painel — escolha o método

A instalação do painel está em páginas dedicadas, passo a passo. Escolha o método:

Se tiver dúvidas, opte pela automática. Este manual continua a ser a fonte única sobre SSL, ferramentas, cron e diagnóstico — as páginas de instalação remetem para as suas secções, sem duplicar nada.

Visão geral

02. O que é o Arcivéo Security Monitor

Arcivéo Security Monitor — o painel de segurança do servidor. Recolhe dados das ferramentas instaladas (Fail2ban, UFW, Lynis, ModSecurity, AIDE, ClamAV, Auditd, CrowdSec, Suricata, Falco, etc.) e apresenta-os numa interface única, com dashboard, mapa de ataques e páginas detalhadas para cada ferramenta.

O Monitor não é um meio de proteção ativo — não bloqueia ataques por si só. A sua função é agregar a informação das ferramentas já em funcionamento e apresentá-la de forma prática.

03. Como o monitor funciona no servidor

O monitor funciona apenas localmente — tem de ser instalado no mesmo servidor que monitoriza. Não existe qualquer SSH nem API remota.

Todos os comandos (fail2ban-client, ufw status, ipset list, etc.) são executados pelo painel em nome do utilizador do servidor web (normalmente www-data, nos painéis de alojamento — a conta do site) com um conjunto restrito de permissões sudo — apenas para utilitários específicos, sem acesso root geral. Os resultados são analisados e apresentados no navegador.

Dois ritmos de trabalho. As páginas do painel consultam as ferramentas no momento em que são abertas — vê sempre o estado atual. Além disso, há tarefas em segundo plano que correm segundo um horário (cron):

  • a cada 5 minutos — um instantâneo da carga para a página «Desempenho»;
  • a cada 5 minutos — o sensor de eventos de segurança: novos banimentos, alertas de IDS, uma proteção parada, uma porta aberta (secção «Eventos de segurança»);
  • uma vez por dia — o relatório de segurança para o Telegram e por e-mail, bem como as verificações pesadas (Lynis, AIDE, ClamAV, debsums, Logwatch);
  • a cada 15 minutos — o resumo do servidor para a área pessoal, se estiver ativado.

A lista completa das tarefas está na secção «Todas as tarefas cron num só lugar».

Para vários servidores, instale o monitor em cada um separadamente, com um domínio próprio. Para os ver numa única lista, ative o resumo dos servidores (secção «Resumo dos servidores na área pessoal»).

04. Como é calculada a Pontuação de segurança

A pontuação começa no máximo e diminui por cada problema detetado:

  • UFW inativo — −30
  • Fail2ban não iniciado (sem jail ativas) — −25
  • Sem chaves WebAuthn — −15
  • Lynis hardening index < 60 — −20; 60–79 — −10
  • IPset ipsum não carregado — −10
  • Ameaças detetadas pelo ClamAV — −20
  • Alterações de ficheiros pelo AIDE — −15
  • SSL expirado — −30, expira em <14 dias — −15, <30 dias — −5
  • CrowdSec instalado, mas não iniciado — −5
  • Suricata instalada, mas não iniciada — −5
  • SGBD/cache (MySQL, PostgreSQL, Redis…) acessíveis externamente — −10
  • Login root por SSH permitido (PermitRootLogin yes) — −20
  • Acesso por palavra-passe via SSH permitido (PasswordAuthentication yes) — −10
  • Definições menores do SSH: X11Forwarding ativo, MaxAuthTries acima de 3 ou sshd_config legível por todas as contas — −5
  • Proteção do servidor web: as respostas revelam a versão do servidor web ou do PHP, o método TRACE está ativo ou falta o cabeçalho Strict-Transport-Security ou X-Frame-Options — −5
  • Atualizações de segurança por instalar — −5

Resultado: 80+ = Protegido, 60–79 = Atenção, <60 = Em risco.

As deduções por ClamAV, AIDE, CrowdSec e Suricata só se aplicam se a ferramenta estiver instalada. Lynis e AIDE sem base inicializada aparecem como «sem dados» e não reduzem a pontuação. O número de ataques de hoje é apresentado no painel, mas não afeta a Pontuação de segurança. A proteção do servidor web não é contabilizada se nenhum site responder à verificação.

Definições e licença

05. WebAuthn — autenticação de dois fatores

WebAuthn — norma de autenticação sem palavra-passe através de uma chave física. Suporta YubiKey, Touch ID, Face ID, Windows Hello e Passkey.

Após iniciar sessão com a palavra-passe, o sistema pede a confirmação através da chave registada. Mesmo que a palavra-passe seja divulgada, sem a chave física ou a biometria é impossível iniciar sessão.

Para configurar, abra Chaves WebAuthn no menu lateral e clique em «Registar chave». Registe logo duas chaves: se a única chave se perder ou avariar, o acesso ao painel através dela será impossível.

O WebAuthn só funciona através de HTTPS. Numa ligação HTTP, o registo e o início de sessão por chave não estão disponíveis.

06. Notificações: Telegram, Email, webhook e push

São quatro canais: Telegram, Email, webhook e push no navegador. São independentes — ative os que quiser, ou os quatro. Por esses mesmos canais seguem o relatório de segurança diário, os alertas de carga e as deteções do sensor de eventos; configuram-se uma só vez aqui, em «Definições».

Telegram. São necessários o token do bot e o chat id:

  1. No Telegram, escreva a @BotFather → /newbot → obtenha o token do tipo 123456:ABC....
  2. Envie qualquer mensagem ao seu novo bot (para que ele lhe possa responder).
  3. Descubra o seu chat id: escreva ao bot @userinfobot ou abra https://api.telegram.org/bot<TOKEN>/getUpdates e procure "chat":{"id":...}.
  4. Insira o token e o chat id em «Definições» → Telegram e clique em «Guardar e enviar teste».

Email. Duas opções em «Definições» → Email:

  • SMTP — anfitrião, porta (465/SSL ou 587/TLS), utilizador e palavra-passe da sua caixa de correio;
  • Resend — API moderna: indique a chave de API (re_...) e um domínio de remetente confirmado.

Webhook. Um único endereço que aceite POST cobre Slack, Discord, Mattermost, n8n e um handler próprio. «Definições» → Webhook: cole o endereço e ative o interruptor.

  • para o Slack e o Mattermost segue {"text": …}, para o Discord {"content": …} (o Discord rejeita mensagens com mais de 2000 caracteres, pelo que o texto é cortado);
  • qualquer outro endereço recebe JSON estruturado: text, event, severity, source, count, host, ts — não é preciso voltar a analisar o texto da mensagem.

Notificações push. A mensagem chega diretamente ao navegador e, se o painel estiver adicionado ao ecrã inicial do telemóvel, como uma notificação normal de aplicação — sem bot e sem servidor de correio pelo meio. «Definições» → Notificações push: ative o interruptor e prima «Ligar este dispositivo» em cada dispositivo que deva receber notificações. O contador «Dispositivos ligados» mostra quantos são.

O push só funciona por HTTPS (tal como o WebAuthn) e a permissão é necessária duas vezes: primeiro no navegador — o pedido aparece ao premir «Ligar este dispositivo» — e depois no próprio sistema operativo. Se no Windows estiver desligado o interruptor geral Definições → Sistema → Notificações, o navegador concede a permissão, o painel indica o envio e no ecrã não aparece nada. O mesmo acontece com o modo «Não incomodar» e com as notificações do navegador desativadas no macOS (Definições do Sistema → Notificações).
Cada canal tem o seu próprio botão «Enviar teste», que o verifica de imediato. O horário do relatório automático passa pelo cron (secção «Todas as tarefas cron»): o cron desencadeia o envio e os canais são retirados das definições.

Estado do relatório: «ATENÇÃO» ou «OK». O título passa a «ATENÇÃO» apenas perante um problema real ou uma ação pendente: ameaça detetada pelo ClamAV, alterações de ficheiros no AIDE, eventos críticos do Falco (Emergency/Alert/Critical nas últimas 24 h), serviço em falha no Monit, reinício necessário, SSL a expirar (≤14 dias) ou atualizações de segurança pendentes. O ruído de fundo — tentativas de força bruta de bots por SSH, IP banidos pelo fail2ban, alertas do Suricata, avisos do Lynis e pedidos já bloqueados pelo ModSecurity — não eleva o estado, por isso esses números no relatório não significam «ATENÇÃO» por si só.

07. Licença — introdução e ativação

Os módulos de monitorização detalhados (Lynis, UFW, ModSecurity, mapa de ataques, AIDE, ClamAV, etc.) ficam disponíveis com uma licença válida. Sem ela, o dashboard, as definições e a conta funcionam, mas os módulos mostram o cartão «Licença necessária».

Após a compra na área pessoal, tem um código de ativação no formato ARCIVEO-XXXX-XXXX-XXXX-XXXX. É preciso «ativá-lo» para o domínio do seu painel — isto transforma o código num ficheiro de licença assinado (bloco [license]), que insere no painel.

Como ativar (3 passos):

  1. Obtenha o código de ativação. Área pessoal my.arciveo.com → secção «Licenças» / «Ativação de licença» — copie o código ARCIVEO-….
  2. Ative o código para o seu domínio. Ainda na área pessoal, abra «Ativação de licença», introduza: o código de ativação, o seu email e o domínio do painel (o endereço em que o monitor abre, ex.: monitor.example.com). Clique em ativar — o sistema gera um ficheiro de licença associado a esse domínio e mostra-o num campo com o botão «Copiar».
  3. Insira a chave no painel. Copie todo o texto da licença → no painel abra «Definições» → bloco «Licença», cole e clique em «Guardar». Os módulos são desbloqueados de imediato.

O painel verifica a chave criptograficamente: a assinatura, a associação ao domínio e a validade.

Na ativação, o domínio tem de coincidir exatamente com o endereço do painel. Retire-o da constante APP_URL em config.php e introduza apenas o nome do host — sem https:// e sem o prefixo www. A ativação é única: o código transforma-se numa licença para o domínio introduzido e não volta a ser ativado — se errar o domínio, a chave não servirá ao seu painel e o código ficará gasto. Por isso, introduza o domínio com atenção.
Se a validade expirar ou o domínio mudar, surge um aviso no topo do painel. A licença fica associada ao domínio para sempre e não é transferível para outro domínio: para um novo período ou um novo domínio é preciso uma chave nova (comprada na área pessoal e ativada uma única vez).

08. Ficheiro config.php — todas as definições do painel

Todos os parâmetros principais do painel estão definidos num único ficheiro config.php na raiz (junto à pasta public/), através de constantes define() normais. O ficheiro é criado durante a instalação; raramente é necessário editá-lo manualmente — sobretudo ao mudar de domínio, na migração ou ao ligar a outra base de dados. Após qualquer alteração, reinicie o PHP-FPM (caso contrário, devido ao OPcache, as alterações não serão aplicadas).

Introduza os seus valores nos locais realçados; deixe o resto como está:

// --- Base de dados --- define('DB_HOST', 'localhost'); // manter define('DB_NAME', 'db_name'); // o que definiu ao criar a BD define('DB_USER', 'user'); // o que definiu ao criar a BD define('DB_PASS', 'db_password'); // o que definiu ao criar a BD define('DB_CHARSET', 'utf8mb4'); // manter // --- Aplicação --- define('APP_URL', 'https://monitor.example.com'); // endereço do painel, sem barra no fim define('TIMEZONE', 'Europe/Lisbon'); // o seu fuso horário // --- Tempo de sessão --- define('SESSION_LIFETIME', 28800); // inatividade até novo início de sessão, seg (28800 = 8 h)

Base de dados. Dados de ligação ao MySQL/MariaDB:

  • DB_HOST — anfitrião do SGBD, quase sempre localhost;
  • DB_NAME — nome da base de dados do painel;
  • DB_USER — utilizador da BD (acesso apenas à sua própria base);
  • DB_PASS — palavra-passe deste utilizador;
  • DB_CHARSET — codificação da ligação, deixe utf8mb4.

Aplicação.

  • APP_URL — endereço completo do painel (ex.: https://monitor.example.com). Deve coincidir com o domínio no qual a licença foi ativada — caso contrário a chave será rejeitada (ver a secção «Licença»);
  • TIMEZONE — fuso horário do PHP: afeta apenas a forma como o painel mostra datas e horas. No horário de execução das tarefas cron não influi — aí vigora o fuso do sistema (ver «Todas as tarefas cron»).

Tempo de sessão. SESSION_LIFETIME — tempo limite de inatividade da sessão em segundos (deslizante: renova-se com a atividade). Por predefinição, 28800 = 8 horas; após esse tempo de inatividade o painel pedirá para iniciar sessão de novo. Por exemplo, 3600 = 1 hora, 86400 = um dia.

Registo de erros. Os erros nunca são mostrados aos visitantes; são escritos em logs/php_errors.log — podem ser vistos na página «Registos da aplicação». Estas linhas (display_errors=0, log_errors=1, o caminho error_log) normalmente não precisam de ser alteradas — as definições estão declaradas no próprio ficheiro e não dependem do php.ini.

config.php — ficheiro secreto. Contém a palavra-passe da BD. Fica na raiz do painel (junto ao public/), e neste painel a raiz web (DocumentRoot) é precisamente a raiz do painel, não o public/. O próprio ficheiro não «vaza»: no .htaccess da raiz existe uma proibição explícita para ele (Require all denied) — o servidor devolve 403. Mesmo sem essa regra o código-fonte não vazaria: é PHP — o servidor executa-o e não o devolve como texto. Por precaução: não o publique em repositórios públicos nem o envie ao suporte com a palavra-passe real. As permissões do ficheiro são 640.
Ao migrar ou ao recuperar o acesso, este ficheiro é a principal fonte de dados: o nome da BD, o utilizador e a palavra-passe são obtidos precisamente daqui (ver as secções «Atualização e migração do painel» e «Recuperação de acesso»).

09. API apenas de leitura (JSON e Prometheus)

O painel está protegido pela sessão com palavra-passe e, se estiver ativado, pelo filtro de IP. A um programa não serve nem um nem outro, por isso os sistemas externos têm a sua própria entrada — dois endereços apenas de leitura protegidos por um token. O token não altera nada no servidor: com ele só é possível obter indicadores.

Ativação — «Definições» → «API apenas de leitura»: o interruptor «Ativar a API»; o token é criado automaticamente ao guardar pela primeira vez. O botão «Novo token» emite outro e, nesse mesmo momento, deixa de fora todos os que usavam o anterior.

  • /api/status.php — JSON: pontuação de segurança, estado dos módulos, a última medição de carga com os processos principais e os eventos de segurança das últimas 24 horas;
  • /api/metrics.php — os mesmos números no formato de texto do Prometheus (métricas arciveo_*: arciveo_security_score, arciveo_fail2ban_banned, arciveo_cpu_busy_pct e por aí adiante).
curl -H "Authorization: Bearer <TOKEN>" https://monitor.example.com/api/status.php curl -H "Authorization: Bearer <TOKEN>" https://monitor.example.com/api/metrics.php

Se o seu cliente não souber colocar um cabeçalho, o token também é aceite como parâmetro — ?token=…. Tenha em conta que nessa forma acaba no registo de acessos do servidor web, pelo que o cabeçalho é preferível.

Prometheus. Um bloco pronto para scrape_configs:

scrape_configs: - job_name: arciveo metrics_path: /api/metrics.php scheme: https authorization: credentials: <TOKEN> static_configs: - targets: ['monitor.example.com']
A resposta fica em cache durante 5 minutos: por trás destes números está a mesma consulta pesada às ferramentas que está por trás do painel. Consultar com frequência não custa nada ao servidor — os valores simplesmente repetem-se até à atualização seguinte — mas um intervalo inferior a um minuto também não faz sentido.
401 — o token está errado ou não foi enviado. 404 com o texto api disabled — a API está desativada nas definições. Os pedidos rejeitados são escritos no registo da aplicação juntamente com o IP do remetente, pelo que uma tentativa de adivinhar o token é visível na página «Registos da aplicação».

10. Resumo dos servidores na área pessoal

O painel é instalado em cada servidor separadamente e responde bem à pergunta «como está esta máquina». Não responde à pergunta «como estão todas as minhas máquinas» — para isso, cada painel pode enviar um resumo breve para a área pessoal, onde os servidores aparecem numa única lista.

O que obter na área pessoal my.arciveo.com → secção «Os meus servidores»: aí é emitido um token (do tipo fk_…) e indicado o endereço do recetor https://my.arciveo.com/api/fleet.php. O token é um por conta — o mesmo para todos os seus servidores.

O que definir no painel — «Definições» → «Resumo dos servidores»: endereço do recetor, token, nome do servidor (como será identificado na lista; se ficar vazio é usado o domínio do painel) e o interruptor «Enviar o resumo». O botão «Enviar agora» verifica a ligação de imediato, sem esperar pelo horário.

Depois o resumo segue por cron, a cada 15 minutos:

# a cada 15 minutos — resumo do servidor para a área pessoal 3,18,33,48 * * * * /usr/local/bin/fleet-push-all.sh >> /path/to/monitor/logs/cron.log 2>&1

O que é enviado exatamente: nome e domínio do servidor, sistema operativo, tempo de funcionamento, versão do PHP; a pontuação de segurança; o estado dos módulos (UFW, número de jails do Fail2ban, banimentos de hoje, se o CrowdSec, o Suricata e o Falco estão a correr, ameaças do ClamAV, alterações do AIDE, índice do Lynis, atualizações pendentes, se é preciso reiniciar, dias até à expiração do certificado, número de portas abertas perigosas); a última medição de carga (load, CPU, RAM, disco, inodes); os contadores de eventos das últimas 24 horas e o último evento crítico. O resumo não contém registos, palavras-passe nem conteúdo de ficheiros.

É o servidor que envia; a área pessoal não pergunta. Por isso não é preciso abrir qualquer entrada para a sua rede a partir do exterior, e um painel fechado por filtro de IP também se reporta. O nó é reconhecido pelo domínio do painel, pelo que a lista não duplica após um reinício ou uma mudança de IP.
Um servidor marcado como silencioso na área pessoal significa que o resumo já não chega há algum tempo. Verifique por ordem: o interruptor «Enviar o resumo», a linha de cron (sudo crontab -l) e o que responde o botão «Enviar agora». Os erros de envio são escritos no registo da aplicação com a marca FLEET — aí vê-se se o token foi rejeitado (HTTP 401) ou se a própria ligação não se chegou a estabelecer.

Ferramentas de segurança

11. Firewall UFW

UFW (Uncomplicated Firewall) — uma interface simples para nftables/iptables. Fecha todas as portas de entrada, exceto as explicitamente permitidas. A página «Firewall UFW» mostra o estado e as regras.

sudo apt install ufw # Permitir SSH (obrigatório ANTES de ativar!) e web sudo ufw allow OpenSSH sudo ufw allow 80,443/tcp # Fechar a BD ao exterior (acesso apenas local) sudo ufw deny 3306 # Ativar e verificar sudo ufw enable sudo ufw status verbose
Antes de ufw enable, permita obrigatoriamente o SSH (ufw allow OpenSSH), caso contrário perderá o acesso ao servidor.
A «exposição externa» no painel tem em conta o UFW: uma porta fechada por uma regra deny não é considerada acessível a partir do exterior.
Skipping adding existing rule — não é um erro. É assim que o UFW indica que já existe exatamente essa regra e não a adiciona novamente. Ao executar novamente a configuração automática (que é idempotente), esta é uma mensagem normal — não é necessário reagir.

12. Instalação do Fail2ban

Bloqueia automaticamente o IP após exceder o número de tentativas de início de sessão falhadas. Analisa os registos do SSH, Nginx, Apache e outros serviços.

sudo apt install fail2ban sudo systemctl enable --now fail2ban # Verificar o estado: sudo fail2ban-client status
A configuração operacional (jail.local com dezenas de jails e banimento automático a partir de ipsum) encontra-se na secção seguinte.

13. Configuração operacional Fail2ban + ipsum

A instalação básica está acima. Aqui fica a configuração operacional que gera dezenas de jails ativas e milhares de bloqueios: definições gerais, jails principais e banimento automático de IP maliciosos da lista ipsum.

Ficheiro /etc/fail2ban/jail.local — definições gerais e as jails mais importantes:

[DEFAULT] bantime = 1w findtime = 900 maxretry = 3 backend = systemd banaction = nftables-multiport ignoreip = 127.0.0.1/8 ::1 <YOUR_IP> <TRUSTED_NETS> # Banimento progressivo: cada repetição dura mais tempo bantime.increment = true bantime.factor = 2 bantime.maxtime = 5w bantime.rndtime = 300 [sshd] enabled = true maxretry = 5 bantime = -1 # banimento permanente por brute-force de SSH findtime = 3600 # Reincidentes: quem apanhou vários banimentos é banido para sempre [recidive] enabled = true logpath = /var/log/fail2ban.log banaction = %(banaction_allports)s bantime = -1 findtime = 86400 maxretry = 2 [http-get-dos] enabled = true maxretry = 100 findtime = 300 bantime = 1w # Serviços web (apache-*, nginx-*, php-url-fopen, phpmyadmin-syslog): [nginx-http-auth] enabled = true port = http,https [apache-badbots] enabled = true port = http,https # … e as restantes jails por serviço (dovecot, exim, postfix-sasl, # mysqld-auth, vsftpd, portscan, pam-generic) — enabled = true
Em ignoreip indique obrigatoriamente o seu IP e as redes de confiança, caso contrário pode banir-se a si próprio. Após as alterações: sudo fail2ban-client reload.

Carregamento automático da lista de bloqueio ipsum — no cron do root (sudo crontab -e): o level 1 (mais de 100 mil IP) é carregado no conjunto ipsum, que é filtrado na firewall (mais detalhes na secção «Lista de bloqueio IPset»):

# 04:00 — atualização do ipset ipsum (level 1, cobertura máxima): 0 4 * * * /usr/local/bin/load-ipsum.sh >> /path/to/monitor/logs/cron.log 2>&1
O conjunto tem de se chamar ipsum — é esse que o dashboard lê (cartão «IPset ipsum»). Níveis: levels/1.txt — cobertura máxima, levels/3.txt — mais preciso (3+ fontes).

Porque é que o «Monitor de segurança» está dividido em duas zonas. A proteção funciona em dois níveis, e o dashboard não os mistura:

  • Ataques reais (reativo) — tudo o que o fail2ban apanhou: tentativas de invasão em curso (jails sshd, apache-*, nginx-*, etc.) e reincidentes graves (jail recidive — os que já foram banidos várias vezes). São IP que realmente o atacaram — estão no mapa de ataques e na «Linha temporal».
  • Bloqueio preventivo (proativo) — lista pública de bloqueio de IP maliciosos conhecidos ipset ipsum, filtrada na firewall pela regra DROP. Na sua maioria, estes endereços nem sequer chegaram ao seu servidor — são cortados antecipadamente; o contador «IPset ipsum» mostra quantos foram filtrados preventivamente.

A diferença é simples: reativo — «estes atacaram e foram banidos», preventivo — «estes foram bloqueados antes sequer de tentar». Antes, a level-3 do ipsum era colocada artificialmente na recidive (daí a antiga divisão «recidive por lista»); agora a recidive contém apenas reincidentes verdadeiros, e o preventivo fica inteiramente na firewall.

14. Lista de bloqueio IPset (ipsum)

ipsum — lista pública de IP maliciosos, atualizada diariamente. O Monitor mostra o número de endereços carregados no painel e no mapa de ataques e considera-o na Avaliação de segurança (−10 se o conjunto não estiver carregado).

Variante mínima sem fail2ban — um conjunto ipsum separado com bloqueio através de iptables:

# Criar o conjunto (uma vez): sudo ipset create ipsum hash:ip # Script de atualização /usr/local/bin/update-ipsum.sh: #!/bin/bash ipset flush ipsum for ip in $(curl -s https://raw.githubusercontent.com/stamparm/ipsum/master/levels/3.txt); do ipset add ipsum "$ip" 2>/dev/null done iptables -C INPUT -m set --match-set ipsum src -j DROP 2>/dev/null \ || iptables -I INPUT -m set --match-set ipsum src -j DROP # Cron (sudo crontab -e, diariamente às 4:00): 0 4 * * * /usr/local/bin/update-ipsum.sh
A variante avançada com fail2ban-recidive está na secção «Configuração operacional Fail2ban + ipsum».
O ipset reside na memória e perde-se ao reiniciar. Apenas o cron diário deixaria o conjunto vazio desde o arranque até à execução seguinte (o painel mostrará 0). Carregue o conjunto também no arranque — coloque o carregamento num script e associe-o a @reboot. Ao mesmo tempo, o comando create … -exist define o limite maxelem 300000 (por predefinição 65536 — o level 1 não cabe, dando «Hash is full»):
# /usr/local/bin/load-ipsum.sh #!/bin/bash curl -s https://raw.githubusercontent.com/stamparm/ipsum/master/levels/1.txt \ | grep -v '^#' | sed 's/^/add ipsum /' \ | (echo "create ipsum hash:ip hashsize 131072 maxelem 300000 -exist"; echo "flush ipsum"; cat) \ | ipset restore -exist # sudo crontab -e — diariamente às 04:00 E a cada arranque: 0 4 * * * /usr/local/bin/load-ipsum.sh @reboot sleep 60 && /usr/local/bin/load-ipsum.sh
Como funciona na instalação automática. O script carrega a lista completa level 1 (mais de 100 mil IP) no conjunto ipsum e, se a firewall for gerida pelo instalador (VPS novo — perfis «Completa»/«Ligeira»), liga o conjunto ao UFW com uma regra DROP — o tráfego desses IP é realmente bloqueado. A regra fica depois de ESTABLISHED,RELATED, por isso as ligações atuais (incluindo o seu SSH) não são cortadas — só se bloqueiam novas ligações da lista. O conjunto é restaurado no arranque do serviço ipsum-load.service antes da firewall (caso contrário o UFW não arrancaria) e é atualizado pelo cron às 04:00. Num servidor já configurado (painel, firewall próprio) o instalador não mexe na firewall — aí o ipsum permanece como lista para o painel e o mapa de ataques, e a regra DROP é adicionada manualmente se assim o desejar (a variante mínima com iptables … --match-set ipsum … -j DROP está acima). Na instalação automática não é preciso fazer nada manualmente.

15. Instalação do CrowdSec

Substituto moderno do Fail2ban com threat intelligence coletiva: bloqueios da comunidade mais regras próprias. Requer um bouncer separado para aplicar os bloqueios à firewall.

curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash sudo apt install crowdsec sudo systemctl enable --now crowdsec # Bouncer para iptables/nftables: sudo apt install crowdsec-firewall-bouncer-iptables # Verificar estado: sudo systemctl status crowdsec sudo cscli decisions list sudo cscli bouncers list
Estado «Não iniciado» no painel = o serviço está instalado, mas o serviço não está ativo (o monitor verifica-o através de systemctl is-active crowdsec). Iniciar: sudo systemctl enable --now crowdsec; em caso de falha, consultar sudo journalctl -u crowdsec -n 30. A mesma regra aplica-se a qualquer serviço no estado «Não iniciado» (Suricata, Falco, Monit, MySQL).
«0 cenários» ou «0 bouncers» no dashboard. O CrowdSec vem quase vazio de origem — sem coleções não deteta nada, e sem um bouncer registado os bloqueios não são aplicados à firewall. Instale as coleções básicas e confirme que o bouncer está na lista:
# Coleções básicas (Linux + SSH + servidor web): sudo cscli collections install crowdsecurity/linux crowdsecurity/sshd crowdsecurity/base-http-scenarios sudo systemctl reload crowdsec # O bouncer deve estar na lista e com estado de ligação ativa: sudo cscli bouncers list
No log do bouncer surge stream halted / os bloqueios não são aplicados. Trata-se de uma chave de API órfã: o bouncer foi removido de cscli bouncers list, mas a sua chave antiga permaneceu em /etc/crowdsec/bouncers/*.yaml. Volte a registar o bouncer e defina a nova chave:
sudo cscli bouncers add fw-bouncer # gera a nova api_key # insira esta chave em api_key: em /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml sudo systemctl restart crowdsec-firewall-bouncer
A instalação automática (perfil «Proteção completa») instala as coleções e regista o firewall-bouncer por si — manualmente só é necessário numa instalação manual ou após uma intervenção manual no CrowdSec.

16. Instalação do AIDE

AIDE (Advanced Intrusion Detection Environment) tira uma imagem do sistema de ficheiros e, em cada verificação, comunica alterações em /etc, /bin, /usr. Após a instalação, é obrigatório inicializar a base de dados (aideinit).

sudo apt install aide # Inicialização da base de dados (5–15 minutos): sudo aideinit sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db # Ubuntu 24.04: a diretoria /var/lib/aide é criada em modo 700 (dono _aide), # e o painel (www-data) não vê a base de dados → mostra «Não inicializada». # Abrir a diretoria para acesso (os ficheiros da base mantêm-se 600): sudo chmod 755 /var/lib/aide # Primeira verificação COM ESCRITA no log que o monitor lê. # No Ubuntu/Debian o aide exige --config explícito (senão «missing configuration»; # o binário aide.wrapper não é fornecido nas versões novas): sudo bash -c 'aide --config /etc/aide/aide.conf --check > /var/log/aide/aide.log 2>&1'
Durante o aideinit o terminal fica 5–15 minutos parado na linha Running aide --init... — é normal (hashing de todo o sistema de ficheiros, carga no disco). Não interrompa com Ctrl+C. Se o processo parecer «pendurado» mas não escrever nada — talvez esteja à espera de resposta a um pedido oculto Overwrite existing aide.db.new [Yn]? (prima Y). Verifique a atividade a partir de outra sessão: pgrep -af aide.
Erro no aideinit: «21_aide_spamassassin … printf: invalid number» (return code 20) — bug conhecido do snippet de configuração do AIDE no Ubuntu 22.04. A base de dados não é criada. Remova o snippet defeituoso e repita:
sudo mv /etc/aide/aide.conf.d/21_aide_spamassassin /etc/aide/21_aide_spamassassin.disabled sudo aideinit sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db
Estados no painel. «Não inicializada» = o painel não vê o ficheiro da base: ou o aideinit nunca foi executado, ou (Ubuntu 24.04) a diretoria /var/lib/aide foi criada em modo 700 e está inacessível ao www-data — resolve-se com sudo chmod 755 /var/lib/aide (ver o bloco acima). «Sem verificações» = a base existe, mas ainda não foi feita nenhuma verificação — não é um erro. O monitor lê os resultados de /var/log/aide/aide.log.
Verificação regular → log para o painel. O /etc/cron.daily/aide padrão nas versões recentes de Ubuntu/Debian pode não escrever /var/log/aide/aide.log no formato esperado (e o aide.wrapper já não existe nelas). É mais fiável adicionar o seu próprio cron com --config explícito — este escreve o log em nome de root no modo 644, e o monitor lê-o sem grupos adicionais:
# sudo crontab -e — verificação diária às 02:00: 0 2 * * * /usr/bin/aide --config /etc/aide/aide.conf --check > /var/log/aide/aide.log 2>&1 # Executar agora, sem esperar pelo agendamento: sudo bash -c 'aide --config /etc/aide/aide.conf --check > /var/log/aide/aide.log 2>&1'
A instalação automática já faz tudo isto: chmod 755 /var/lib/aide e o cron de verificação às 02:00 — não é preciso fazer nada manualmente.
Faça a primeira inicialização num servidor limpo — antes de instalar as aplicações web. Após alterações legítimas, recrie a base de dados: sudo aideinit && sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db.

17. Instalação do ClamAV

Antivírus para Linux. É especialmente útil para verificar /var/www em busca de shells PHP e código malicioso.

sudo apt install clamav clamav-daemon sudo systemctl enable --now clamav-daemon # Atualizar a base de assinaturas: sudo freshclam # Analisar a pasta manualmente: sudo clamscan -r /var/www --infected
O daemon clamd mostra «Inativo» após enable --now? Três causas típicas:

1. Ficou no ficheiro de configuração a linha Example — o clamd recusa-se a iniciar enquanto ela existir:

sudo sed -i '/^Example/d' /etc/clamav/clamd.conf sudo systemctl restart clamav-daemon

2. A base de assinaturas não foi descarregada — o clamd não inicia sem ela:

sudo systemctl stop clamav-freshclam sudo freshclam sudo systemctl start clamav-freshclam sudo systemctl restart clamav-daemon

3. Está simplesmente a carregar — o clamd carrega ~8 milhões de assinaturas para a memória em 30–60 seg. Aguarde e verifique: systemctl is-active clamav-daemon (estado activating → ainda a carregar).

Diagnóstico: sudo journalctl -u clamav-daemon -n 30 --no-pager.
No painel aparece «Ficheiros analisados: 0» / «Última análise: —»? O daemon clamd apenas mantém as assinaturas na memória, ele próprio não analisa nada por agendamento. O painel mostra os resultados da análise agendada, por isso é necessário um cron que analise e escreva o registo. A instalação automática coloca o invólucro /usr/local/bin/clamav-scan.sh e um cron às 01:30 — após a primeira execução preenchem-se «Ficheiros analisados» e «Última análise». Para executar já, sem esperar pelo agendamento: sudo /usr/local/bin/clamav-scan.sh.

18. Instalação do Linux Malware Detect (maldet)

Linux Malware Detect (LMD) — analisador de software malicioso para ameaças web: shells PHP, backdoors web, downloaders. Usa o motor do ClamAV e complementa-o com as suas próprias assinaturas.

wget https://www.rfxn.com/downloads/maldetect-current.tar.gz tar -xzf maldetect-current.tar.gz dir=$(ls -d maldetect-*/ | head -1) && cd "$dir" && sudo bash install.sh && cd ~ rm -rf maldetect-* maldetect-current.tar.gz # Atualizar assinaturas: sudo maldet -u # Analisar /var/www: sudo maldet -a /var/www
O LMD e o ClamAV funcionam bem em conjunto. Último relatório: maldet --report.
Durante a instalação pode surgir a linha update-rc.d: error: unable to read /etc/init.d/maldet — é inofensiva. O maldet não usa o init.d; a atualização de assinaturas e as análises são executadas via /etc/cron.daily/maldet. Se abaixo vir installation completed, então instalou tudo.
A página do LMD indica «Não instalado» apesar de estar instalado? O maldet não é instalado via apt, mas sim em /usr/local/maldetect, e com o open_basedir ativo a sua presença é verificada via shell — consulte a secção «A página está vazia apesar de haver dados no servidor».

19. Instalação do Suricata

Sistema de deteção de intrusões na rede: analisa o tráfego ao nível dos pacotes e conhece milhares de assinaturas de ataques. Complementa o ModSecurity (este atua ao nível HTTP, o Suricata ao nível TCP/IP).

sudo add-apt-repository ppa:oisf/suricata-stable sudo apt update && sudo apt install suricata # Descarregar as regras atuais: sudo suricata-update sudo systemctl enable --now suricata
O Suricata está «Ativo», mas o painel não mostra alertas / o número de eventos é 0? O Suricata escreve em /var/log/suricata/eve.json como root com permissões 750 na diretoria, e o servidor web (www-data) não a consegue ler. Abra a diretoria para acesso — os ficheiros dentro dela permanecem protegidos:
sudo chmod o+rx /var/log/suricata
A instalação automática faz isto sozinha — não é preciso fazê-lo manualmente.

20. Instalação do Falco

Interceta chamadas de sistema através de eBPF/kernel module e deteta anomalias em tempo real: shell a partir do nginx, leitura de /etc/passwd por um processo web, escrita em /bin, etc.

curl -fsSL https://falco.org/repo/falcosecurity-packages.asc > /tmp/falco.asc gpg --dearmor < /tmp/falco.asc | sudo tee /usr/share/keyrings/falco-archive-keyring.gpg > /dev/null sudo chmod 644 /usr/share/keyrings/falco-archive-keyring.gpg && rm /tmp/falco.asc echo "deb [signed-by=/usr/share/keyrings/falco-archive-keyring.gpg] https://download.falco.org/packages/deb stable main" \ | sudo tee /etc/apt/sources.list.d/falcosecurity.list sudo apt update && sudo apt install falco sudo systemctl enable --now falco
O Monitor lê os eventos do Falco através de journalctl -u falco (sem sudo — pelo grupo systemd-journal). Certifique-se de que o www-data está nesse grupo — consulte «Configuração de sudo» (ponto 2) na página de instalação manual.
«0 eventos em 24 horas» é o normal, não um erro. O Falco é event-driven: mantém-se em silêncio enquanto está tudo bem e regista um evento apenas perante uma anomalia (shell a partir de um processo web, leitura de /etc/passwd, escrita em diretórios do sistema). Zero eventos críticos num dia num servidor calmo é um estado saudável.
Para o painel, a saída para ficheiro é mais fiável. A leitura através de journalctl exige permissões sobre o journal; para que o painel veja os eventos de forma estável, a instalação automática ativa no Falco o file_output → /var/log/falco/falco.log e define UMask=0022 no serviço (o log é lido pelo servidor web). Numa nova instalação não é necessário configurar isto manualmente.

21. Instalação do ModSecurity (WAF)

ModSecurity — firewall de aplicações web (WAF) para Apache ou Nginx. Bloqueia ataques ao nível da aplicação: injeções SQL, XSS, travessia de diretórios, scanners.

# Apache: sudo apt install libapache2-mod-security2 sudo a2enmod security2 # Conjunto de regras OWASP Core Rule Set: sudo apt install modsecurity-crs # OBRIGATÓRIO: sem este ficheiro o motor de regras está desligado sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf sudo sed -i 's/^SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/modsecurity/modsecurity.conf sudo apache2ctl configtest && sudo systemctl reload apache2 # Verificação: deve devolver 403 curl -s -o /dev/null -w '%{http_code}\n' "https://monitor.example.com/?id=1%20UNION%20SELECT%201,2--"
A instalação do pacote, por si só, não protege nada. O Apache inclui os ficheiros de configuração com a linha IncludeOptional /etc/modsecurity/*.conf, mas o pacote instala apenas modsecurity.conf-recommended — que não corresponde à máscara *.conf. Se não o copiar para modsecurity.conf, o SecRuleEngine mantém-se Off: o módulo está carregado, as regras CRS estão carregadas, mas o tráfego não é verificado e o registo de auditoria não é criado. O modo intermédio DetectionOnly apenas escreve eventos no registo, sem bloquear pedidos — o painel apresenta-o a amarelo.

Acesso do painel ao registo de auditoria. O registo /var/log/apache2/modsec_audit.log pertence ao root (permissões 640), o utilizador web não o consegue ler. O painel obtém os dados através de um wrapper — crie-o:

sudo tee /usr/local/bin/monitor-modsec >/dev/null <<'EOF' #!/bin/sh echo "ENGINE=$(grep -hE '^SecRuleEngine[[:space:]]+' /etc/modsecurity/*.conf 2>/dev/null | tail -1 | awk '{print $2}')" echo "---LOG---" tail -n 3000 /var/log/apache2/modsec_audit.log 2>/dev/null echo "---RULES---" for f in /etc/modsecurity/crs/rules/*.conf /usr/share/modsecurity-crs/rules/*.conf /etc/modsecurity/custom-rules.conf; do [ -f "$f" ] && { echo "===FILE:$(basename "$f")==="; cat "$f"; } done EOF sudo chown root:root /usr/local/bin/monitor-modsec sudo chmod 755 /usr/local/bin/monitor-modsec # em /etc/sudoers.d/monitor (utilizador = aquele com que o PHP-FPM corre): # www-data ALL=(ALL) NOPASSWD: /usr/local/bin/monitor-modsec
O wrapper obtém a última diretiva SecRuleEngine sem indentação: as linhas indentadas estão dentro de blocos <LocationMatch>/<Directory> (por exemplo, a desativação do WAF para o phpMyAdmin) e não definem o modo global.
O utilizador em sudoers deve coincidir com o utilizador do pool FPM: num Apache/Debian normal é o www-data, no HestiaCP o pool do site corre com o dono do site (por exemplo, admin) — verifique com grep -E '^user' /etc/php/*/fpm/pool.d/monitor.example.com.conf.
Se o site estiver atrás de um proxy Nginx (HestiaCP), o Apache vê o próprio proxy como cliente — o painel obtém o IP real do atacante a partir do cabeçalho X-Forwarded-For. Só entram nas estatísticas as transações com uma regra acionada: a diretiva SecAuditLogRelevantStatus escreve no registo de auditoria quaisquer respostas 4xx/5xx, por isso também lá entram os 403/500 normais — o painel não os conta como eventos do WAF.
O bloco ---RULES--- é necessário para a secção «Todas as regras ativas» — o painel apresenta não só as regras acionadas, mas todas as regras CRS carregadas + as personalizadas. Os três caminhos no ciclo for f in … são os locais típicos para as regras CRS e para as adições locais; se tiver uma disposição diferente (o pacote coloca os ficheiros no seu próprio diretório, ou as regras personalizadas não estão em /etc/modsecurity/custom-rules.conf), encontre os caminhos reais com o comando sudo grep -rl 'IncludeOptional\|^Include ' /etc/apache2/mods-enabled/security2.conf /etc/apache2/conf-enabled/*.conf 2>/dev/null e coloque-os na lista. Se o wrapper for antigo (sem esta secção) — a secção apenas apresenta o aviso «indisponível», o resto da página funciona como antes.

22. Instalação do Auditd

Auditd (Linux Audit Daemon) regista chamadas de sistema ao nível do kernel: inícios e términos de sessão, comandos sudo, tentativas de autenticação falhadas e alterações de ficheiros. O Monitor mostra os inícios de sessão, as tentativas falhadas e os comandos sudo de hoje.

sudo apt install auditd audispd-plugins sudo systemctl enable --now auditd # Verificar o estado e os eventos: sudo systemctl status auditd sudo ausearch -m USER_LOGIN -ts today
O Monitor lê os eventos através do ausearch (/usr/sbin/ausearch) e, se necessário, a partir de /var/log/audit/audit.log com o comando tail. Ambos têm de estar no sudoers.

23. Instalação do Monit

Vigia os serviços (nginx, php-fpm, mysql, etc.) e reinicia-os em caso de falha. Pode enviar alertas por email.

sudo apt install monit sudo systemctl enable --now monit # Configurações: sudo nano /etc/monit/monitrc ls /etc/monit/conf.d/
O Monitor obtém a lista de serviços através de monit status. Em /etc/monit/monitrc a interface HTTP deve estar ativada (bloco set httpd com allow localhost), caso contrário monit status devolve um erro.
«0 serviços sob monitorização» no painel? Duas razões. (1) A interface HTTP está desativada — em monitrc a linha set httpd está comentada (por predefinição surge como # set httpd port 2812 …). Descomente o bloco e permita o localhost. (2) O httpd ativado por si só não monitoriza nada — o Monit considera apenas o que está descrito em blocos check; sem eles a lista fica vazia mesmo com a interface a funcionar. Configuração mínima funcional:
# /etc/monit/conf.d/00-httpd — interface HTTP para localhost: set httpd port 2812 use address localhost allow localhost # exemplos de blocos check (o que monitorizar): check process sshd with pidfile /run/sshd.pid start program = "/usr/bin/systemctl start ssh" stop program = "/usr/bin/systemctl stop ssh" check filesystem rootfs with path / if space usage > 90% then alert
sudo monit -t # verificar a sintaxe (Control file syntax OK) sudo systemctl reload monit sudo monit status
A instalação automática coloca um conf.d pronto com httpd na 2812 e um conjunto de verificações — numa instalação nova não é preciso configurar manualmente.
Serviço no estado «Com erros»? O Monitor apenas mostra o estado e, propositadamente, não reinicia os serviços a partir do painel web (isso seria execução remota de comandos root num painel de segurança). O diagnóstico e o reinício fazem-se por SSH através do Monit:
sudo monit status <service> # causa do erro sudo monit restart <service> # reinício através do Monit # se o Monit não iniciar o serviço — consulte o unit próprio: sudo systemctl status <unit> --no-pager sudo journalctl -u <unit> -n 50 --no-pager

24. Instalação do PSAD (deteção de varrimento de portas)

PSAD analisa o registo do iptables e deteta varrimentos de portas e ataques de rede, atribuindo a cada origem um nível de ameaça (1–5). Complementa o fail2ban e o Suricata.

sudo apt install psad # O PSAD lê o log do iptables — é preciso ativar o registo (o UFW faz isto sozinho). # Para iptables puro, adicione regras LOG às cadeias INPUT/FORWARD. sudo psad --sig-update sudo systemctl enable --now psad
O Monitor lê os dados através de psad --Status (necessário no sudoers). Sem o registo do iptables, a página fica vazia — é normal enquanto não houver varrimentos.

25. AppArmor / SELinux (controlo de acesso)

O Mandatory Access Control limita a que ficheiros e recursos um programa pode aceder, mesmo que este tenha sido comprometido. No Ubuntu/Debian, é usado por predefinição o AppArmor (normalmente já instalado e ativo).

# AppArmor (Ubuntu/Debian): sudo apt install apparmor apparmor-utils sudo systemctl enable --now apparmor sudo aa-status # verificar perfis
O monitor lê o estado através do aa-status (necessário no sudoers). Mostra o número de perfis em modo enforce/complain e os processos sem perfil.

«Perfis carregados» ser mais do que enforce + complain é normal. No AppArmor 4.x (Ubuntu 24.04 e mais recente) surgiu o modo unconfined: o perfil está carregado no kernel, mas não limita nada. O Ubuntu marca assim dezenas de perfis para programas que usam user namespaces (navegadores, clientes torrent e afins). Quando existem esses perfis, o cartão «Perfis carregados» fica âmbar e mostra o seu número — por exemplo unconfined: 90 com 120 carregados e 26 em enforce. Só protegem de facto os perfis em enforce; no Ubuntu 22.04 (AppArmor 3.x) este modo não existe e os números batem sempre certo.

sudo aa-status | grep -E "profiles are" # divisão por modos sudo aa-enforce /etc/apparmor.d/profile-name # passar o perfil para enforce
Passar para enforce os perfis que o Ubuntu deixou intencionalmente em unconfined só deve ser feito de forma consciente: não estão desativados por engano, mas porque de outro modo os próprios programas deixam de funcionar. Os perfis em complain são outra questão: aí as regras já estão escritas e apenas não são aplicadas.

26. Instalação do debsums (integridade dos pacotes)

O debsums verifica se os ficheiros dos pacotes instalados coincidem com as somas de verificação do repositório — deteta binários de sistema adulterados (complementa o AIDE). Uma verificação completa demora 1–2 minutos, por isso é executada por cron, e o painel lê o resultado de data/debsums/debsums.log e classifica-o por categorias (só importam os binários e as bibliotecas).

A tarefa fica no cron do root (sudo crontab -e). O wrapper pronto debsums-scan.sh é colocado em /usr/local/bin/ (chmod +x; ver o resumo das tarefas cron) e ele próprio grava o relatório em data/debsums/ do painel.

sudo apt install debsums # Linha cron (diariamente às 4:30): 30 4 * * * /usr/local/bin/debsums-scan.sh >> /path/to/monitor/logs/cron.log 2>&1

O wrapper debsums-scan.sh encontra sozinho a pasta data/ do painel — não é preciso indicar o caminho.

Alterações em /etc/ (configurações) e /usr/share/ (recursos) no servidor costumam ser normais — o painel assinala-as com uma cor distinta. Preocupantes são as alterações de binários e bibliotecas (/bin, /sbin, /usr/lib, etc.) — o cartão «Binários / bibliotecas» mostra precisamente essas.

27. Configuração dos relatórios do Lynis

O Lynis é executado manualmente ou por cron. O relatório deve ser guardado na pasta data/lynis/ do projeto — o monitor lê o ficheiro lynis-report.dat.

# Execução única (indique o seu caminho para a raiz do painel): sudo lynis audit system --report-file /path/to/monitor/data/lynis/lynis-report.dat # Auditoria diária — linha do cron (wrapper lynis-scan.sh pronto em /usr/local/bin/, ver resumo): 0 3 * * * /usr/local/bin/lynis-scan.sh >> /path/to/monitor/logs/cron.log 2>&1
Após a primeira execução, a página «Auditoria Lynis» mostra logo o hardening index, os avisos e as recomendações.
Botão «Executar auditoria» na página do Lynis. Executa o lynis-scan.sh em segundo plano diretamente a partir do painel (sem esperar pelo cron): mostra «A analisar…» e, ao concluir, atualiza o relatório automaticamente. Para isso, o utilizador web precisa de uma linha no sudoers para executar o script — o instalador adiciona-a automaticamente em /etc/sudoers.d/monitor. Se o painel foi instalado manualmente/anteriormente, acrescente-a com o mesmo utilizador já indicado no ficheiro:
u=$(sudo awk '/NOPASSWD/ && !/lynis-scan/ {print $1; exit}' /etc/sudoers.d/monitor) [ -n "$u" ] && echo "$u ALL=(ALL) NOPASSWD: /usr/local/bin/lynis-scan.sh" | sudo tee -a /etc/sudoers.d/monitor sudo visudo -c && sudo chmod 440 /etc/sudoers.d/monitor

28. Configuração de relatórios Logwatch

O Logwatch deve guardar os relatórios diários na pasta data/logwatch/ do projeto em formato .txt. O monitor mostra o último relatório e o arquivo.

# Diariamente (6:00) — linha cron (wrapper pronto logwatch_daily.sh em /usr/local/bin/, ver resumo): 0 6 * * * /usr/local/bin/logwatch_daily.sh >> /path/to/monitor/logs/cron.log 2>&1

Módulos do painel

29. Monitor de rede (integrado)

O monitor de rede não requer instalação — é uma página integrada no painel. Mostra o estado de rede do servidor a partir de fontes locais:

  • interfaces e tráfego — de /proc/net/dev;
  • estado das ligações (UP/DOWN) e IP — através de ip;
  • ligações e portas em escuta — através de ss;
  • eventos de rede do kernel nas últimas 24 h — através de journalctl -k.

As três primeiras fontes funcionam sem sudo, por isso as interfaces, o tráfego, as ligações e as portas ficam visíveis de imediato. O bloco «Eventos do kernel» utiliza journalctl -k — é lido através do grupo systemd-journal («Configuração do sudo», p.2), não é preciso sudo. Para verificar que tudo está acessível ao utilizador web:

# Verificação em nome de www-data (é sob ele que o PHP corre): sudo -u www-data bash -c 'cat /proc/net/dev' sudo -u www-data bash -c 'ip -o link show' sudo -u www-data bash -c 'ss -s' sudo -u www-data journalctl -k --no-pager -n 5
O bloco «Eventos de rede do kernel» mostra eventos da pilha de rede do kernel (mudança de ligação up/down, erros de portadora, «network unreachable»). Os registos da firewall UFW BLOCK não aparecem aqui — estão nas páginas «Firewall UFW» e «Mapa de ataques». Um bloco vazio com um visto verde = não houve falhas de rede nas últimas 24 horas.

30. Disco e SMART

A página integrada mostra três coisas:

  • Sistemas de ficheiros — ocupação das partições (df); a barra fica vermelha a partir de ≥90%;
  • Unidades de armazenamento — lista de discos (lsblk), apenas os reais (loop/snap ocultos);
  • Saúde (SMART) — estado do disco e atributos (smartctl).

O espaço e a lista de dispositivos funcionam de imediato, sem configuração. Para o SMART é necessário o pacote smartmontools. O processo web não tem acesso direto aos dispositivos de disco, por isso o SMART é recolhido por cron para o ficheiro data/disk/smart.txt, e o painel lê-o.

A tarefa fica no cron do root (sudo crontab -e). O wrapper pronto smart-scan.sh é colocado em /usr/local/bin/ (chmod +x; ver o resumo das tarefas cron) e escreve ele próprio em data/disk/ do painel.

sudo apt install smartmontools # Linha de cron (a cada 30 minutos): */30 * * * * /usr/local/bin/smart-scan.sh >> /path/to/monitor/logs/cron.log 2>&1

O wrapper smart-scan.sh encontra ele próprio a pasta data/ do painel — não é preciso indicar o caminho. Internamente, lsblk -e7,11 exclui loop/cdrom.

Em discos virtuais (QEMU/KVM e semelhantes) normalmente só está disponível o estado geral «saúde: OK», enquanto a temperatura, as horas de funcionamento e os setores realocados podem estar vazios — isto é normal. Num servidor físico são apresentados todos os atributos.

31. Desempenho (CPU/RAM/Rede/Disco)

A página mostra o histórico de carga do servidor nas últimas 24 horas — Load Average, ocupação da CPU e espera de I/O, RAM/Swap, tráfego de rede (receção/envio), I/O de disco (leitura/escrita), ocupação do disco e inodes, descritores de ficheiro abertos e ligações MySQL, além do número atual de ligações TCP e processos. A janela de 24 horas é a predefinida: os botões no topo mudam-na para 7 dias ou 30 dias.

Os dados são recolhidos pelo cron/collect_metrics.php — a cada 5 minutos regista um instantâneo «em bruto» dos contadores (/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') na tabela da BD system_metrics; as percentagens e velocidades são calculadas pela própria página com base na diferença entre instantâneos consecutivos (ocupação do disco/inodes/descritores/ligações MySQL — valores instantâneos, sem recálculo). Não é necessário sudo — as fontes são lidas sem privilégios de root.

Duas profundidades de armazenamento. Os instantâneos «em bruto» de cinco minutos vivem um dia (mais uma hora de margem, para que o ponto mais antigo do gráfico ainda tenha um antecessor para o cálculo). Cada hora terminada é dobrada numa única linha; essa série horária é guardada 30 dias e é precisamente ela que as vistas «7 dias» e «30 dias» leem. Assim, um mês de histórico custa cerca de 720 linhas em vez de aproximadamente 8600.

Nos períodos de 7 e 30 dias são mostradas médias por hora e não medições individuais — um pico de cinco minutos é suavizado a essa escala, e a página avisa disso. Para o Load Average a linha horária guarda tanto a média como o pico da hora, pelo que o gráfico mostra as duas linhas. Logo após uma atualização, os períodos longos estão vazios («ainda não há histórico para este período») — a série horária começa a acumular-se a partir desse momento.
# Linha do cron (a cada 5 minutos): */5 * * * * /usr/local/bin/collect-metrics-all.sh >> /path/to/monitor/logs/cron.log 2>&1

O wrapper collect-metrics-all.sh (ver o resumo das tarefas cron) encontra automaticamente todas as instâncias do painel instaladas no servidor e executa o cron/collect_metrics.php de cada uma em nome do proprietário do site.

Enquanto o coletor não tiver corrido pelo menos duas vezes (os primeiros ~10 minutos após a instalação), a página mostra «a recolher dados» — os gráficos precisam de, no mínimo, um par de pontos consecutivos para calcular velocidades e percentagens.

Processos principais. Por baixo dos gráficos há duas tabelas com os cinco processos que mais CPU consomem:

  • «Processos na última medição» — o que carregava o servidor no momento do último instantâneo (a hora está no cabeçalho do cartão);
  • «Processos no pico do dia» — os mesmos cinco, mas retirados da medição com o Load Average mais alto das últimas 24 horas. É a resposta à pergunta «o que foi aquilo durante a noite», quando o pico no gráfico já passou.

Ambas as tabelas descrevem sempre as últimas 24 horas, mesmo quando os gráficos mostram um mês: o instantâneo dos processos só existe nas linhas de cinco minutos, e essas não são guardadas mais de um dia.

Os processos da própria medição (ps, o script PHP de recolha e a cadeia cron/sudo acima dele) são excluídos da lista — caso contrário encabeçariam todos os instantâneos: o ps, no momento em que arranca, mostra quase 100 % de CPU. As restantes tarefas cron que correram no mesmo minuto aparecem, e é exatamente para isso que a tabela serve.

Alertas de carga (secção «Definições» → «Alertas de carga») — ao ser ultrapassado o limiar de CPU/RAM/disco/inodes, o painel envia uma notificação por todos os canais ativados (Telegram, Email, webhook, push — os mesmos do relatório diário; não é preciso ativar nada à parte para os alertas) e outra quando a métrica volta ao normal. Enquanto o limiar se mantém, não repete o aviso: a notificação seguinte só chega depois de um ciclo «voltou ao normal → voltou a ser ultrapassado».

Os limites são verificados pelo mesmo collect_metrics.php a cada execução (a cada 5 minutos) — não é preciso um cron à parte. O estado «já notificado / ainda não» é guardado em data/alerts_state.json, e os limites nas definições do painel.

32. Eventos de segurança (um sensor a cada 5 minutos)

O relatório diário responde à pergunta «como está o servidor». O sensor responde a outra — «o que acabou de acontecer». A cada 5 minutos consulta as fontes rápidas e regista apenas o que mudou desde a verificação anterior; a página «Eventos de segurança» no menu mostra o registo das deteções.

O sensor não toca nas verificações lentas — Lynis, debsums, atualizações de pacotes, validade do SSL: os seus números mudam de qualquer forma uma vez por dia e ficam a cargo do relatório diário.

O que deteta — nove fontes:

  • Fail2ban — novos banimentos (é comparada a lista de endereços banidos, pelo que um desbanimento não é um evento e um banimento em vigor é comunicado exatamente uma vez);
  • CrowdSec — novas decisões;
  • Falco — eventos de nível Emergency, Alert e Critical;
  • Suricata — novos alertas; a marca [Priority: 1] eleva a deteção a crítica;
  • ClamAV — novas linhas FOUND no registo da análise;
  • AIDE — alterações de ficheiros segundo a última verificação (ou seja, não mais frequentemente do que ela é executada pelo seu próprio horário);
  • SSH — uma sessão iniciada com êxito como root (crítico) e um início de sessão a partir de um endereço nunca visto (aviso);
  • Serviços de proteção — UFW, Fail2ban, CrowdSec, Suricata, Falco: uma mensagem quando um serviço para e outra quando volta a funcionar;
  • Portas — uma porta que ficou acessível do exterior; se por trás dela estiver uma base de dados ou uma cache, é crítico.

Uma ferramenta que não está instalada, ou cujo registo o painel não consegue ler, simplesmente cala-se — isso não é um erro nem um evento.

A primeira verificação não envia nada. Cada fonte guarda uma marca de «até onde já leu» (ficheiro data/secwatch_state.json) e a primeira passagem apenas escreve essa marca. Caso contrário, ao ativar a vigilância chegaria uma notificação sobre tudo o que os registos acumularam durante meses.

Configuração — «Definições» → «Vigilância de eventos»:

  • Ativar a vigilância — enquanto estiver desativada, a tarefa cron corre em vazio e o registo não se enche;
  • Notificar a partir de — «Aviso» ou «Crítico». As deteções abaixo do limiar chegam na mesma ao registo, mas não às notificações;
  • Pausa, min — intervalo mínimo entre notificações, 15 por predefinição;
  • Horas de silêncio — uma janela no formato 23-7 durante a qual os avisos não são enviados.
As deteções críticas saem de imediato — nem a pausa nem as horas de silêncio as retêm: é precisamente para elas que existe um sensor de 5 minutos. Ainda assim há um fusível: no máximo 6 notificações por hora, para o caso de uma fonte começar a oscilar.

As notificações seguem pelos mesmos canais do relatório diário (Telegram, Email, webhook, push) — não é preciso ativar nada à parte. Todas as deteções são escritas no registo independentemente de ter sido enviada ou não uma notificação, e são guardadas 90 dias.

O registo. Filtros: período (24 horas, 7, 30 ou 90 dias), importância e fonte; são mostrados os últimos 500 registos. O botão «Verificar agora» executa exatamente a mesma passagem que o cron — prático logo a seguir à configuração, para não esperar cinco minutos; indica também o motivo quando não foi enviada nenhuma notificação («abaixo do limiar de importância», «horas de silêncio», «nenhum canal de notificação configurado»). O contador vermelho junto ao item do menu é o número de eventos críticos das últimas 24 horas.

A ficha do endereço. Clicar numa linha do registo de eventos ou do bloco Endereços relacionados abre tudo o que se sabe sobre um endereço: a cronologia dos seus eventos, o país e, logo na primeira linha, uma conclusão. A linha do registo abre a ficha quando o evento diz respeito a exatamente um endereço; se indicar vários, as ligações são os próprios endereços dentro da linha. As conclusões são quatro:

  • Acesso possível — houve um acesso bem-sucedido a partir do endereço e esse mesmo endereço foi detetado por outras defesas, ou logo a seguir atuaram o AIDE, o ClamAV ou abriu-se uma porta nova. O único caso que vale a pena verificar de imediato;
  • Início de sessão a partir deste endereço — há um acesso e mais nada. Costuma ser o proprietário a partir de um local novo, por isso a ficha não fica vermelha;
  • Bloqueado — o endereço foi detetado e bloqueado: a defesa fez o seu trabalho, não há nada a fazer;
  • Requer verificação — há deteções mas não houve bloqueio. Se o endereço voltar, feche-o manualmente.

Por baixo da cronologia está o bloco O que acontecia no servidor ao mesmo tempo: eventos que não têm endereço nenhum. O AIDE não sabe quem alterou o ficheiro, o ClamAV não sabe quem trouxe o infetado. São mostrados os que coincidem com a atividade do endereço e as seis horas seguintes. É precisamente o acesso em conjunto com o que se seguiu que separa uma tentativa de um acesso real.

A relação é construída pelo endereço. Um atacante que muda de endereço quebra-a. A ficha mostra o que as defesas viram e não se compromete a distinguir uma intrusão do seu próprio acesso a partir de um local novo.

Horário. A tarefa vai para o cron do root (sudo crontab -e), desfasada 2 minutos da recolha de métricas, para que as duas não arranquem ao mesmo tempo:

# a cada 5 minutos — verificação dos eventos de segurança 2-57/5 * * * * /usr/local/bin/security-watch-all.sh >> /path/to/monitor/logs/cron.log 2>&1
Execute-a apenas através do wrapper, nunca diretamente com php cron/security_watch.php. O wrapper encontra todas as instalações do painel no servidor e executa a verificação em nome do proprietário do site — com os mesmos direitos de sudo que o painel usa a partir da web. Uma chamada direta a partir do cron do root criaria o ficheiro de estado data/secwatch_state.json pertencente ao root, e o botão «Verificar agora» deixaria de funcionar.
Se preparou o servidor com a instalação automática, a tarefa já está lá — resta apenas ativar a vigilância nas definições.

33. Servidor web: pedidos e códigos de resposta

A página «Servidor web» analisa o registo de acessos e mostra quantos pedidos houve, que parte terminou em erro e quem os fez. O ModSecurity responde à pergunta «o que foi bloqueado»; esta página responde à pergunta «o que chegou ao site».

O que se vê: número de pedidos, RPS, percentagem de erros, um contador separado de 5xx e o volume servido; um gráfico de pedidos por hora; a distribuição dos códigos 2xx/3xx/4xx/5xx; endereços frequentes, IP frequentes, erros frequentes (código mais caminho) e clientes (User-Agent). Período: 24 horas ou 7 dias.

A fonte é o registo de acessos no formato combined, pelo que servem tanto o Nginx como o Apache e não é preciso qualquer módulo adicional. O painel procura o registo entre os ficheiros padrão:

  • /var/log/nginx/access.log
  • /var/log/apache2/access.log
  • /var/log/apache2/other_vhosts_access.log
  • /var/log/httpd/access_log

Além destes, o painel verifica os ficheiros *access*.log e *requests*.log em /var/log/nginx/, /var/log/apache2/ e /var/log/httpd/. Assim encontra também o registo que um site escreve num ficheiro próprio com a diretiva CustomLog — por exemplo arciveo-monitor_requests.log, criado pelo instalador do Arcivéo.

É lido apenas um registo: quando o Nginx está à frente do Apache, somar dois registos contaria cada pedido duas vezes. É usado o primeiro registo da lista acima em que se escreveu nas últimas 24 horas (os encontrados pelo padrão de nome são testados a seguir, dos mais recentes para os mais antigos); se em nenhum se escreveu durante um dia, é usado o não vazio mais recente. Assim, um registo esvaziado pelo logrotate ou deixado por um servidor web parado já não esconde o que está ativo. É lido o último fragmento do registo (3 MB) e o resultado fica em cache durante 5 minutos — por isso, num site com muito tráfego, o período escolhido pode não ficar totalmente coberto; a cobertura efetiva está indicada por cima do gráfico.

«O painel não consegue ler o registo do servidor web». A causa são quase sempre as permissões: os registos e o respetivo diretório pertencem ao grupo adm e o utilizador web não pertence a ele. O instalador do Arcivéo acrescenta-o a esse grupo; após uma instalação manual, resolve um pequeno wrapper de root a quem é permitida uma única coisa — mostrar o registo (a página do ModSecurity funciona da mesma forma). O wrapper escolhe o registo pelas mesmas regras que o painel. O comando aparece na própria página; aqui fica também. Cole-o por inteiro — é um único comando:
sudo tee /usr/local/bin/monitor-weblog >/dev/null <<'EOF' && sudo chmod 755 /usr/local/bin/monitor-weblog && echo 'www-data ALL=(root) NOPASSWD: /usr/local/bin/monitor-weblog' | sudo tee /etc/sudoers.d/monitor-weblog >/dev/null && sudo visudo -c #!/bin/sh L="/var/log/nginx/access.log /var/log/apache2/access.log /var/log/apache2/other_vhosts_access.log /var/log/httpd/access_log $(ls -t /var/log/nginx/*access*.log /var/log/apache2/*access*.log /var/log/apache2/*requests*.log /var/log/httpd/*access_log 2>/dev/null)" for f in $L; do [ -r "$f" ] && [ -n "$(find "$f" -size +0 -mmin -1440 2>/dev/null)" ] && exec tail -c 3000000 "$f"; done for f in $(ls -t $L 2>/dev/null); do [ -r "$f" ] && [ -s "$f" ] && exec tail -c 3000000 "$f"; done EOF

O utilizador web pode não ser o www-data — verifique com que utilizador corre a pool PHP-FPM do site (ps -o user= -C php-fpm) e indique-o na linha do sudoers. Se o nome do registo não contiver nem access nem requests, acrescente o respetivo caminho no início da linha L=… dentro do wrapper.

«Não há registos para o período selecionado» — o registo foi lido, mas não se encontraram linhas correspondentes: ou o site não foi realmente aberto, ou o registo acabou de ser rodado (logrotate) e começou do zero.

Tentativas de exploits conhecidos. O painel compara os pedidos do registo com as assinaturas de cerca de vinte exploits comuns: PHP-CGI, travessia de diretórios no Apache, PHPUnit, Log4Shell e falhas de routers, câmaras e gateways VPN. Para cada tentativa indica se pode funcionar neste servidor, com base em factos: as versões instaladas do Apache e do PHP, a presença de Java e o código com que o servidor respondeu ao pedido do ficheiro. Só fica a vermelho o que pode realmente funcionar.

Proteção do servidor web. O painel pede cada site do servidor através de 127.0.0.1 e verifica o que um visitante recebe: se as versões do servidor web e do PHP estão visíveis, se o método TRACE está ativo e se existem os cabeçalhos de proteção Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy e Content-Security-Policy. Os sites vêm do endereço com que o painel é aberto e dos ficheiros de anfitriões virtuais, se o painel os conseguir ler. O resultado é atualizado a cada 10 minutos.

Se faltar algo, surge um único comando por baixo da tabela. Cria um ficheiro separado, zz-arciveo-hardening.conf, e só recarrega o servidor web depois de o teste de configuração passar; os cabeçalhos que o site já envia não são duplicados. A CSP não é proposta numa linha: a política é construída para cada site; caso contrário, este deixa de carregar os próprios scripts, estilos e tipos de letra. Para anular a alteração no Apache:

sudo a2disconf zz-arciveo-hardening && sudo systemctl reload apache2
A assinatura nas páginas de erro (ServerSignature) não é assinalada se a versão do servidor web já estiver oculta: sem versão mostra apenas o nome do servidor e o nome do anfitrião.

34. Mapa de ataques (GeoIP)

A página «Mapa de ataques» determina o país a partir do IP com o comando geoiplookup. Sem o pacote GeoIP os países não são determinados e os pontos não aparecem no mapa:

sudo apt install geoip-bin geoip-database # Verificação: geoiplookup 8.8.8.8
Sudo não é necessário — a base /usr/share/GeoIP/GeoIP.dat é lida por todos, os resultados ficam em cache em tmp/geoip_cache.json. O próprio mapa (Leaflet + tiles do OpenStreetMap) carrega no navegador — é preciso Internet no computador onde o painel está aberto.

35. Exposição externa, atualizações e atualizações automáticas

Dois cartões integrados no dashboard que mostram não o «ligado/desligado» de uma ferramenta, mas a proteção real do servidor. Não requerem instalação e são lidos localmente sem sudo.

Exposição externa — quantos serviços escutam todas as interfaces (0.0.0.0/[::]) e estão acessíveis do exterior. Fica vermelho se uma base de dados ou cache estiver exposta (MySQL, PostgreSQL, Redis, MongoDB, Memcached, Elasticsearch) — é uma falha direta (−10 na Pontuação de segurança). Fonte: ss -tuln.

Se o cartão estiver vermelho — feche a base de dados ao exterior: associe-a a 127.0.0.1 (bind-address na configuração do MySQL/PostgreSQL, bind 127.0.0.1 no Redis) ou feche a porta na UFW.
«Porta aberta» ≠ «acessível do exterior». Um serviço que escuta em 127.0.0.1 (loopback) só é visível para o próprio servidor — não é alcançável de fora, mesmo que a porta esteja «aberta». Por isso o Postfix na porta 25, associado ao loopback, é seguro: a configuração automática define inet_interfaces = loopback-only (mais um smtpd_banner neutro — resolve o aviso Lynis MAIL-8818 sobre a exposição da versão). O cartão «Exposição externa» conta como externo apenas o que escuta em 0.0.0.0/[::]; os serviços em loopback não entram nessa conta.
Lynis MAIL-8818 manualmente (se instalou o correio por conta própria): em /etc/postfix/main.cf defina smtpd_banner = $myhostname ESMTP (sem versão nem SO) e inet_interfaces = loopback-only, depois sudo systemctl restart postfix.

Atualizações de segurança — quantos patches de segurança aguardam instalação e se é necessário reiniciar após a atualização do kernel (−5 na Pontuação de segurança se houver patches). Fonte: /usr/lib/update-notifier/apt-check, ficheiro /var/run/reboot-required. Lista detalhada — na página «Atualizações de segurança».

# Instalar atualizações: sudo apt update && sudo apt upgrade # Verificar o que escuta para o exterior: ss -tuln | grep -E '0\.0\.0\.0|\[::\]'
O cartão de atualizações funciona no Ubuntu/Debian (update-notifier-common). Se o apt-check não existir — o monitor calcula os patches através de apt-get -s upgrade.

Atualizações de segurança automáticas (unattended-upgrades) — na página «Atualizações de segurança» um cartão à parte mostra se a instalação automática de patches de segurança está ativa e quando foi executada pela última vez. Não é preciso sudo — o estado é lido através de apt-config dump.

sudo apt install unattended-upgrades sudo dpkg-reconfigure -plow unattended-upgrades # ativar # Verificar o que está ativo: apt-config dump | grep Unattended-Upgrade

Manutenção

36. Cópias de segurança

A cópia de segurança é o seguro principal: a perda de dados é pior do que qualquer intrusão. São precisas duas coisas — cópia do servidor/sites e, à parte, cópia da BD do painel (contém utilizadores, chaves WebAuthn, definições, licença).

Opção A — HestiaCP: separador Backup do utilizador → botão de criação de cópia (ou por agendamento nas definições do servidor). A cópia inclui os sites e as respetivas BD.

Opção B — manual (cron): dump da BD + arquivo do diretório data/ do painel:

# cron do root (sudo crontab -e) — cópia diária às 2:30 (indique os seus nomes/caminhos): 30 2 * * * mysqldump -u root MY_DB | gzip > /var/backups/monitor-db-$(date +\%F).sql.gz 40 2 * * * tar czf /var/backups/monitor-data-$(date +\%F).tar.gz -C /path/to/monitor data # Eliminar arquivos com mais de 14 dias: 0 3 * * * find /var/backups -name 'monitor-*' -mtime +14 -delete
Uma cópia no mesmo servidor protege de erros, mas não da perda do servidor. Copie os arquivos para um armazenamento externo (outro servidor, S3, rclone para a nuvem). Verifique se o restauro funciona mesmo.

37. Atualização e migração do painel

Atualização para uma nova versão. Comece por fazer uma cópia de segurança. Depois, volte a carregar os ficheiros de código, mantendo os seus dados:

  • substituir (código): public/, includes/, assets/, cron/, database/, bem como os .htaccess de raiz (o controlador frontal — o encaminhamento não pode ficar da versão antiga), manifest.json, sw.js;
  • não mexer: config.php (dados da BD), data/ (relatórios), logs/, tmp/ (sessões e cache).
# Após o carregamento — limpar a cache do PHP (se o opcache estiver ativo): sudo systemctl reload php*-fpm
O FileZilla indica SSH_FX_PERMISSION_DENIED — Permission denied. Os ficheiros do painel pertencem ao www-data (foi assim que ficaram definidos na instalação), enquanto o cliente SFTP se liga com o seu próprio utilizador, que não tem permissão de escrita. Entregar todo o painel ao www-data «para funcionar» é precisamente o que causa este erro; abaixo há três formas, qualquer uma resolve o problema.
# Variante A (recomendada) — separar os proprietários: o código é seu, as pastas de trabalho do servidor web. # O servidor web não recebe de todo permissão de escrita no CÓDIGO do painel: sudo chown -R deploy:www-data /path/to/monitor sudo chown -R www-data:www-data /path/to/monitor/data /path/to/monitor/tmp /path/to/monitor/logs sudo find /path/to/monitor -type d -exec chmod 755 {} \; sudo find /path/to/monitor -type f -exec chmod 644 {} \; sudo chmod 750 /path/to/monitor/data /path/to/monitor/tmp /path/to/monitor/logs sudo chmod 640 /path/to/monitor/config.php # Variante B — ACL por cima dos proprietários atuais (não movemos nada): sudo apt install -y acl sudo setfacl -R -m u:deploy:rwX /path/to/monitor sudo setfacl -R -d -m u:deploy:rwX /path/to/monitor # Variante C — através do grupo www-data. Mais simples, mas a escrita nos ficheiros # do painel passa também para o servidor web (com falha no PHP o código é substituível): sudo usermod -aG www-data deploy sudo find /path/to/monitor -type d -exec chmod 2775 {} \; sudo find /path/to/monitor -type f -exec chmod 664 {} \; sudo chmod 640 /path/to/monitor/config.php sudo chmod 2750 /path/to/monitor/data /path/to/monitor/tmp /path/to/monitor/logs
Porque a variante A é segura. O painel escreve apenas em três diretórios — data/ (relatórios), tmp/ (sessões e cache), logs/; que continuam a pertencer ao www-data. O restante é código, e o servidor web só precisa dele para leitura, garantida pelo grupo www-data com permissões 644. Vantagem adicional: em caso de falha no PHP, os ficheiros do painel já não podem ser reescritos. Em painéis de alojamento (HestiaCP e semelhantes) a variante A não é necessária: aí os ficheiros do site já pertencem à conta com que se liga por SFTP, e o servidor web lê-os pelo grupo.
Armadilha da variante B: qualquer chmod posterior sobre os ficheiros repõe a máscara da ACL e o acesso desaparece silenciosamente. Se, depois de «arrumar as permissões», o carregamento voltar a esbarrar em Permission denied, repita ambos os comandos setfacl.
O bit 2 na variante C é o setgid: os ficheiros enviados por SFTP permanecem no grupo www-data, caso contrário o painel não os consegue substituir. Depois da variante C volte a ligar-se no FileZilla — o novo grupo só passa a valer numa nova sessão. Verificação: id deploy (deve aparecer o grupo www-data) e ls -ld /path/to/monitor (drwxrwsr-x — a letra s significa que o setgid está ativo).

Migração para outro servidor:

  1. No novo servidor, coloque o site em funcionamento + HTTPS (ver a página de instalação manual).
  2. Copie todos os ficheiros do painel juntamente com config.php, data/.
  3. Migre a BD: mysqldump no antigo → importar no novo; corrija os dados da BD em config.php.
  4. Repita no novo servidor: sudoers, pertença ao grupo adm, tarefas cron.
  5. A licença está associada ao domínio — se o domínio for o mesmo, a chave continua a funcionar.

38. Um segundo site neste servidor (mais um domínio)

O painel já está a funcionar e no mesmo servidor é preciso mais um site — com domínio próprio ao lado do monitor. Depois da configuração automática o servidor é um Apache + PHP-FPM vulgar, por isso o site precisa de três coisas: a sua pasta, o seu vhost e o seu certificado. O painel não se mexe: vigia o servidor inteiro, não é preciso uma segunda instância para o segundo domínio.

Esta secção destina-se a um servidor sem painel de alojamento. Se tiver o HestiaCP ou um painel semelhante, adicione o domínio nele e não à mão. E se prevê alojar muitos sites (ainda por cima com correio), instale o painel antes da configuração automática: o script deteta-o e passa ao modo aditivo, ao passo que um painel instalado depois reconstrói a configuração do Apache, da firewall e do correio.
O que substituir nos comandos. site2.example.com é o endereço do novo site. Tanto pode ser um domínio próprio (mycompany.com) como um subdomínio (shop.mycompany.com): para o Apache não há diferença, mudam apenas o registo DNS (passo 1) e a emissão do certificado (passo 5). site2 é apenas um nome curto do site: assim se chamam a pasta /var/www/site2 e o ficheiro de configuração site2.conf. Não tem qualquer relação com o domínio, o Apache retira o endereço somente de ServerName; o nome pode ser qualquer um — para shop.mycompany.com, por exemplo, é cómodo usar shop: pasta /var/www/shop, configuração shop.conf.

1. DNS. No painel de DNS do seu registador crie um registo A para o IP deste servidor:

# Subdomínio (shop.mycompany.com): Tipo: A Nome: shop Valor: 203.0.113.10 TTL: 3600 # Domínio próprio (mycompany.com) — dois registos, para que funcione também o endereço com www: Tipo: A Nome: @ Valor: 203.0.113.10 TTL: 3600 Tipo: A Nome: www Valor: 203.0.113.10 TTL: 3600

Ao fim de alguns minutos verifique: dig +short site2.example.com deve devolver o IP do servidor.

2. Pasta do site.

sudo mkdir -p /var/www/site2 sudo chown -R www-data:www-data /var/www/site2

3. O vhost. O bloco cola-se no terminal por inteiro — o caminho para o socket do PHP-FPM é detetado sozinho:

PHPSOCK=$(ls -1 /run/php/php*-fpm.sock 2>/dev/null | head -1) # deteção automática do socket do PHP-FPM sudo tee /etc/apache2/sites-available/site2.conf > /dev/null <<'EOF' <VirtualHost *:80> ServerName site2.example.com # Domínio próprio: retire o # da linha abaixo para que o site abra também com www. # Para um subdomínio a linha não é necessária. #ServerAlias www.site2.example.com DocumentRoot /var/www/site2 <Directory /var/www/site2> Options -Indexes +FollowSymLinks AllowOverride All Require all granted </Directory> <FilesMatch \.php$> SetHandler "proxy:unix:__PHPSOCK__|fcgi://localhost" </FilesMatch> ErrorLog ${APACHE_LOG_DIR}/site2_error.log CustomLog ${APACHE_LOG_DIR}/site2_access.log combined </VirtualHost> EOF sudo sed -i "s#__PHPSOCK__#${PHPSOCK}#" /etc/apache2/sites-available/site2.conf

4. Ative o site e verifique a configuração antes de recarregar:

sudo a2ensite site2.conf sudo apache2ctl configtest # esperado: Syntax OK sudo systemctl reload apache2

5. SSL. O certbot já foi instalado pela configuração automática; a esta altura o DNS já deve apontar para o servidor:

# Subdomínio — um único endereço: sudo certbot --apache -d site2.example.com # Domínio próprio — logo com www (ambos os registos A do passo 1 têm de apontar para o servidor # e www tem de estar no ServerAlias, caso contrário o certbot não consegue verificar o segundo endereço): sudo certbot --apache -d site2.example.com -d www.site2.example.com

Um terceiro domínio e os seguintes acrescentam-se da mesma forma — repita os passos 1–5 mudando o domínio e a pasta.

Quem responde aos pedidos «alheios». Se o Host não corresponder a nenhum ServerName (acesso pelo IP ou um domínio alheio apontado ao seu servidor), o Apache serve o primeiro site ativado por ordem alfabética de /etc/apache2/sites-enabled/. A configuração automática desativa o 000-default padrão, pelo que esse papel «de serviço» fica com o vhost do painel (arciveo-monitor.conf) — isto não afeta os sites abertos pelo nome próprio. Se quiser que pelo IP abra outro site, dê ao ficheiro um nome que venha antes por ordem alfabética (por exemplo 000-site2.conf).
A proteção é de todo o servidor: UFW, Fail2ban, CrowdSec, ModSecurity e Suricata já estão a funcionar e cobrem o novo site automaticamente, não é preciso configurar nada à parte. As portas 80/443 também já estão abertas. O site tem os seus próprios registos: site2_error.log e site2_access.log em /var/log/apache2/.
Não coloque a pasta do novo site dentro da pasta do painel — a raiz web do painel é governada pelo seu próprio .htaccess (controlador frontal) e um site aninhado devolveria algo diferente do esperado. Mantenha os sites lado a lado: /var/www/monitor, /var/www/site2.

39. Recuperação de acesso (chave, palavra-passe ou IP bloqueado)

Se não conseguir iniciar sessão, tudo se resolve diretamente na BD a partir do servidor. Abra a BD (o nome está em config.php):

sudo mysql MY_DB

Chave WebAuthn perdida (o segundo fator não passa) — desative o 2FA, inicie sessão com a palavra-passe e registe uma nova chave:

UPDATE users SET webauthn_enabled = 0;

Palavra-passe esquecida — defina um novo hash (gere-o no servidor e insira-o):

# Gerar o hash da nova palavra-passe: php -r "echo password_hash('NEW_PASSWORD', PASSWORD_BCRYPT), \"\n\";" # Na BD (insira o hash obtido): # UPDATE users SET password = '$2y$10$...' WHERE username = 'admin';

Bloqueou-se a si próprio com o filtro de IP — desative a restrição:

UPDATE settings SET value = '0' WHERE name = 'ip_restriction_enabled';
O acesso à BD está sempre disponível: sudo mysql no servidor, ou o phpMyAdmin / secção da BD no painel do alojamento. Após a recuperação, volte a ativar o WebAuthn e o filtro de IP.

40. Todas as tarefas cron num só lugar

O resumo das tarefas fica no cron root do servidor (adicionadas via sudo crontab -e). Deixe apenas as linhas das ferramentas que usa; ajuste os caminhos ao seu servidor.

# Cron do servidor do monitor (root) — inscreva via: sudo crontab -e # 01:30 — análise ClamAV por caminhos perigosos (web, home, temp) → cartões «Ficheiros verificados» e «Última análise» 30 1 * * * /usr/local/bin/clamav-scan.sh >> /path/to/monitor/logs/cron.log 2>&1 # 02:00 — verificação de integridade de ficheiros AIDE (é preciso o --config explícito) 0 2 * * * /usr/bin/aide --config /etc/aide/aide.conf --check > /var/log/aide/aide.log 2>&1 # no arranque — restaurar permissões de /var/lib/aide (o ficheiro tmpfiles do pacote # aide-common.conf repõe-nas em 0700, e o painel deixa de ver a base) @reboot chmod 755 /var/lib/aide # 03:00 — auditoria de segurança Lynis 0 3 * * * /usr/local/bin/lynis-scan.sh >> /path/to/monitor/logs/cron.log 2>&1 # 04:00 — atualização da lista de bloqueio ipsum (level 1) 0 4 * * * /usr/local/bin/load-ipsum.sh >> /path/to/monitor/logs/cron.log 2>&1 # o conjunto ipsum no arranque é carregado pelo serviço ipsum-load.service (ANTES da firewall, senão # o UFW não vê o conjunto em before.rules) — não é cron. Aqui só o refresh diário acima. # 06:00 — relatório Logwatch 0 6 * * * /usr/local/bin/logwatch_daily.sh >> /path/to/monitor/logs/cron.log 2>&1 # a cada 30 min — verificação de discos SMART */30 * * * * /usr/local/bin/smart-scan.sh >> /path/to/monitor/logs/cron.log 2>&1 # 04:30 — integridade de pacotes debsums 30 4 * * * /usr/local/bin/debsums-scan.sh >> /path/to/monitor/logs/cron.log 2>&1 # 08:00 — relatório agendado para Email e Telegram 0 8 * * * /usr/local/bin/daily-report-all.sh >> /path/to/monitor/logs/cron.log 2>&1 # a cada hora — atualização das listas de pacotes (para o cartão «Atualizações de segurança») 0 * * * * /usr/bin/apt-get update -qq >/dev/null 2>&1 # a cada 5 min — instantâneo de recursos (CPU/RAM/rede/disco) para a página «Desempenho» */5 * * * * /usr/local/bin/collect-metrics-all.sh >> /path/to/monitor/logs/cron.log 2>&1 # a cada 5 min, desfasado do instantâneo acima — sensor de eventos de segurança: # novos banimentos, alertas de IDS, proteção parada, porta aberta. # Enquanto a vigilância estiver desativada nas definições, a tarefa não faz nada. 2-57/5 * * * * /usr/local/bin/security-watch-all.sh >> /path/to/monitor/logs/cron.log 2>&1 # a cada 15 min — resumo do servidor para a área pessoal, para ver vários servidores # numa única lista. Enquanto o resumo estiver desativado, a tarefa não faz nada. 3,18,33,48 * * * * /usr/local/bin/fleet-push-all.sh >> /path/to/monitor/logs/cron.log 2>&1
Os detalhes de cada uma estão nas secções correspondentes. As tarefas de backup (secção anterior) são adicionadas a este mesmo cron. Após as alterações, verifique: sudo crontab -l e que o serviço cron está ativo.
A hora do cron = fuso horário do servidor, e não a TIMEZONE do config.php. A constante TIMEZONE afeta apenas o PHP (como o painel mostra as datas), mas o daemon cron executa as tarefas pela hora do sistema operativo. Se o fuso do servidor não coincidir com o seu, o relatório «08:00» chegará à hora errada. Exemplo: o servidor está noutro fuso (UTC, UTC+0) e o utilizador está em Lisboa (UTC+1) → o relatório «08:00» chega às 09:00 pelo seu fuso. Verifique e, se necessário, alinhe o fuso do sistema com o seu:
# Verificar o fuso atual do servidor: timedatectl # Definir o seu fuso (exemplo) e reiniciar o cron: sudo timedatectl set-timezone Europe/Lisbon sudo systemctl restart cron
Depois disto, a linha 0 8 * * * será executada às 08:00 pela hora local. Caso contrário, teria de deslocar o próprio cron, mas na mudança para a hora de inverno/verão o desfasamento voltaria a divergir — por isso é mais correto configurar o fuso do sistema.
Scripts wrapper prontos. As suas cópias de trabalho e um modelo de crontab (crontab.txt) estão na pasta system/ junto ao projeto, fora de public_html. Isto não faz parte do site — não é preciso carregá-los para a raiz web; coloque-os no servidor nos caminhos de sistema (como no crontab acima):
  • lynis-scan.sh → /usr/local/bin/ (chmod +x) — executa lynis audit system, durante a análise coloca a flag /tmp/lynis-running e copia lynis-report.dat para data/lynis/ do painel;
  • logwatch_daily.sh → /usr/local/bin/ (chmod +x) — gera o relatório diário Logwatch (sshd, fail2ban, sudo, postfix) em data/logwatch/;
  • smart-scan.sh → /usr/local/bin/ (chmod +x) — recolhe o estado dos discos (smartctl) em data/disk/;
  • debsums-scan.sh → /usr/local/bin/ (chmod +x) — verifica a integridade dos pacotes (debsums) em data/debsums/;
  • clamav-scan.sh → /usr/local/bin/ (chmod +x) — análise antivírus ClamAV por caminhos perigosos (web, home, temp); escreve o resumo em /var/log/clamav/scan.log, de onde a página ClamAV o lê (linha 01:30 no crontab acima);
  • load-ipsum.sh → /usr/local/bin/ (chmod +x) — atualiza o conjunto ipset ipsum (level 1) no lugar, sem quebrar as regras ativas da firewall (linha 04:00 no crontab acima);
  • daily-report-all.sh → /usr/local/bin/ (chmod +x) — executa o relatório cron/daily_report.php do painel (linha 08:00 no crontab acima);
  • daily_report.php — já faz parte do painel (cron/daily_report.php), é executado via daily-report-all.sh, não é preciso instalá-lo à parte;
  • collect-metrics-all.sh → /usr/local/bin/ (chmod +x) — executa cron/collect_metrics.php do painel (página «Desempenho», linha */5 no crontab acima); o collect_metrics.php já faz parte do painel, não é preciso instalá-lo à parte;
  • security-watch-all.sh → /usr/local/bin/ (chmod +x) — executa o cron/security_watch.php do painel em nome do proprietário do site (página «Eventos de segurança», linha 2-57/5 no crontab acima); o security_watch.php já faz parte do painel e não precisa de instalação separada;
  • fleet-push-all.sh → /usr/local/bin/ (chmod +x) — envia o resumo do servidor para a área pessoal através do cron/fleet_push.php do painel (linha 3,18,33,48 acima);
  • crontab.txt (system/cron/) — modelo de tarefas; inscreva as linhas necessárias via sudo crontab -e.
O caminho do script no crontab deve coincidir com o local onde o colocou.
Como colocar o script em /usr/local/bin/. Não é possível gravar diretamente para lá a partir do FileZilla — o diretório pertence ao root, e o cliente SFTP receberá SSH_FX_PERMISSION_DENIED. O procedimento é este: primeiro carregar o ficheiro para /tmp (onde todos podem escrever) e depois movê-lo para o local com um só comando:
# no FileZilla: no campo «Site remoto» introduzir /tmp e carregar aí o script, # depois por SSH (o install define logo o dono e as permissões, chown/chmod não são precisos): sudo install -o root -g root -m 755 /tmp/lynis-scan.sh /usr/local/bin/lynis-scan.sh rm -f /tmp/lynis-scan.sh # verificação: ficheiro no lugar, permissões rwxr-xr-x, sintaxe intacta bash -n /usr/local/bin/lynis-scan.sh && ls -l /usr/local/bin/lynis-scan.sh
Não confunda os diretórios: precisa de /tmp na raiz do servidor — não /var/tmp nem tmp/ dentro do próprio painel (este último pertence ao www-data e está fechado ao seu utilizador). Na árvore do FileZilla, /tmp é um ramo de topo, ao lado de var, e não dentro dele.
Instalou o servidor com a configuração automática? Estes wrappers e as suas tarefas cron já estão instalados pelo script (em /usr/local/bin/, o log é /var/log/arciveo-cron.log) — não é preciso fazer nada manualmente.
Onde os scripts procuram o painel. Os wrappers são neutros quanto ao domínio: encontram as instalações do painel percorrendo /home/*/web/*/public_html e /var/www/*, e colocam os relatórios nos respetivos data/. Se o painel estiver noutro caminho — adicione-o à linha for app in … dentro dos scripts, senão os relatórios do Lynis/SMART/debsums/Logwatch não chegam ao painel.
cron.log e permissões de acesso. O ficheiro logs/cron.log é criado primeiro pelo cron root — pertencerá ao root, e o separador «Registo do cron» no painel não conseguirá lê-lo nem limpá-lo. Crie o ficheiro previamente como utilizador web (dono do diretório do site; no HestiaCP é a conta, p. ex. admin) — assim o cron root apenas acrescenta, sem mudar o dono:
# criar previamente como utilizador web (antes de adicionar as linhas cron): sudo -u OWNER touch /path/to/monitor/logs/cron.log # se o cron.log já foi criado pelo cron root — atribuir ao utilizador web: sudo chown OWNER:OWNER /path/to/monitor/logs/cron.log sudo chmod 644 /path/to/monitor/logs/cron.log
Descobrir o dono do diretório: stat -c %U /path/to/monitor.
Gestão a partir do painel. Na secção «Sistema» existe a página «Crontab» — permite ver e adicionar tarefas sem SSH. O painel edita apenas as tarefas adicionadas por ele próprio (um bloco separado no crontab root, marcado com comentários de serviço); tudo o que já está no crontab (a lista acima) é apresentado em modo de leitura na lista «Outras tarefas do servidor» com o botão «Copiar para o editor» — este apenas transfere o agendamento/comando para o formulário de adição, sem tocar na linha original. Para «passar» uma tarefa existente para a gestão do painel — copie-a para o editor, guarde e depois elimine a linha antiga manualmente (sudo crontab -e), senão será executada duas vezes.
Configuração única no servidor. A página precisa de um script wrapper com privilégios — não do sudo crontab puro (seria uma escalada direta para root por qualquer pessoa que obtivesse acesso à sessão do painel), mas de um script restrito com dois comandos (list/set), que só toca no seu bloco entre os comentários de serviço. Instale uma vez:
sudo install -m 0755 -o root -g root /dev/stdin /usr/local/sbin/arciveo-cron <<'ARCIVEO_CRON_EOF' #!/bin/bash set -euo pipefail BEGIN='# >>> ARCIVEO-CRON-MANAGED (edited from the panel; do not edit by hand) >>>' END='# <<< ARCIVEO-CRON-MANAGED <<<' cmd=${1:-} case "$cmd" in list) [ "$#" -eq 1 ] || { echo "usage: ${0##*/} list" >&2; exit 2; } crontab -l -u root 2>/dev/null || true ;; set) [ "$#" -eq 1 ] || { echo "usage: ${0##*/} set (body on stdin)" >&2; exit 2; } body=$(cat) if grep -qF "$BEGIN" <<<"$body" || grep -qF "$END" <<<"$body"; then echo "invalid body: markers not allowed" >&2; exit 2 fi current=$(crontab -l -u root 2>/dev/null || true) tmp=$(mktemp); trap 'rm -f "$tmp"' EXIT { if grep -qF "$BEGIN" <<<"$current"; then awk -v b="$BEGIN" '{print} $0==b{exit}' <<<"$current" else [ -n "$current" ] && printf '%s\n' "$current" echo "$BEGIN" fi printf '%s\n' "$body" echo "$END" if grep -qF "$END" <<<"$current"; then awk -v e="$END" 'f{print} $0==e{f=1}' <<<"$current" fi } > "$tmp" crontab -u root "$tmp" ;; *) echo "usage: ${0##*/} <list|set>" >&2; exit 2 ;; esac ARCIVEO_CRON_EOF echo "www-data ALL=(ALL) NOPASSWD: /usr/local/sbin/arciveo-cron" | sudo tee -a /etc/sudoers.d/monitor sudo visudo -c
O utilizador web pode ser diferente de www-data — verifique com que utilizador corre o pool PHP-FPM do site (ps -o user= -C php-fpm) e substitua-o na linha do sudoers.
O novo ficheiro foi enviado com o dono errado — a página responde «Access denied.». Se o ficheiro public/crontab_monitor.php foi carregado por FTP/SFTP com um utilizador de sistema diferente (por exemplo, root) do dos restantes ficheiros do site, o servidor web não conseguirá lê-lo. Compare o dono e as permissões com um ficheiro vizinho e alinhe-os:
ls -la public/crontab_monitor.php public/ssl_monitor.php sudo chown OWNER:OWNER public/crontab_monitor.php sudo chmod 644 public/crontab_monitor.php

Diagnóstico

41. A ferramenta está instalada, mas aparece «Não instalada»

O monitor deteta a presença de ferramentas através do dpkg-query — a base de pacotes APT. Se a ferramenta não tiver sido instalada através do apt (manualmente, via snap ou a partir do código-fonte), o dpkg não a vê.

# Verificar através do dpkg: dpkg -l fail2ban | grep '^ii' dpkg -l auditd | grep '^ii' # Encontrar o caminho do binário: which ufw fail2ban-client auditctl # Teste de sudo a partir de www-data: sudo -u www-data sudo fail2ban-client status sudo -u www-data sudo ufw status verbose

42. Resolução de problemas (500, sem dados)

Erro 500 — verifique os registos do PHP, do nginx e do próprio monitor:

tail -50 /var/log/nginx/error.log tail -50 /var/log/php*-fpm.log # Registos do monitor: tail -50 logs/monitor_$(date +%Y-%m-%d).log # Permissões das pastas: ls -la data/ tmp/ logs/
Painel numa consola de alojamento (HestiaCP, ISPmanager, cPanel)? Aí o PHP não corre sob www-data, mas sob a conta do utilizador (por exemplo admin — proprietário do diretório do site). Todas as regras sudo e a pertença a grupos (adm, systemd-journal) têm de ser definidas para esse utilizador, caso contrário os módulos mostrarão «Inativo / 0» com os serviços a funcionar. Para saber o utilizador real do PHP: ps -o user= -C php-fpm | sort -u ou o proprietário do diretório do site stat -c '%U' /path/to/monitor. A seguir, em todos os comandos abaixo, use-o em vez de www-data. A instalação automática deteta o utilizador web por si própria e configura o sudoers para ele.

Os dados não são apresentados — quase sempre por permissões sudo não definidas. Verifique o comando concreto em nome do utilizador web (substitua www-data pelo seu). A flag -n = sem palavra-passe, como no PHP — se pedir palavra-passe, é porque não há regra no sudoers:

sudo -u www-data sudo -n fail2ban-client status sudo -u www-data sudo -n ufw status verbose sudo -u www-data sudo -n ipset list -t ipsum sudo -u www-data sudo -n /usr/sbin/ausearch -m USER_LOGIN -ts today sudo -u www-data sudo -n /usr/sbin/aa-status sudo -u www-data sudo -n /usr/sbin/psad --Status sudo -u www-data journalctl -u falco --no-pager -n 5
O módulo indica «Inativo» / «0», apesar de a ferramenta funcionar (por exemplo sudo aa-status no terminal mostra os perfis, mas a página «AppArmor» diz «Inativo»). Motivo: o utilizador web não tem permissão sudo para o comando desse módulo. Verifique-o na lista acima: se pedir palavra-passe — acrescente a linha em falta em /etc/sudoers.d/monitor («Configuração do sudo»). Comandos «novos» frequentes: /usr/sbin/aa-status (MAC), /usr/sbin/psad --Status (PSAD).
Se uma página específica (Falco, ModSecurity, Auditd, portas abertas do UFW) estiver vazia — confira a lista na secção sobre sudo: provavelmente não estão autorizados apache2ctl, ausearch, aa-status ou ss, ou o utilizador web não está nos grupos adm/systemd-journal (é de lá que se leem os registos do fail2ban/auth/modsec e o journalctl — Falco e eventos do núcleo).

43. A página está vazia, apesar de existirem dados no servidor

Sintoma: no servidor há dados (visíveis via shell), mas a página mostra «sem dados» ou um estado incorreto — por exemplo, o AIDE indica «Não inicializada», embora a base tenha sido criada.

A causa é o open_basedir: muitos painéis e alojamentos restringem o pool do PHP-FPM ao diretório do domínio, por isso as funções PHP file_exists(), file_get_contents(), filemtime() ficam bloqueadas para caminhos de sistema (/var/lib/aide, /var/log, /proc…). O Monitor contorna isto lendo esses caminhos com comandos de sistema padrão (cat, test, stat).

# O ficheiro é visível via shell (é assim que o monitor lê): sudo -u www-data bash -lc 'test -e /var/lib/aide/aide.db && echo VISIBLE || echo NO' # Valor atual de open_basedir para o pool do domínio: grep -ri open_basedir /etc/php/*/fpm/pool.d/ 2>/dev/null
Se o shell «vê» o ficheiro (VISIBLE) mas a página não — é o open_basedir. A solução correta é a leitura por comandos de sistema (já feito para o AIDE e o Monitor de rede). Alargar o open_basedir a /var, /proc não é necessário e é menos seguro.

44. A página SSL não funciona

O monitor verifica os certificados ligando-se diretamente aos domínios pela porta 443. Se o domínio estiver inacessível a partir do próprio servidor ou a porta estiver fechada pela firewall, a verificação falha.

# Verificar o certificado manualmente: echo | openssl s_client -connect monitor.example.com:443 2>/dev/null \ | openssl x509 -noout -dates # Verificar a disponibilidade: curl -I https://monitor.example.com
O monitor obtém os domínios automaticamente das configurações do Nginx (/etc/nginx/sites-enabled/, /etc/nginx/conf.d/) e do Apache (/etc/apache2/sites-enabled/), mais o host atual de HTTP_HOST.
Deteção automática de subdomínios. Os subdomínios são detetados automaticamente a partir dos registos públicos de Certificate Transparency e verificados pela rede — mesmo que estejam alojados noutros servidores. Não é preciso adicionar nada manualmente.

45. Apenas uma BD é visível de várias

O monitor liga-se ao MySQL com o utilizador de config.php, que só tem acesso à sua própria base de dados. O MySQL mostra em information_schema apenas as bases com privilégios — por isso as restantes não aparecem.

Para que o monitor veja todas as BD, conceda a este utilizador permissão apenas de leitura (uma vez como root; substitua o nome do utilizador de config.php):

sudo mysql -u root GRANT SELECT, PROCESS, SHOW DATABASES ON *.* TO 'DB_USER'@'localhost'; FLUSH PRIVILEGES; EXIT;
SELECT ON *.* concede apenas permissão de leitura — não é possível alterar, eliminar ou criar nada, é seguro para monitorização.
Sem este GRANT, o painel só vê a sua própria base — não é um erro, mas uma limitação de permissões. O painel não usa qualquer sudo mysql: a lista de bases é obtida através da sua própria ligação PDO.

46. O PostgreSQL não aparece na página «Base de dados»

O PostgreSQL exige acesso ao nível do utilizador postgres, que o utilizador web do painel não tem. Expor um sudo psql amplo a partir do PHP é inseguro — em vez disso, o painel invoca uma camada restrita sem parâmetros, que apenas imprime a versão, o número de ligações e a lista de bases com os respetivos tamanhos. Crie-a:

sudo tee /usr/local/bin/monitor-pgstat >/dev/null <<'EOF' #!/bin/sh # Arciveo Security Monitor - read-only PostgreSQL version, connections and per-database size sudo -u postgres psql -tAc "SELECT version();" | grep -oE 'PostgreSQL [0-9.]+' echo "---" sudo -u postgres psql -tAc "SELECT count(*) FROM pg_stat_activity;" echo "---" sudo -u postgres psql -tAc "SELECT datname || '|' || pg_size_pretty(pg_database_size(datname)) FROM pg_database WHERE datistemplate = false;" EOF sudo chown root:root /usr/local/bin/monitor-pgstat sudo chmod 755 /usr/local/bin/monitor-pgstat # em /etc/sudoers.d/monitor (utilizador = aquele sob o qual corre o PHP-FPM): # www-data ALL=(ALL) NOPASSWD: /usr/local/bin/monitor-pgstat
Se não utilizar o PostgreSQL — remova a linha monitor-pgstat do sudoers (passo 13 da instalação manual) e não crie o script: o cartão do PostgreSQL fica apenas inativo.

47. Um alerta foi disparado — o que fazer

O painel mostra o que está a acontecer; abaixo, o que fazer em situações típicas. Princípio geral: não entrar em pânico, comparar com a atividade legítima (as suas ações, atualizações, backups) e reagir consoante a gravidade.

  • Mapa de ataques / muitos bans do fail2ban — é normal para qualquer servidor na internet (os bots tentam constantemente SSH/web). O importante é que os bans funcionem. Certifique-se de que o acesso por SSH é feito apenas por chave (palavra-passe desativada) e de que o seu IP está em ignoreip.
  • O ModSecurity bloqueou pedidos — o WAF repele ataques ao site, é o trabalho dele. Se estiver a bloquear o seu tráfego legítimo (falso positivo), encontre o rule id nos detalhes e adicione uma exceção na config do CRS.
  • AIDE: ficheiros alterados — compare a lista com aquilo que fez (atualização de pacotes, edição de configs é normal). Alterações a binários do sistema em que não mexeu são motivo de alerta. Após alterações legítimas, atualize a base do AIDE.
  • debsums: binários/bibliotecas alterados (fora de /etc, fora de /usr/share) — possível substituição. Verifique o pacote: debsums PACKAGE_NAME e, em caso de dúvida, reinstale-o (apt install --reinstall).
  • ClamAV / maldet: ameaça encontrada — verifique o ficheiro em quarentena, não o abra. Se for uma web shell no diretório do site, isole o servidor e procure o ponto de entrada (plugin vulnerável, fuga de credenciais).
  • Falco: eventos críticos (execução de shell num contentor, acesso a ficheiros sensíveis) — analise o evento: de quem é o processo, o que o lançou. Muitas vezes é atividade legítima de administração.
  • Exposição externa: BD/cache a vermelho — feche imediatamente: associe o serviço a 127.0.0.1 ou feche a porta na UFW. É uma falha real.
  • SSL a expirar / expirado — renove o certificado (o Let's Encrypt renova-se sozinho; se não, verifique o certbot renew ou as definições no painel).
  • Atualizações de segurança pendentes — instale: sudo apt update && sudo apt upgrade; após atualizar o kernel, reinicie o servidor.
Sinais de uma invasão real (processos/utilizadores desconhecidos, binários alterados, spam de saída, tarefas cron desconhecidas): desligue o servidor do acesso externo, faça um backup para análise e, se os dados forem críticos, prepare um servidor limpo a partir de um backup fiável — remover um rootkit de forma segura é difícil.