FAQ

Arcivéo Monitor의 설치, 설정, 유지 관리 안내서입니다. 섹션은 전체 개요, 패널 배포, 보안 도구 연결, 내장 모듈, 진단으로 그룹화되어 있습니다. 명령어는 오른쪽 버튼으로 복사할 수 있습니다.

시작하기

01. 패널 설치 — 방식 선택

패널 설치는 단계별 안내 페이지에 있습니다. 방식을 선택하세요:

확신이 서지 않으면 자동을 선택하세요. 이 안내서는 SSL, 도구, cron, 진단에 대한 단일 참조 자료로 남으며, install 페이지들은 내용을 중복하지 않고 여기 해당 섹션을 링크합니다.

개요

02. Arcivéo Monitor란?

Arcivéo Monitor는 서버 보안 대시보드입니다. 설치된 도구(Fail2ban, UFW, Lynis, ModSecurity, AIDE, ClamAV, Auditd, CrowdSec, Suricata, Falco 등)에서 데이터를 수집해 대시보드, 공격 지도, 도구별 상세 페이지를 갖춘 단일 인터페이스에 표시합니다.

Monitor는 능동적 방어 수단이 아니며 스스로 공격을 차단하지 않습니다. 이미 작동 중인 도구의 정보를 취합해 보기 쉽게 제공하는 것이 목적입니다.

03. 모니터가 서버에서 작동하는 방식

모니터는 로컬에서만 작동합니다. 즉, 모니터링 대상 서버와 동일한 서버에 설치해야 합니다. SSH나 원격 API는 사용하지 않습니다.

모든 명령(fail2ban-client, ufw status, ipset list 등)은 웹 서버 사용자(보통 www-data, 호스팅 패널에서는 사이트 계정) 권한으로 실행되며, sudo제한된 권한 집합, 즉 특정 유틸리티에만 허용되고 전체 root 접근은 없는 상태로 실행됩니다. 결과는 파싱되어 브라우저에 표시됩니다.

여러 서버를 사용할 경우 각 서버에 고유한 도메인으로 모니터를 개별 설치하세요.

04. 보안 점수 산정 방식

점수는 최댓값에서 시작하며 발견된 문제마다 차감됩니다:

  • UFW 비활성 — −30
  • Fail2ban 미실행(활성 jail 없음) — −25
  • WebAuthn 키 없음 — −15
  • Lynis hardening index < 60 — −20; 60–79 — −10
  • IPset ipsum 미로드 — −10
  • ClamAV 위협 탐지 — −20
  • AIDE 파일 변경 — −15
  • SSL 만료 — −30, 만료 <14일 — −15, <30일 — −5
  • CrowdSec 설치됨, 미실행 — −5
  • Suricata 설치됨, 미실행 — −5
  • DBMS/캐시(MySQL, PostgreSQL, Redis…) 외부 접근 가능 — −10
  • SSH root 로그인 허용(PermitRootLogin yes) — −20
  • 보안 업데이트 설치 대기 중 — −5

결과: 80+ = 보호됨, 60–79 = 주의, <60 = 위험.

ClamAV, AIDE, CrowdSec, Suricata 차감은 해당 도구가 설치된 경우에만 적용됩니다. 데이터베이스가 초기화되지 않은 Lynis와 AIDE는 "데이터 없음"으로 표시되며 점수를 낮추지 않습니다. 오늘의 공격 횟수는 대시보드에 표시되지만 보안 점수에는 영향을 주지 않습니다.

설정 및 라이선스

05. WebAuthn — 2단계 인증

WebAuthn은 하드웨어 키를 이용한 비밀번호 없는 인증 표준입니다. YubiKey, Touch ID, Face ID, Windows Hello, Passkey를 지원합니다.

비밀번호로 로그인한 후 시스템이 등록된 키로 확인을 요청합니다. 비밀번호가 유출되더라도 물리적 키나 생체 인증 없이는 로그인할 수 없습니다.

설정하려면 측면 메뉴에서 WebAuthn 키를 열고 “키 등록”을 누르세요. 키를 두 개 등록해 두세요. 유일한 키를 분실하거나 고장 나면 그 키로는 대시보드에 로그인할 수 없습니다.

WebAuthn은 HTTPS에서만 작동합니다. HTTP 연결에서는 키 등록과 로그인을 사용할 수 없습니다.

06. 알림: Telegram 및 Email

대시보드는 보안 보고서를 Telegram과 이메일로 보낼 수 있습니다(버튼 및 예약 전송). “설정” 섹션에서 구성합니다.

Telegram. 봇 토큰chat id가 필요합니다:

  1. Telegram에서 @BotFather에게 /newbot을 보내면 123456:ABC... 형식의 토큰을 받습니다.
  2. 새 봇에게 아무 메시지나 보내세요(봇이 답장할 수 있도록).
  3. 자신의 chat id를 확인하세요: @userinfobot에게 메시지를 보내거나 https://api.telegram.org/bot<TOKEN>/getUpdates를 열어 "chat":{"id":...}를 찾습니다.
  4. 토큰과 chat id를 “설정” → Telegram에 입력하고 “저장 후 테스트 전송”을 누릅니다.

Email. “설정” → Email에서 두 가지 방식 중 선택:

  • SMTP — 메일함의 호스트, 포트(465/SSL 또는 587/TLS), 로그인 및 비밀번호;
  • Resend — 최신 API: API 키(re_...)와 인증된 발신 도메인을 입력합니다.
“테스트 전송” 버튼은 채널을 즉시 확인합니다. 자동 보고서 예약은 cron으로 처리됩니다(“모든 cron 작업” 섹션): cron이 전송을 호출하고 채널은 설정에서 가져옵니다.

보고서 상태: “주의” 또는 “정상”. 제목은 실제 문제나 대기 중인 작업이 있을 때만 “주의”가 됩니다: ClamAV 위협 발견, AIDE 파일 변경, Falco 심각 이벤트(최근 24시간 내 Emergency/Alert/Critical), Monit의 다운된 서비스, 재부팅 필요, SSL 만료 임박(≤14일) 또는 대기 중인 보안 업데이트. 봇의 SSH 무차별 대입, fail2ban으로 차단된 IP, Suricata 경보, Lynis 경고, 이미 막아낸 ModSecurity 요청 같은 배경 잡음은 상태를 올리지 않으므로, 이런 수치가 보고서에 있다고 해서 그 자체로 “주의”를 의미하지는 않습니다.

07. 라이선스 — 입력 및 활성화

세부 모니터링 모듈(Lynis, UFW, ModSecurity, 공격 지도, AIDE, ClamAV 등)은 유효한 라이선스가 있어야 열립니다. 라이선스가 없으면 대시보드, 설정, 계정은 작동하지만 모듈에는 “라이선스 필요” 카드가 표시됩니다.

개인 계정에서 구매를 마치면 ARCIVEO-XXXX-XXXX-XXXX-XXXX 형식의 활성화 코드가 발급됩니다. 이 코드를 패널 도메인에 “활성화”해야 하며, 그러면 코드가 서명된 라이선스 파일([license] 블록)로 변환되어 패널에 붙여 넣을 수 있습니다.

활성화 방법(3단계):

  1. 활성화 코드를 준비하세요. 개인 계정 my.arciveo.com → “라이선스” / “라이선스 활성화” 섹션에서 ARCIVEO-… 코드를 복사하세요.
  2. 코드를 도메인에 활성화하세요. 같은 개인 계정에서 “라이선스 활성화”를 열고 활성화 코드, 이메일, 패널 도메인(모니터가 열리는 주소, 예: monitor.example.com)을 입력하세요. 활성화를 누르면 시스템이 해당 도메인에 연결된 라이선스 파일을 생성해 “복사” 버튼이 있는 입력란에 표시합니다.
  3. 키를 패널에 붙여 넣으세요. 라이선스 텍스트 전체를 복사한 뒤 → 패널에서 “설정” → “라이선스” 블록을 열고 붙여 넣은 다음 “저장”을 누르세요. 모듈이 즉시 잠금 해제됩니다.

패널은 키를 암호학적으로 검증합니다: 서명, 도메인 연결, 유효 기간을 확인합니다.

활성화 시 도메인은 패널 주소와 정확히 일치해야 합니다. config.phpAPP_URL 상수에서 값을 가져와 호스트 이름만 입력하세요 — https://www 접두사는 빼야 합니다. 활성화는 1회성입니다: 코드는 입력한 도메인의 라이선스로 변환되며 다시 활성화되지 않습니다 — 도메인을 잘못 입력하면 키가 패널에 맞지 않고 코드도 소모됩니다. 따라서 도메인을 신중하게 입력하세요.
유효 기간이 만료되거나 도메인이 바뀌면 패널 상단에 경고가 표시됩니다. 라이선스는 도메인에 영구히 연결되며 다른 도메인으로 이전되지 않습니다: 기간을 연장하거나 도메인을 바꾸려면 새 키가 필요합니다(개인 계정에서 구매하고 1회성으로 활성화합니다).

08. config.php 파일 — 패널의 모든 설정

패널의 모든 주요 매개변수는 루트(public/ 폴더 옆)에 있는 하나의 파일 config.php에 일반 상수 define()로 지정됩니다. 파일은 설치 시 생성되며, 수동으로 편집할 일은 드뭅니다 — 주로 도메인 변경, 이전, 다른 데이터베이스 연결 시에만 필요합니다. 편집 후에는 반드시 PHP-FPM을 재시작하세요(그렇지 않으면 OPcache 때문에 변경 사항이 적용되지 않습니다).

강조 표시된 곳에 자신의 값을 넣고, 나머지는 그대로 두세요:

// --- 데이터베이스 --- define('DB_HOST', 'localhost'); // 그대로 두기 define('DB_NAME', 'db_name'); // DB 생성 시 지정한 값 define('DB_USER', 'user'); // DB 생성 시 지정한 값 define('DB_PASS', 'db_password'); // DB 생성 시 지정한 값 define('DB_CHARSET', 'utf8mb4'); // 그대로 두기 // --- 애플리케이션 --- define('APP_URL', 'https://monitor.example.com'); // 패널 주소, 끝에 슬래시 없이 define('TIMEZONE', 'Asia/Seoul'); // 사용자의 시간대 // --- 세션 시간 --- define('SESSION_LIFETIME', 28800); // 재로그인까지 유휴 시간, 초 (28800 = 8시간)

데이터베이스. MySQL/MariaDB 연결 정보:

  • DB_HOST — DBMS 호스트, 거의 항상 localhost;
  • DB_NAME — 패널 데이터베이스 이름;
  • DB_USER — DB 사용자(자신의 데이터베이스에만 접근);
  • DB_PASS — 해당 사용자의 비밀번호;
  • DB_CHARSET — 연결 인코딩, utf8mb4로 그대로 두세요.

애플리케이션.

  • APP_URL — 패널의 전체 주소(예: https://monitor.example.com). 라이선스가 활성화된 도메인과 일치해야 하며, 그렇지 않으면 키가 거부됩니다(“라이선스” 섹션 참조);
  • TIMEZONEPHP 시간대: 패널이 날짜와 시간을 표시하는 방식에만 영향을 줍니다. cron 작업 실행 시간에는 영향을 주지 않으며, 거기서는 시스템 시간대가 적용됩니다(“모든 cron 작업” 참조).

세션 시간. SESSION_LIFETIME — 세션 유휴 타임아웃(초 단위, 슬라이딩 방식: 활동 시 갱신). 기본값은 28800 = 8시간이며, 이 시간 동안 활동이 없으면 패널이 재로그인을 요청합니다. 예를 들어 3600 = 1시간, 86400 = 하루입니다.

오류 로깅. 오류는 방문자에게 절대 표시되지 않고 logs/php_errors.log에 기록됩니다 — “애플리케이션 로그” 페이지에서 확인할 수 있습니다. 이 줄들(display_errors=0, log_errors=1, error_log 경로)은 보통 변경할 필요가 없습니다 — 설정은 파일에 직접 지정되어 있으며 php.ini에 의존하지 않습니다.

config.php는 비밀 파일입니다. 여기에 DB 비밀번호가 들어 있습니다. 파일은 패널 루트(public/ 옆)에 있으며, 이 패널의 웹 루트(DocumentRoot)는 바로 패널 루트이지 public/가 아닙니다. 파일 자체가 “유출”되지는 않습니다: 루트의 .htaccess에 이 파일에 대한 명시적 거부(Require all denied)가 설정되어 있어 서버가 403을 반환합니다. 이 규칙이 없더라도 소스는 유출되지 않습니다: PHP이므로 서버가 텍스트로 내보내지 않고 실행합니다. 만약을 위해: 공개 저장소에 올리지 말고 실제 비밀번호가 담긴 채로 지원팀에 전달하지 마세요. 파일 권한은 640입니다.
이전이나 접근 복구 시 이 파일이 주요 정보 출처입니다: DB 이름, 사용자, 비밀번호를 바로 여기서 가져옵니다(“패널 업데이트 및 이전”과 “접근 복구” 섹션 참조).

보안 도구

09. UFW 방화벽

UFW(Uncomplicated Firewall)는 nftables/iptables를 위한 간단한 인터페이스입니다. 명시적으로 허용한 포트를 제외한 모든 수신 포트를 차단합니다. “UFW 방화벽” 페이지에서 상태와 규칙을 확인할 수 있습니다.

sudo apt install ufw # SSH(활성화 전에 반드시!)와 웹 허용 sudo ufw allow OpenSSH sudo ufw allow 80,443/tcp # 외부에서 DB 차단(로컬 접근만 허용) sudo ufw deny 3306 # 활성화 및 확인 sudo ufw enable sudo ufw status verbose
ufw enable 전에 반드시 SSH를 허용하세요(ufw allow OpenSSH). 그렇지 않으면 서버 접근을 잃게 됩니다.
대시보드의 “외부 노출”은 UFW를 반영합니다. deny 규칙으로 차단된 포트는 외부에서 접근 가능한 것으로 간주되지 않습니다.
Skipping adding existing rule은 오류가 아닙니다. UFW가 동일한 규칙이 이미 존재하여 다시 추가하지 않는다는 것을 알리는 메시지입니다. 자동 설정을 다시 실행하면(멱등성이 있음) 나타나는 정상적인 메시지이므로 대응할 필요가 없습니다.

10. Fail2ban 설치

로그인 실패 횟수가 초과되면 IP를 자동으로 차단합니다. SSH, nginx, Apache 등 여러 서비스의 로그를 분석합니다.

sudo apt install fail2ban sudo systemctl enable --now fail2ban # 상태 확인: sudo fail2ban-client status
실제 운영 구성(수십 개의 jail과 ipsum 기반 자동 차단이 포함된 jail.local)은 다음 섹션에 있습니다.

11. Fail2ban + ipsum 실전 구성

기본 설치는 위에서 설명했습니다. 여기서는 수십 개의 활성 jail과 수천 건의 차단을 만들어내는 실전 구성을 다룹니다: 공통 설정, 핵심 jail, 그리고 ipsum 목록의 악성 IP 자동 차단입니다.

/etc/fail2ban/jail.local 파일 — 공통 설정과 핵심 jail:

[DEFAULT] bantime = 1w findtime = 900 maxretry = 3 backend = systemd banaction = nftables-multiport ignoreip = 127.0.0.1/8 ::1 <YOUR_IP> <TRUSTED_NETS> # 점진적 차단: 반복될 때마다 차단 시간 증가 bantime.increment = true bantime.factor = 2 bantime.maxtime = 5w bantime.rndtime = 300 [sshd] enabled = true maxretry = 5 bantime = -1 # SSH 무차별 대입에 영구 차단 findtime = 3600 # 재범자: 여러 번 차단당한 대상은 영구 차단 [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 # 웹 서비스 (apache-*, nginx-*, php-url-fopen, phpmyadmin-syslog): [nginx-http-auth] enabled = true port = http,https [apache-badbots] enabled = true port = http,https # … 나머지 서비스별 jail (dovecot, exim, postfix-sasl, # mysqld-auth, vsftpd, portscan, pam-generic) — enabled = true
ignoreip에 반드시 본인 IP와 신뢰하는 네트워크를 넣으세요. 그러지 않으면 자기 자신을 차단할 수 있습니다. 수정 후: sudo fail2ban-client reload.

ipsum 차단 목록 자동 로드는 root 크론(sudo crontab -e)에 등록합니다: level 1(IP 10만+ 개)이 ipsum 세트에 로드되고, 이 세트는 방화벽에서 차단됩니다(자세한 내용은 “IPset 차단 목록” 섹션 참고):

# 04:00 — ipset ipsum 갱신 (level 1, 최대 범위): 0 4 * * * /usr/local/bin/load-ipsum.sh >> /path/to/monitor/logs/cron.log 2>&1
세트 이름은 반드시 ipsum이어야 합니다 — 대시보드(“IPset ipsum” 카드)가 바로 이 이름을 읽습니다. 레벨: levels/1.txt는 최대 범위, levels/3.txt는 더 정확함(출처 3개 이상).

“보안 모니터”가 두 영역으로 나뉜 이유. 방어는 두 단계로 작동하며, 대시보드는 이를 섞지 않습니다:

  • 실제 공격 (대응형) — fail2ban이 잡아낸 모든 것: 실시간 침입 시도(jail sshd, apache-*, nginx-* 등)와 악성 재범자(jail recidive — 이미 여러 번 차단된 대상). 이들은 실제로 침입을 시도한 IP이며, 공격 지도와 “타임라인”에 표시됩니다.
  • 예방적 차단 (선제형) — 알려진 악성 IP의 공개 차단 목록 ipset ipsum으로, DROP 규칙에 의해 방화벽에서 차단됩니다. 이 주소들은 대부분 서버에 접근조차 하지 않았지만 사전에 차단됩니다. “IPset ipsum” 카운터는 예방적으로 차단된 건수를 보여줍니다.

차이는 간단합니다: 대응형은 “공격해서 차단당한 대상”, 선제형은 “시도하기도 전에 차단된 대상”입니다. 예전에는 recidive에 ipsum list-3를 인위적으로 밀어 넣었습니다(여기서 예전 구분인 “목록 기반 recidive”가 나왔습니다). 이제 recidive는 실제 재범자만 담고, 선제형 차단은 전부 방화벽에서 처리합니다.

12. IPset 차단 목록 (ipsum)

ipsum — 매일 갱신되는 공개 악성 IP 목록입니다. 모니터는 대시보드와 공격 지도에 로드된 주소 수를 표시하며, 보안 점수에도 반영합니다(세트가 로드되지 않으면 −10).

fail2ban 없이 쓰는 최소 구성 — iptables로 차단하는 별도 세트 ipsum:

# 세트 생성(최초 한 번): sudo ipset create ipsum hash:ip # 갱신 스크립트 /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, 매일 4:00): 0 4 * * * /usr/local/bin/update-ipsum.sh
fail2ban-recidive를 활용한 확장 구성은 “Fail2ban + ipsum 실전 구성” 섹션을 참고하세요.
ipset은 메모리에 상주하므로 재부팅 시 사라집니다. 매일 도는 크론만으로는 재부팅 시점부터 다음 실행 전까지 세트가 비어 있게 됩니다(대시보드에 0으로 표시). 부팅 시에도 로드하려면 로딩을 스크립트로 분리해 @reboot에 걸어 두세요. 이때 create … -exist 명령이 maxelem 300000 한도를 지정합니다(기본값 65536으로는 level 1이 들어가지 않아 “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 — 매일 04:00 그리고 매 부팅 시: 0 4 * * * /usr/local/bin/load-ipsum.sh @reboot sleep 60 && /usr/local/bin/load-ipsum.sh
자동 설치 시 동작 방식. 스크립트는 전체 목록 level 1(10만 개 이상의 IP)을 ipsum 세트에 로드하며, 방화벽을 설치 프로그램이 관리하는 경우(새 VPS — “전체”/“경량” 프로필) DROP 규칙으로 세트를 UFW에 연결합니다 — 해당 IP의 트래픽이 실제로 차단됩니다. 규칙은 ESTABLISHED,RELATED 뒤에 위치하므로 현재 연결(SSH 포함)은 끊기지 않고, 목록에 있는 새 연결만 차단됩니다. 세트는 ipsum-load.service 서비스가 방화벽보다 먼저 로드될 때 복원되며(그렇지 않으면 UFW가 올라오지 못합니다), 04:00 크론으로 갱신됩니다. 이미 구성된 서버(패널, 자체 방화벽)에서는 설치 프로그램이 방화벽을 건드리지 않으며, 이때 ipsum은 대시보드와 공격 지도용 목록으로 남고 DROP 규칙은 원하면 수동으로 추가합니다(iptables … --match-set ipsum … -j DROP 최소 구성은 위 참고). 자동 설치에서는 수동으로 할 일이 없습니다.

13. CrowdSec 설치

집단 위협 인텔리전스를 갖춘 최신 Fail2ban 대체 도구입니다. 커뮤니티의 차단 정보와 자체 규칙을 함께 사용합니다. 방화벽에 차단을 적용하려면 별도의 bouncer가 필요합니다.

curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash sudo apt install crowdsec sudo systemctl enable --now crowdsec # iptables/nftables용 bouncer: sudo apt install crowdsec-firewall-bouncer-iptables # 상태 확인: sudo systemctl status crowdsec sudo cscli decisions list sudo cscli bouncers list
패널에 표시되는 “실행 안 됨” 상태는 서비스가 설치되었지만 서비스가 활성 상태가 아님을 뜻합니다(모니터가 systemctl is-active crowdsec로 확인). 시작하려면 sudo systemctl enable --now crowdsec, 실패 시 sudo journalctl -u crowdsec -n 30을 확인하세요. “실행 안 됨” 상태의 다른 서비스(Suricata, Falco, Monit, MySQL)에도 동일하게 적용됩니다.
대시보드에 “시나리오 0개” 또는 “bouncer 0개”로 표시됨. CrowdSec는 기본 상태로는 거의 비어 있습니다. 컬렉션이 없으면 아무것도 탐지하지 못하고, 등록된 bouncer가 없으면 차단이 방화벽에 적용되지 않습니다. 기본 컬렉션을 설치하고 bouncer가 목록에 있는지 확인하세요:
# 기본 컬렉션(Linux + SSH + 웹 서버): sudo cscli collections install crowdsecurity/linux crowdsecurity/sshd crowdsecurity/base-http-scenarios sudo systemctl reload crowdsec # bouncer가 목록에 있고 연결 활성 상태여야 합니다: sudo cscli bouncers list
bouncer 로그에 stream halted / 차단이 적용되지 않음. 고아 상태가 된 api 키 문제입니다. bouncer가 cscli bouncers list에서 삭제되었지만 오래된 키가 /etc/crowdsec/bouncers/*.yaml에 남아 있습니다. bouncer를 다시 등록하고 새 키를 기입하세요:
sudo cscli bouncers add fw-bouncer # 새 api_key를 출력합니다 # 이 키를 /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml의 api_key:에 기입하세요 sudo systemctl restart crowdsec-firewall-bouncer
자동 설치(“완전 보호” 프로필)는 컬렉션을 설치하고 firewall-bouncer를 자동으로 등록합니다. 수동 설치를 하거나 CrowdSec에 직접 손을 댄 경우에만 수동으로 처리하면 됩니다.

14. AIDE 설치

AIDE (Advanced Intrusion Detection Environment)는 파일 시스템의 스냅샷을 만들어 검사할 때마다 /etc, /bin, /usr의 변경 사항을 알려줍니다. 설치 후에는 데이터베이스 초기화(aideinit)가 필수입니다.

sudo apt install aide # 데이터베이스 초기화(5~15분): sudo aideinit sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db # Ubuntu 24.04: /var/lib/aide 디렉터리가 700 모드(소유자 _aide)로 생성되어 # 패널(www-data)이 데이터베이스를 보지 못함 → “초기화되지 않음”으로 표시됨. # 디렉터리에 통과 권한 부여(데이터베이스 파일은 600 유지): sudo chmod 755 /var/lib/aide # 모니터가 읽는 로그에 기록하는 첫 검사. # Ubuntu/Debian에서 aide는 명시적 --config가 필요함(없으면 “missing configuration”; # 새 버전에는 aide.wrapper 바이너리가 포함되지 않음): sudo bash -c 'aide --config /etc/aide/aide.conf --check > /var/log/aide/aide.log 2>&1'
aideinit 실행 중에는 터미널이 5~15분 동안 Running aide --init... 줄에 머무릅니다 — 정상입니다(전체 파일 시스템 해싱, 디스크 부하). Ctrl+C로 중단하지 마세요. 프로세스가 “멈춘” 것처럼 보이면서 아무것도 출력하지 않으면, 숨겨진 Overwrite existing aide.db.new [Yn]? 요청에 대한 응답을 기다리는 것일 수 있습니다(Y를 누르세요). 다른 세션에서 활동을 확인: pgrep -af aide.
aideinit 오류: “21_aide_spamassassin … printf: invalid number” (return code 20) — Ubuntu 22.04의 AIDE 설정 스니펫에서 알려진 버그입니다. 데이터베이스가 생성되지 않습니다. 문제가 있는 스니펫을 옮기고 다시 시도하세요:
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
대시보드의 상태. “초기화되지 않음” = 패널이 데이터베이스 파일을 보지 못함: aideinit가 실행되지 않았거나, (Ubuntu 24.04) /var/lib/aide 디렉터리가 700 모드로 생성되어 www-data가 접근할 수 없음 — sudo chmod 755 /var/lib/aide로 해결(위 블록 참고). “검사 없음” = 데이터베이스는 있으나 검사가 아직 수행되지 않음 — 오류가 아닙니다. 모니터는 결과를 /var/log/aide/aide.log에서 읽습니다.
정기 검사 → 패널용 로그. 기본 /etc/cron.daily/aide는 새 Ubuntu/Debian에서 /var/log/aide/aide.log를 필요한 형식으로 기록하지 않을 수 있습니다(그리고 이들 버전에는 aide.wrapper가 이미 없습니다). 명시적 --config를 지정한 자체 크론을 추가하는 것이 더 안정적입니다 — root가 644 모드로 로그를 기록하므로 모니터가 추가 그룹 없이 읽을 수 있습니다:
# sudo crontab -e — 매일 02:00 검사: 0 2 * * * /usr/bin/aide --config /etc/aide/aide.conf --check > /var/log/aide/aide.log 2>&1 # 스케줄을 기다리지 않고 지금 실행: sudo bash -c 'aide --config /etc/aide/aide.conf --check > /var/log/aide/aide.log 2>&1'
자동 설치가 이 모든 것을 이미 수행합니다: chmod 755 /var/lib/aide와 02:00 검사 크론 — 수동으로 할 것이 없습니다.
첫 초기화는 웹 애플리케이션 설치 전, 깨끗한 서버에서 하세요. 정상적인 변경 후에는 데이터베이스를 다시 생성하세요: sudo aideinit && sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db.

15. ClamAV 설치

Linux용 바이러스 검사기입니다. 특히 /var/www의 PHP 셸과 악성 코드를 검사하는 데 유용합니다.

sudo apt install clamav clamav-daemon sudo systemctl enable --now clamav-daemon # 시그니처 데이터베이스 업데이트: sudo freshclam # 폴더 수동 검사: sudo clamscan -r /var/www --infected
clamd 데몬이 enable --now 이후에도 “비활성”으로 표시되나요? 일반적인 원인은 세 가지입니다:

1. 설정 파일에 Example 줄이 남아 있음 — 이 줄이 있는 한 clamd는 시작을 거부합니다:

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

2. 시그니처 데이터베이스가 다운로드되지 않음 — 이것 없이는 clamd가 시작되지 않습니다:

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

3. 그저 로딩 중 — clamd는 약 800만 개의 시그니처를 30~60초에 걸쳐 메모리에 올립니다. 잠시 기다린 뒤 확인하세요: systemctl is-active clamav-daemon (상태가 activating이면 → 아직 로딩 중).

진단: sudo journalctl -u clamav-daemon -n 30 --no-pager.
대시보드에 “검사한 파일: 0” / “마지막 검사: —”로 표시되나요? clamd 데몬은 시그니처를 메모리에만 유지할 뿐, 자체적으로 예약 검사를 수행하지는 않습니다. 대시보드는 예약 검사 결과를 표시하므로, 검사를 실행하고 로그를 기록하는 크론이 필요합니다. 자동 설치는 래퍼 /usr/local/bin/clamav-scan.sh와 01:30 크론을 설정합니다 — 첫 실행 후 “검사한 파일”과 “마지막 검사”가 채워집니다. 예약 시각을 기다리지 않고 바로 실행하려면: sudo /usr/local/bin/clamav-scan.sh.

16. Linux Malware Detect(maldet) 설치

Linux Malware Detect(LMD)는 PHP 셸, 웹 백도어, 다운로더 등 웹 위협을 겨냥한 악성코드 스캐너입니다. ClamAV 엔진을 사용하며 자체 시그니처로 이를 보완합니다.

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 # 시그니처 업데이트: sudo maldet -u # /var/www 스캔: sudo maldet -a /var/www
LMD와 ClamAV는 함께 잘 작동합니다. 최근 보고서: maldet --report.
설치 중 update-rc.d: error: unable to read /etc/init.d/maldet 줄이 나타날 수 있는데, 이는 무해합니다. maldet은 init.d를 사용하지 않으며, 시그니처 업데이트와 스캔은 /etc/cron.daily/maldet를 통해 실행됩니다. 아래에 installation completed가 보이면 모두 설치된 것입니다.
설치했는데도 페이지에 LMD가 "설치되지 않음"으로 표시되나요? maldet은 apt가 아니라 /usr/local/maldetect에 설치되며, open_basedir가 켜져 있으면 shell을 통해 설치 여부를 확인합니다. "데이터는 서버에 있는데 페이지가 비어 있음" 섹션을 참고하세요.

17. Suricata 설치

네트워크 침입 탐지 시스템: 패킷 수준에서 트래픽을 분석하며 수천 개의 공격 시그니처를 알고 있습니다. ModSecurity를 보완합니다(ModSecurity는 HTTP 수준에서, Suricata는 TCP/IP 수준에서 동작).

sudo add-apt-repository ppa:oisf/suricata-stable sudo apt update && sudo apt install suricata # 최신 규칙 다운로드: sudo suricata-update sudo systemctl enable --now suricata
Suricata가 "활성" 상태인데 대시보드에 알림이 안 보이거나 이벤트 수가 0인가요? Suricata는 /var/log/suricata/eve.json을 root 권한, 디렉터리 750 모드로 기록하므로 웹 서버(www-data)가 읽을 수 없습니다. 디렉터리에 진입 권한을 열어 주세요 — 내부 파일은 계속 보호됩니다:
sudo chmod o+rx /var/log/suricata
자동 설치는 이 작업을 자동으로 처리하므로 수동으로 할 필요가 없습니다.

18. Falco 설치

eBPF/커널 모듈을 통해 시스템 호출을 가로채 실시간으로 이상 징후를 탐지합니다: nginx에서 실행된 shell, 웹 프로세스의 /etc/passwd 읽기, /bin 쓰기 등.

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
모니터는 journalctl -u falco로 Falco 이벤트를 읽습니다(sudo 없이 systemd-journal 그룹을 통해). www-data가 이 그룹에 속해 있는지 확인하세요 — 수동 설치 페이지의 “sudo 설정”(2항) 참조.
“24시간 동안 이벤트 0건”은 오류가 아니라 정상입니다. Falco는 이벤트 기반입니다: 모든 것이 정상이면 조용히 있다가 이상 징후가 있을 때만 이벤트를 기록합니다(웹 프로세스에서 실행된 shell, /etc/passwd 읽기, 시스템 디렉터리 쓰기). 조용한 서버에서 하루 동안 심각한 이벤트가 0건이라면 건강한 상태입니다.
대시보드에는 파일 출력이 더 안정적입니다. journalctl 읽기는 저널 권한이 필요합니다. 대시보드가 이벤트를 안정적으로 보도록, 자동 설치는 Falco의 file_output/var/log/falco/falco.log를 켜고 서비스에 UMask=0022를 설정합니다(로그를 웹 서버가 읽음). 새로 설치한 경우 수동으로 설정할 필요는 없습니다.

19. ModSecurity(WAF) 설치

ModSecurity — Apache 또는 Nginx용 웹 방화벽(WAF)입니다. SQL 인젝션, XSS, 경로 우회, 스캐너 등 애플리케이션 계층 공격을 차단합니다.

# Apache: sudo apt install libapache2-mod-security2 sudo a2enmod security2 # OWASP Core Rule Set 규칙 세트: sudo apt install modsecurity-crs # 필수: 이 파일이 없으면 규칙 엔진이 꺼져 있습니다 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 # 확인: 403이 반환되어야 합니다 curl -s -o /dev/null -w '%{http_code}\n' "https://monitor.example.com/?id=1%20UNION%20SELECT%201,2--"
패키지 설치만으로는 아무것도 보호되지 않습니다. Apache는 IncludeOptional /etc/modsecurity/*.conf 줄로 설정을 불러오지만, 패키지는 modsecurity.conf-recommended만 넣어 두므로 *.conf 마스크에 해당하지 않습니다. 이 파일을 modsecurity.conf로 복사하지 않으면 SecRuleEngineOff로 남습니다. 모듈은 로드되고 CRS 규칙도 로드되지만, 트래픽은 검사되지 않고 감사 로그도 생성되지 않습니다. 중간 모드 DetectionOnly는 요청을 차단하지 않고 이벤트만 로그에 기록하며 — 대시보드는 이를 노란색으로 표시합니다.

대시보드의 감사 로그 접근. 로그 /var/log/apache2/modsec_audit.log는 root 소유(권한 640)이므로 웹 사용자는 읽을 수 없습니다. 대시보드는 래퍼를 통해 데이터를 가져오니 — 다음과 같이 생성하세요:

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 # /etc/sudoers.d/monitor에서 (사용자 = PHP-FPM이 실행되는 사용자): # www-data ALL=(ALL) NOPASSWD: /usr/local/bin/monitor-modsec
래퍼는 들여쓰기 없는 마지막 SecRuleEngine 지시문을 가져옵니다. 들여쓰기된 줄은 <LocationMatch>/<Directory> 블록(예: phpMyAdmin용 WAF 비활성화) 안에 있어 전역 모드를 결정하지 않습니다.
sudoers의 사용자는 FPM 풀 사용자와 일치해야 합니다. 일반 Apache/Debian에서는 www-data이고, HestiaCP에서는 사이트 풀이 사이트 소유자(예: admin)로 실행되니 — grep -E '^user' /etc/php/*/fpm/pool.d/monitor.example.com.conf로 확인하세요.
사이트가 Nginx 프록시(HestiaCP) 뒤에 있으면 Apache는 클라이언트로 프록시 자체를 보게 됩니다. 대시보드는 X-Forwarded-For 헤더에서 공격자의 실제 IP를 가져옵니다. 규칙이 작동한 트랜잭션만 통계에 포함됩니다. SecAuditLogRelevantStatus 지시문은 4xx/5xx 응답을 모두 감사 로그에 기록하므로 일반 403/500도 거기에 들어가지만 — 대시보드는 이를 WAF 이벤트로 간주하지 않습니다.
---RULES--- 블록은 “모든 활성 규칙” 섹션에 필요합니다. 대시보드는 작동한 규칙뿐 아니라 로드된 모든 CRS 규칙과 사용자 지정 규칙을 표시합니다. for f in … 루프의 세 경로는 CRS 규칙과 로컬 추가 규칙의 일반적인 위치입니다. 배치가 다른 경우(패키지가 파일을 자체 디렉터리에 넣거나, 사용자 지정 규칙이 /etc/modsecurity/custom-rules.conf가 아닌 곳에 있는 경우) sudo grep -rl 'IncludeOptional\|^Include ' /etc/apache2/mods-enabled/security2.conf /etc/apache2/conf-enabled/*.conf 2>/dev/null 명령으로 실제 경로를 찾아 목록에 넣으세요. 래퍼가 오래되어 이 섹션이 없으면 — 해당 섹션은 “사용 불가” 경고만 표시하고, 나머지 페이지는 이전과 동일하게 작동합니다.

20. Auditd 설치

Auditd(Linux Audit Daemon)는 커널 수준에서 시스템 호출을 기록합니다: 로그인 및 로그아웃, sudo 명령, 인증 실패 시도, 파일 변경. 모니터는 오늘의 로그인, 실패한 시도, sudo 명령을 표시합니다.

sudo apt install auditd audispd-plugins sudo systemctl enable --now auditd # 상태 및 이벤트 확인: sudo systemctl status auditd sudo ausearch -m USER_LOGIN -ts today
모니터는 ausearch(/usr/sbin/ausearch)로, 필요한 경우 tail 명령으로 /var/log/audit/audit.log에서 이벤트를 읽습니다. 둘 다 sudoers에 등록되어야 합니다.

21. Monit 설치

서비스(nginx, php-fpm, mysql 등)를 감시하고 중단되면 재시작합니다. 이메일로 알림을 보낼 수 있습니다.

sudo apt install monit sudo systemctl enable --now monit # 설정 파일: sudo nano /etc/monit/monitrc ls /etc/monit/conf.d/
모니터는 monit status를 통해 서비스 목록을 가져옵니다. /etc/monit/monitrc에서 HTTP 인터페이스가 활성화되어 있어야 하며(allow localhost가 포함된 set httpd 블록), 그렇지 않으면 monit status가 오류를 반환합니다.
대시보드에 “감시 중인 서비스 0개”가 표시되나요? 원인은 두 가지입니다. (1) HTTP 인터페이스가 꺼져 있음 — monitrcset httpd 줄이 주석 처리되어 있습니다(기본값은 # set httpd port 2812 …). 블록의 주석을 해제하고 localhost를 허용하세요. (2) httpd를 켠 것만으로는 아무것도 감시하지 않습니다 — Monit은 check 구문에 정의된 것만 계산하며, 이것이 없으면 인터페이스가 작동해도 목록은 비어 있습니다. 최소 작동 설정:
# /etc/monit/conf.d/00-httpd — localhost용 HTTP 인터페이스: set httpd port 2812 use address localhost allow localhost # check 구문 예시(무엇을 감시할지): 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 # 문법 확인 (Control file syntax OK) sudo systemctl reload monit sudo monit status
자동 설치는 2812 포트의 httpd와 점검 항목 세트가 포함된 conf.d를 바로 배치합니다 — 새로 설치한 경우 수동으로 설정할 필요가 없습니다.
서비스가 “오류 있음” 상태인가요? 모니터는 상태만 표시하며 웹 패널에서 서비스를 재시작하지 않도록 의도적으로 설계되었습니다(보안 패널에서 root 명령을 원격으로 실행하는 셈이 되기 때문입니다). 진단과 재시작은 SSH로 Monit을 통해 하세요:
sudo monit status <service> # 오류 원인 sudo monit restart <service> # Monit을 통한 재시작 # Monit이 서비스를 올리지 못하면 해당 유닛을 직접 확인하세요: sudo systemctl status <unit> --no-pager sudo journalctl -u <unit> -n 50 --no-pager

22. PSAD 설치 (포트 스캔 탐지)

PSAD는 iptables 로그를 분석하여 포트 스캔과 네트워크 공격을 탐지하고, 각 출처에 위협 수준(1~5)을 부여합니다. fail2ban 및 Suricata를 보완합니다.

sudo apt install psad # PSAD는 iptables 로그를 읽습니다 — 로깅을 켜야 합니다(UFW는 자동으로 처리). # 순수 iptables의 경우 INPUT/FORWARD 체인에 LOG 규칙을 추가하세요. sudo psad --sig-update sudo systemctl enable --now psad
모니터는 psad --Status를 통해 데이터를 읽습니다(sudoers에 등록 필요). iptables 로깅이 없으면 페이지가 비어 있습니다 — 스캔이 없었다면 정상입니다.

23. AppArmor / SELinux (접근 제어)

Mandatory Access Control은 프로그램이 침해되더라도 접근할 수 있는 파일과 리소스를 제한합니다. Ubuntu/Debian에서는 기본적으로 AppArmor를 사용합니다(보통 이미 설치되어 활성화되어 있습니다).

# AppArmor (Ubuntu/Debian): sudo apt install apparmor apparmor-utils sudo systemctl enable --now apparmor sudo aa-status # 프로파일 확인
모니터는 aa-status를 통해 상태를 읽습니다(sudoers에 필요). enforce/complain 모드의 프로파일 수와 프로파일 없는 프로세스를 표시합니다.

“로드된 프로파일”이 enforce + complain보다 많은 것은 정상입니다. AppArmor 4.x(Ubuntu 24.04 이상)에는 unconfined 모드가 추가되었습니다. 프로파일이 커널에 로드되지만 아무것도 제한하지 않습니다. Ubuntu는 user namespaces를 사용하는 프로그램(브라우저, 토렌트 클라이언트 등)의 프로파일 수십 개를 이렇게 표시합니다. 이런 프로파일이 있으면 “로드된 프로파일” 카드가 호박색으로 바뀌고 그 수를 표시합니다. 예를 들어 로드된 120개, enforce 26개일 때 unconfined: 90처럼 나옵니다. 실제로 보호하는 것은 enforce 모드의 프로파일뿐입니다. Ubuntu 22.04(AppArmor 3.x)에는 이 모드가 없어 숫자가 항상 일치합니다.

sudo aa-status | grep -E "profiles are" # 모드별 분류 sudo aa-enforce /etc/apparmor.d/profile-name # 프로파일을 enforce로 전환
Ubuntu가 의도적으로 unconfined로 남겨둔 프로파일을 enforce로 전환하는 것은 신중하게 판단해야 합니다. 실수로 꺼둔 것이 아니라, 켜면 프로그램 자체의 동작이 깨지기 때문입니다. complain 모드의 프로파일은 다릅니다. 규칙이 이미 작성되어 있고 단지 적용되지 않을 뿐입니다.

24. debsums 설치 (패키지 무결성)

debsums는 설치된 패키지의 파일이 저장소의 체크섬과 일치하는지 검사해 변조된 시스템 바이너리를 찾아냅니다(AIDE를 보완). 전체 검사는 1~2분이 걸리므로 cron으로 실행하며, 패널은 그 결과를 data/debsums/debsums.log에서 읽어 카테고리별로 자동 분류합니다(바이너리와 라이브러리만 중요).

작업은 root의 cron(sudo crontab -e)에 등록합니다. 준비된 래퍼 debsums-scan.sh/usr/local/bin/에 넣고(chmod +x; cron 작업 요약 참고), 이 스크립트가 알아서 패널의 data/debsums/에 보고서를 기록합니다.

sudo apt install debsums # Cron 항목 (매일 4:30): 30 4 * * * /usr/local/bin/debsums-scan.sh >> /path/to/monitor/logs/cron.log 2>&1

래퍼 debsums-scan.sh는 패널의 data/를 스스로 찾으므로 경로를 지정할 필요가 없습니다.

서버에서 /etc/(설정)와 /usr/share/(리소스)의 변경은 대체로 정상이며, 패널은 이를 별도 색상으로 표시합니다. 위험한 것은 바이너리와 라이브러리의 변경(/bin, /sbin, /usr/lib 등)으로, “바이너리 / 라이브러리” 카드가 바로 이것들을 보여줍니다.

25. Lynis 보고서 설정

Lynis는 수동으로 또는 cron으로 실행됩니다. 보고서는 프로젝트의 data/lynis/ 폴더에 저장되어야 하며, 모니터는 lynis-report.dat 파일을 읽습니다.

# 1회 실행(패널 루트 경로를 본인 것으로 바꾸세요): sudo lynis audit system --report-file /path/to/monitor/data/lynis/lynis-report.dat # 일일 감사 — cron 항목(래퍼 lynis-scan.sh가 /usr/local/bin/에 준비되어 있음, 요약 참고): 0 3 * * * /usr/local/bin/lynis-scan.sh >> /path/to/monitor/logs/cron.log 2>&1
최초 실행 후 “Lynis 감사” 페이지에 hardening index, 경고 및 권장 사항이 바로 표시됩니다.
Lynis 페이지의 “감사 실행” 버튼. 이 버튼은 cron을 기다리지 않고 패널에서 바로 lynis-scan.sh를 백그라운드로 실행합니다. “스캔 중…”을 표시하고 완료되면 보고서를 자동으로 갱신합니다. 이를 위해 웹 사용자에게 스크립트 실행용 sudoers 항목이 필요하며, 설치 프로그램이 /etc/sudoers.d/monitor에 자동으로 추가합니다. 패널을 수동으로/이전에 설치했다면, 파일에 이미 지정된 동일한 사용자로 항목을 추가하세요:
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

26. Logwatch 보고서 설정

Logwatch는 매일 생성되는 보고서를 프로젝트의 data/logwatch/ 폴더에 .txt 형식으로 저장해야 합니다. 모니터는 최신 보고서와 보관된 기록을 표시합니다.

# 매일 (6:00) — cron 항목 (완성된 래퍼 logwatch_daily.sh는 /usr/local/bin/에 있음, 요약 참고): 0 6 * * * /usr/local/bin/logwatch_daily.sh >> /path/to/monitor/logs/cron.log 2>&1

패널 모듈

27. 네트워크 모니터 (내장)

네트워크 모니터는 설치가 필요 없습니다 — 대시보드에 내장된 페이지입니다. 로컬 소스에서 서버의 네트워크 상태를 보여줍니다:

  • 인터페이스와 트래픽 — /proc/net/dev에서;
  • 링크 상태(UP/DOWN)와 IP — ip를 통해;
  • 연결과 수신 대기 포트 — ss를 통해;
  • 최근 24시간 커널 네트워크 이벤트 — journalctl -k를 통해.

앞의 세 소스는 sudo 없이 동작하므로 인터페이스, 트래픽, 연결, 포트는 즉시 표시됩니다. “커널 이벤트” 블록은 journalctl -k를 사용하며, systemd-journal 그룹을 통해 읽습니다(“sudo 설정”, 2번 항목), sudo는 필요 없습니다. 웹 사용자에게 모두 접근 가능한지 확인:

# www-data 이름으로 확인(그 아래에서 PHP가 실행됨): 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
“커널 네트워크 이벤트” 블록은 커널 네트워크 스택 이벤트(링크 up/down 변경, 캐리어 오류, “network unreachable”)를 보여줍니다. 방화벽 UFW BLOCK 기록은 여기에 포함되지 않습니다 — “UFW 방화벽”과 “공격 지도” 페이지에 있습니다. 초록색 체크와 함께 비어 있는 블록 = 하루 동안 네트워크 장애가 없었음.

28. 디스크 및 SMART

내장 페이지는 세 가지를 보여줍니다:

  • 파일 시스템 — 파티션 사용량(df); ≥90%면 막대가 빨갛게 표시됩니다;
  • 저장 장치 — 디스크 목록(lsblk), 실제 장치만(loop/snap는 숨김);
  • 상태(SMART) — 디스크 상태 및 속성(smartctl).

용량과 장치 목록은 설정 없이 바로 작동합니다. SMART에는 smartmontools 패키지가 필요합니다. 웹 프로세스는 디스크 장치에 직접 접근할 수 없으므로, SMART는 cron으로 data/disk/smart.txt 파일에 기록되고 대시보드가 이를 읽습니다.

작업은 root 크론에 등록합니다(sudo crontab -e). 준비된 래퍼 smart-scan.sh/usr/local/bin/에 넣으면 되며(chmod +x; cron 작업 요약 참고), 대시보드의 data/disk/에 직접 기록합니다.

sudo apt install smartmontools # Cron 줄(30분마다): */30 * * * * /usr/local/bin/smart-scan.sh >> /path/to/monitor/logs/cron.log 2>&1

래퍼 smart-scan.sh는 대시보드의 data/를 스스로 찾으므로 경로를 지정할 필요가 없습니다. 내부적으로 lsblk -e7,11이 loop/cdrom을 제외합니다.

가상 디스크(QEMU/KVM 등)에서는 보통 전체 상태 “상태: OK”만 제공되며, 온도, 가동 시간, 재할당 섹터는 비어 있을 수 있습니다 — 정상입니다. 물리 서버에서는 모든 속성이 표시됩니다.

29. 성능 (CPU/RAM/네트워크/디스크)

이 페이지는 최근 24시간 동안의 서버 부하 이력을 보여 줍니다 — Load Average, CPU 사용률 및 I/O 대기, RAM/Swap, 네트워크 트래픽(수신/송신), 디스크 I/O(읽기/쓰기), 디스크 및 inode 사용량, 열린 파일 디스크립터와 MySQL 연결 수, 그리고 현재 TCP 연결 및 프로세스 수입니다.

데이터는 cron/collect_metrics.php가 수집합니다 — 5분마다 카운터의 “원시” 스냅샷 하나(/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')을 DB 테이블 system_metrics에 기록합니다. 백분율과 속도는 페이지가 인접한 두 스냅샷의 차이로 직접 계산합니다(디스크/inode/디스크립터 사용량과 MySQL 연결 수는 재계산 없이 순간 값). sudo는 필요 없습니다 — 소스는 root 권한 없이 읽습니다. 24시간이 지난 데이터 지점은 기록할 때마다 자동으로 삭제됩니다.

# Cron 줄 (5분마다): */5 * * * * /usr/local/bin/collect-metrics-all.sh >> /path/to/monitor/logs/cron.log 2>&1

래퍼 collect-metrics-all.sh(cron 작업 요약 참조)는 서버에 설치된 모든 패널 인스턴스를 스스로 찾아 각각의 cron/collect_metrics.php를 사이트 소유자 권한으로 실행합니다.

수집기가 최소 두 번 실행되기 전(설치 후 처음 ~10분간)에는 페이지에 “데이터 수집 중”이 표시됩니다 — 그래프가 속도와 백분율을 계산하려면 인접한 데이터 지점이 최소 한 쌍은 필요합니다.

부하 알림(“설정” → “부하 알림” 섹션) — CPU/RAM/디스크/inode 임계값을 초과하면 패널이 Telegram/Email로 알림을 보내고(일일 보고서와 같은 채널이므로 알림용으로 따로 켤 필요 없음), 지표가 정상으로 돌아오면 다시 하나를 보냅니다. 임계값이 유지되는 동안 반복 스팸은 하지 않습니다: 다음 알림은 “복구됨 → 다시 초과” 주기 이후에만 옵니다.

임계값은 동일한 collect_metrics.php가 실행될 때마다(5분마다) 확인합니다 — 별도의 cron은 필요 없습니다. “이미 알림함 / 아직 아님” 상태는 data/alerts_state.json에, 임계값은 패널 설정에 저장됩니다.

30. 공격 지도 (GeoIP)

“공격 지도” 페이지는 geoiplookup 명령으로 IP의 국가를 확인합니다. GeoIP 패키지가 없으면 국가가 확인되지 않아 지도에 점이 표시되지 않습니다:

sudo apt install geoip-bin geoip-database # 확인: geoiplookup 8.8.8.8
sudo는 필요 없습니다 — /usr/share/GeoIP/GeoIP.dat 데이터베이스는 누구나 읽을 수 있고 결과는 tmp/geoip_cache.json에 캐시됩니다. 지도 자체(Leaflet + OpenStreetMap 타일)는 브라우저에서 로드되므로 대시보드를 연 컴퓨터에 인터넷이 필요합니다.

31. 외부 노출, 업데이트 및 자동 업데이트

도구의 “켜짐/꺼짐”이 아니라 서버의 실제 보안 상태를 보여주는 두 개의 내장 대시보드 카드입니다. 설치가 필요 없고 sudo 없이 로컬에서 읽습니다.

외부 노출 — 모든 인터페이스(0.0.0.0/[::])를 수신하며 외부에서 접근 가능한 서비스의 수입니다. DBMS나 캐시(MySQL, PostgreSQL, Redis, MongoDB, Memcached, Elasticsearch)가 외부로 드러나 있으면 빨간색으로 강조합니다 — 이는 직접적인 구멍입니다(보안 점수 −10). 출처: ss -tuln.

카드가 빨간색이면 DBMS를 외부로부터 차단하세요. 127.0.0.1에 바인딩하거나(MySQL/PostgreSQL 설정의 bind-address, Redis의 bind 127.0.0.1) UFW에서 포트를 닫으세요.
“열린 포트” ≠ “외부에서 접근 가능”. 127.0.0.1(loopback)을 수신하는 서비스는 서버 자신에게만 보이며, 포트가 “열려” 있어도 외부에서는 접근할 수 없습니다. 그래서 loopback에 바인딩된 25번 포트의 Postfix는 안전합니다. 자동 설정이 inet_interfaces = loopback-only를 지정합니다(더불어 중립적인 smtpd_banner로 버전 노출에 관한 Lynis MAIL-8818 경고를 해결합니다). “외부 노출” 카드는 0.0.0.0/[::]를 수신하는 것만 외부로 간주하며, loopback 서비스는 포함하지 않습니다.
Lynis MAIL-8818 수동 설정(메일을 직접 설치한 경우): /etc/postfix/main.cf에서 smtpd_banner = $myhostname ESMTP(버전과 OS 제외)와 inet_interfaces = loopback-only를 설정한 뒤 sudo systemctl restart postfix를 실행하세요.

보안 업데이트 — 설치를 기다리는 보안 패치의 수와 커널 업데이트 후 재부팅이 필요한지 여부입니다(패치가 있으면 보안 점수 −5). 출처: /usr/lib/update-notifier/apt-check, /var/run/reboot-required 파일. 상세 목록은 “보안 업데이트” 페이지에 있습니다.

# 업데이트 설치: sudo apt update && sudo apt upgrade # 외부로 무엇이 수신 중인지 확인: ss -tuln | grep -E '0\.0\.0\.0|\[::\]'
업데이트 카드는 Ubuntu/Debian(update-notifier-common)에서 작동합니다. apt-check가 없으면 모니터는 apt-get -s upgrade로 패치를 계산합니다.

보안 자동 업데이트(unattended-upgrades) — “보안 업데이트” 페이지의 별도 카드에서 보안 패치 자동 설치가 켜져 있는지, 마지막으로 언제 실행되었는지 보여줍니다. sudo가 필요 없으며 상태는 apt-config dump로 읽습니다.

sudo apt install unattended-upgrades sudo dpkg-reconfigure -plow unattended-upgrades # 활성화 # 활성화되었는지 확인: apt-config dump | grep Unattended-Upgrade

유지 관리

32. 백업

백업은 가장 확실한 안전장치입니다. 데이터 손실은 어떤 침해보다 치명적입니다. 두 가지가 필요합니다 — 서버/사이트 백업과 별도의 패널 DB 백업(사용자, WebAuthn 키, 설정, 라이선스가 들어 있습니다).

방법 A — HestiaCP: 사용자의 Backup 탭 → 백업 생성 버튼(또는 서버 설정의 예약 실행). 백업에는 사이트와 해당 DB가 포함됩니다.

방법 B — 수동(cron): DB 덤프 + 패널의 data/ 디렉터리 아카이브:

# root 크론(sudo crontab -e) — 매일 2:30 백업(자신의 이름/경로로 바꾸세요): 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 # 14일이 지난 아카이브 삭제: 0 3 * * * find /var/backups -name 'monitor-*' -mtime +14 -delete
같은 서버에 저장한 백업은 실수는 막아주지만 서버 자체를 잃는 상황은 막지 못합니다. 아카이브를 외부 저장소(다른 서버, S3, rclone로 클라우드)에 복사하세요. 복원이 실제로 작동하는지 확인하세요.

33. 패널 업데이트 및 이전

새 버전으로 업데이트. 먼저 백업을 하세요. 그런 다음 데이터를 유지한 채 코드 파일을 다시 업로드하세요:

  • 덮어쓰기(코드): public/, includes/, assets/, cron/, database/, 그리고 루트의 .htaccess(프런트 컨트롤러 — 라우팅을 이전 버전 것으로 두면 안 됩니다), manifest.json, sw.js;
  • 건드리지 말 것: config.php(DB 정보), data/(보고서), logs/, tmp/(세션 및 캐시).
# 업로드 후 — PHP 캐시 초기화(opcache가 켜져 있는 경우): sudo systemctl reload php*-fpm
FileZilla가 SSH_FX_PERMISSION_DENIEDPermission denied라고 표시합니다. 패널 파일의 소유자는 www-data이고(설치 시 그렇게 설정됨), SFTP 클라이언트는 쓰기 권한이 없는 본인 계정으로 접속합니다. 패널 전체를 www-data에 넘겨 “작동하게” 만드는 것이 바로 이 오류를 일으키는 원인입니다; 아래 세 가지 방법 중 어느 것이든 문제를 해결합니다.
# 방법 A(권장) — 소유자 분리: 코드는 본인, 작업 폴더는 웹 서버. # 웹 서버는 패널 코드에 대한 쓰기 권한을 전혀 얻지 않습니다: 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 # 방법 B — 현재 소유자 위에 ACL 적용(아무것도 옮기지 않음): 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 # 방법 C — www-data 그룹을 통해. 더 간단하지만 패널 파일에 대한 쓰기 권한을 # 웹 서버도 갖습니다(PHP 취약점이 있으면 코드가 교체될 수 있음): 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
방법 A가 안전한 이유. 패널은 세 개의 디렉터리에만 기록합니다 — data/(보고서), tmp/(세션 및 캐시), logs/; 이들은 www-data 소유로 남습니다. 나머지는 코드이며, 웹 서버는 읽기 권한만 필요한데 이는 644 권한의 www-data 그룹으로 제공됩니다. 부수적 이점: PHP 취약점이 있어도 패널 파일을 덮어쓸 수 없습니다. 호스팅 패널(HestiaCP 등)에서는 방법 A가 필요 없습니다: 거기서는 사이트 파일이 이미 SFTP로 접속하는 계정 소유이고, 웹 서버는 그룹으로 파일을 읽습니다.
방법 B의 함정: 이후 파일에 chmod를 실행하면 ACL 마스크가 초기화되어 접근 권한이 조용히 사라집니다. “권한 정리” 후 업로드가 다시 Permission denied에 막히면 — 두 setfacl 명령을 다시 실행하세요.
방법 C의 2 비트가 setgid입니다: SFTP로 올린 파일이 www-data 그룹에 남으며, 그렇지 않으면 패널이 덮어쓸 수 없습니다. 방법 C 이후에는 FileZilla에서 다시 연결하세요 — 새 그룹은 새로 로그인할 때만 적용됩니다. 확인: id deploy(www-data 그룹이 나타나야 함) 및 ls -ld /path/to/monitor(drwxrwsr-xs는 setgid가 설정되었다는 뜻).

다른 서버로 이전:

  1. 새 서버에서 사이트 + HTTPS를 구성하세요(수동 설치 페이지 참고).
  2. 패널의 모든 파일을 config.php, data/와 함께 복사하세요.
  3. DB 이전: 기존 서버에서 mysqldump → 새 서버로 가져오기; config.php의 DB 정보를 수정하세요.
  4. 새 서버에서 다시 설정: sudoers, adm 그룹 소속, cron 작업.
  5. 라이선스는 도메인에 연결되어 있습니다 — 도메인이 동일하면 키는 계속 작동합니다.

34. 접근 복구 (보안 키·비밀번호 분실, IP 차단)

로그인할 수 없다면 서버의 DB에서 직접 해결할 수 있습니다. DB를 여세요(이름은 config.php에서 확인):

sudo mysql MY_DB

WebAuthn 보안 키 분실(2차 인증 통과 불가) — 2FA를 끄고 비밀번호로 로그인한 뒤 새 키를 등록하세요:

UPDATE users SET webauthn_enabled = 0;

비밀번호 분실 — 새 해시를 설정하세요(서버에서 생성해 넣으세요):

# 새 비밀번호 해시 생성: php -r "echo password_hash('NEW_PASSWORD', PASSWORD_BCRYPT), \"\n\";" # DB에서(생성된 해시를 넣으세요): # UPDATE users SET password = '$2y$10$...' WHERE username = 'admin';

IP 필터로 본인을 차단함 — 제한을 끄세요:

UPDATE settings SET value = '0' WHERE name = 'ip_restriction_enabled';
DB에는 항상 접근할 수 있습니다: 서버에서 sudo mysql, 또는 phpMyAdmin / 호스팅 패널의 DB 섹션. 복구 후에는 WebAuthn과 IP 필터를 다시 켜세요.

35. 모든 cron 작업을 한곳에서

작업 모음은 서버의 root cron에 있습니다(추가는 sudo crontab -e로). 사용하는 도구의 줄만 남기고, 경로는 자신의 서버에 맞게 수정하세요.

# 모니터 서버 cron(root) — 다음으로 입력: sudo crontab -e # 01:30 — 위험 경로(web, home, temp) ClamAV 스캔 → “검사된 파일 수” 및 “마지막 스캔” 카드 30 1 * * * /usr/local/bin/clamav-scan.sh >> /path/to/monitor/logs/cron.log 2>&1 # 02:00 — AIDE 파일 무결성 검사(명시적 --config 필요) 0 2 * * * /usr/bin/aide --config /etc/aide/aide.conf --check > /var/log/aide/aide.log 2>&1 # 부팅 시 — /var/lib/aide 권한 복구(패키지 tmpfiles 파일 # aide-common.conf가 0700으로 되돌려 패널이 DB를 못 봄) @reboot chmod 755 /var/lib/aide # 03:00 — Lynis 보안 감사 0 3 * * * /usr/local/bin/lynis-scan.sh >> /path/to/monitor/logs/cron.log 2>&1 # 04:00 — ipsum 차단 목록 갱신(level 1) 0 4 * * * /usr/local/bin/load-ipsum.sh >> /path/to/monitor/logs/cron.log 2>&1 # 부팅 시 ipsum 세트는 ipsum-load.service가 올림(방화벽보다 먼저, 아니면 # UFW가 before.rules에서 세트를 못 봄) — cron 아님. 여기엔 위 일일 갱신만. # 06:00 — Logwatch 보고서 0 6 * * * /usr/local/bin/logwatch_daily.sh >> /path/to/monitor/logs/cron.log 2>&1 # 30분마다 — SMART 디스크 검사 */30 * * * * /usr/local/bin/smart-scan.sh >> /path/to/monitor/logs/cron.log 2>&1 # 04:30 — debsums 패키지 무결성 30 4 * * * /usr/local/bin/debsums-scan.sh >> /path/to/monitor/logs/cron.log 2>&1 # 08:00 — 이메일 및 Telegram 예약 보고서 0 8 * * * /usr/local/bin/daily-report-all.sh >> /path/to/monitor/logs/cron.log 2>&1 # 매시간 — 패키지 목록 갱신(“보안 업데이트” 카드용) 0 * * * * /usr/bin/apt-get update -qq >/dev/null 2>&1 # 5분마다 — 리소스 스냅샷(CPU/RAM/네트워크/디스크), “성능” 페이지용 */5 * * * * /usr/local/bin/collect-metrics-all.sh >> /path/to/monitor/logs/cron.log 2>&1
각 항목의 자세한 내용은 해당 섹션에 있습니다. 백업 작업(이전 섹션)도 같은 cron에 추가됩니다. 수정 후 확인하세요: sudo crontab -l 및 cron 서비스가 활성 상태인지.
cron 시간 = 서버의 시간대이며, config.phpTIMEZONE이 아닙니다. TIMEZONE 상수는 PHP에만 영향(패널이 날짜를 표시하는 방식)을 주고, cron 데몬은 OS 시스템 시간으로 작업을 실행합니다. 서버 시간대가 여러분의 것과 다르면 “08:00” 보고서가 엉뚱한 시각에 옵니다. 예: 서버는 다른 시간대(Asia/Shanghai, UTC+8)이고 여러분은 서울(UTC+9) → “08:00” 보고서가 여러분 기준 09:00에 옵니다. 확인하고 필요하면 시스템 시간대를 자신의 것으로 맞추세요:
# 현재 서버 시간대 확인: timedatectl # 자신의 시간대 설정(예)하고 cron 재시작: sudo timedatectl set-timezone Asia/Seoul sudo systemctl restart cron
그러면 0 8 * * * 줄이 현지 시간 08:00에 실행됩니다. 그렇지 않으면 cron 자체를 옮겨야 하는데, 겨울/여름 시간 전환 때 다시 어긋나므로 시스템 시간대를 설정하는 편이 옳습니다.
준비된 래퍼 스크립트. 실제 사본과 crontab 예시(crontab.txt)는 프로젝트 옆의 system/ 폴더에 있으며, public_html 바깥에 있습니다. 이는 사이트의 일부가 아니므로 웹 루트에 올릴 필요가 없습니다; 서버의 시스템 경로에 배치하세요(위 crontab처럼):
  • lynis-scan.sh/usr/local/bin/ (chmod +x) — lynis audit system을 실행하고, 스캔 동안 /tmp/lynis-running 플래그를 설정하며 lynis-report.dat를 패널의 data/lynis/에 복사합니다;
  • logwatch_daily.sh/usr/local/bin/ (chmod +x) — 일일 Logwatch 보고서(sshd, fail2ban, sudo, postfix)를 data/logwatch/에 생성합니다;
  • smart-scan.sh/usr/local/bin/ (chmod +x) — 디스크 상태(smartctl)를 data/disk/에 기록합니다;
  • debsums-scan.sh/usr/local/bin/ (chmod +x) — 패키지 무결성(debsums)을 data/debsums/에서 검사합니다;
  • clamav-scan.sh/usr/local/bin/ (chmod +x) — 위험 경로(web, home, temp)의 ClamAV 안티바이러스 스캔; 요약을 /var/log/clamav/scan.log에 기록하고 ClamAV 페이지가 이를 읽습니다(위 crontab의 01:30 줄);
  • load-ipsum.sh/usr/local/bin/ (chmod +x) — ipset 세트 ipsum(level 1)을 제자리에서 갱신하며 활성 방화벽 규칙을 끊지 않음(위 crontab의 04:00 줄);
  • daily-report-all.sh/usr/local/bin/ (chmod +x) — 패널의 cron/daily_report.php 보고서를 실행합니다(위 crontab의 08:00 줄);
  • daily_report.php — 이미 패널에 포함(cron/daily_report.php), daily-report-all.sh로 실행되므로 별도 설치 불필요;
  • collect-metrics-all.sh/usr/local/bin/ (chmod +x) — 패널의 cron/collect_metrics.php를 실행합니다(“성능” 페이지, 위 crontab의 */5 줄); collect_metrics.php는 이미 패널에 포함되어 별도 설치 불필요;
  • crontab.txt (system/cron/) — 작업 예시; 필요한 줄을 sudo crontab -e로 입력하세요.
crontab의 스크립트 경로는 실제로 배치한 위치와 일치해야 합니다.
스크립트를 /usr/local/bin/에 넣는 방법. FileZilla에서 그곳에 직접 쓸 수 없습니다 — 디렉터리가 root 소유라 SFTP 클라이언트는 SSH_FX_PERMISSION_DENIED를 받습니다. 순서는 이렇습니다: 먼저 파일을 /tmp에 올리고(그곳은 누구나 쓸 수 있음), 명령 한 줄로 제자리로 옮깁니다:
# FileZilla에서: “원격 사이트” 필드에 /tmp를 입력하고 스크립트를 그곳에 업로드, # 그다음 SSH로(install이 소유자와 권한을 바로 설정하므로 chown/chmod 불필요): sudo install -o root -g root -m 755 /tmp/lynis-scan.sh /usr/local/bin/lynis-scan.sh rm -f /tmp/lynis-scan.sh # 확인: 파일이 제자리에 있고, 권한은 rwxr-xr-x, 문법 이상 없음 bash -n /usr/local/bin/lynis-scan.sh && ls -l /usr/local/bin/lynis-scan.sh
디렉터리를 혼동하지 마세요: 서버 루트의 /tmp가 필요합니다 — /var/tmp나 패널 내부의 tmp/가 아닙니다(후자는 www-data 소유이며 여러분 사용자에게는 닫혀 있음). FileZilla 트리에서 /tmpvar 옆의 최상위 가지이며, 그 안에 있지 않습니다.
서버를 자동 설정으로 구성했나요? 이 래퍼와 그 cron 작업은 스크립트로 이미 설치되었습니다(/usr/local/bin/에, 로그는 /var/log/arciveo-cron.log) — 수동으로 할 일은 없습니다.
스크립트가 패널을 찾는 방식. 래퍼는 도메인 중립적입니다: /home/*/web/*/public_html/var/www/*를 훑어 패널 설치를 찾고, 보고서를 각각의 data/에 넣습니다. 패널이 다른 경로에 있으면 스크립트 안 for app in … 줄에 추가하세요. 그렇지 않으면 Lynis/SMART/debsums/Logwatch 보고서가 패널에 들어오지 않습니다.
cron.log와 접근 권한. logs/cron.log 파일은 root cron이 처음 만들면 root 소유가 되어, 패널의 “cron 로그” 탭이 이를 읽거나 비울 수 없습니다. 파일을 미리 웹 사용자 이름으로 만드세요(사이트 디렉터리 소유자; HestiaCP에서는 예: admin 계정) — 그러면 root cron은 소유자를 바꾸지 않고 덧붙이기만 합니다:
# cron 줄 추가 전에 웹 사용자로 미리 생성: sudo -u OWNER touch /path/to/monitor/logs/cron.log # cron.log를 이미 root cron이 만들었다면 — 웹 사용자에게 넘기기: sudo chown OWNER:OWNER /path/to/monitor/logs/cron.log sudo chmod 644 /path/to/monitor/logs/cron.log
디렉터리 소유자 확인: stat -c %U /path/to/monitor.
패널에서 관리. “시스템” 섹션에 “Crontab” 페이지가 있어 SSH 없이 작업을 보고 추가할 수 있습니다. 패널은 자신을 통해 추가된 작업만 편집합니다(root crontab의 별도 블록, 서비스 주석으로 표시됨); 이미 crontab에 있는 것(위 목록)은 “기타 서버 작업” 읽기 전용 목록으로 “편집기에 복사” 버튼과 함께 표시됩니다 — 이 버튼은 일정/명령을 추가 폼으로 옮길 뿐 원본 줄은 건드리지 않습니다. 기존 작업을 패널 관리로 “옮기려면” — 편집기에 복사해 저장한 뒤, 옛 줄을 수동으로 삭제하세요(sudo crontab -e). 그렇지 않으면 두 번 실행됩니다.
서버에서 한 번만 설정. 이 페이지에는 권한이 있는 래퍼 스크립트가 필요합니다 — 맨 sudo crontab이 아니라(그것은 패널 세션 접근권을 얻은 누구든 root로 직접 상승시키게 됨), 서비스 주석 사이의 자기 블록만 건드리는 두 명령(list/set)의 좁은 스크립트입니다. 한 번만 설치하세요:
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
웹 사용자는 www-data와 다를 수 있습니다 — 사이트의 PHP-FPM 풀이 어느 사용자로 동작하는지 확인하고(ps -o user= -C php-fpm) sudoers 줄에 넣으세요.
새 파일이 다른 소유자로 올라가 페이지가 “Access denied.”로 응답함. public/crontab_monitor.php 파일이 사이트의 다른 파일과 다른 시스템 사용자(예: root)로 FTP/SFTP로 업로드되면 웹 서버가 이를 읽을 수 없습니다. 옆 파일과 소유자·권한을 비교해 맞추세요:
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

진단

36. 도구가 설치되어 있는데 "설치되지 않음"이라고 표시됩니다

모니터는 APT 패키지 데이터베이스인 dpkg-query를 통해 도구 설치 여부를 확인합니다. apt를 거치지 않고 설치한 경우(수동, snap, 소스 빌드) dpkg는 이를 인식하지 못합니다.

# dpkg로 확인: dpkg -l fail2ban | grep '^ii' dpkg -l auditd | grep '^ii' # 바이너리 경로 찾기: which ufw fail2ban-client auditctl # www-data 권한으로 sudo 테스트: sudo -u www-data sudo fail2ban-client status sudo -u www-data sudo ufw status verbose

37. 문제 해결 (500, 데이터 없음)

500 오류 — PHP, nginx, 모니터 자체의 로그를 확인하세요:

tail -50 /var/log/nginx/error.log tail -50 /var/log/php*-fpm.log # 모니터 로그: tail -50 logs/monitor_$(date +%Y-%m-%d).log # 폴더 권한: ls -la data/ tmp/ logs/
호스팅 패널(HestiaCP, ISPmanager, cPanel)에서 실행 중인가요? 그곳에서는 PHP가 www-data가 아니라 사용자 계정(예: 사이트 디렉터리 소유자인 admin)으로 동작합니다. 모든 sudo 규칙과 그룹 멤버십(adm, systemd-journal)은 이 사용자에게 지정해야 하며, 그렇지 않으면 서비스가 동작 중이어도 모듈에 “비활성 / 0”이 표시됩니다. 실제 PHP 사용자 확인: ps -o user= -C php-fpm | sort -u 또는 사이트 디렉터리 소유자 stat -c '%U' /path/to/monitor. 이후 아래 모든 명령에서 www-data 대신 그 사용자를 넣으세요. 자동 설치는 웹 사용자를 스스로 판별해 sudoers를 지정합니다.

데이터가 표시되지 않음 — 거의 항상 sudo 권한이 지정되지 않아 발생합니다. 웹 사용자 이름으로 해당 명령을 확인하세요(www-data를 본인 사용자로 교체). -n 플래그 = PHP처럼 비밀번호 없이 실행 — 비밀번호를 요구하면 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
도구는 동작하는데 모듈에 “비활성” / “0”이 표시됨(예: 터미널의 sudo aa-status는 프로필을 보여주지만 “AppArmor” 페이지는 “비활성”). 원인: 웹 사용자에게 모듈 명령에 대한 sudo 권한이 없기 때문입니다. 위 목록에서 해당 명령을 확인하세요. 비밀번호를 요구하면 /etc/sudoers.d/monitor에 빠진 줄을 추가하세요(“sudo 설정”). 자주 나오는 “새” 명령: /usr/sbin/aa-status(MAC), /usr/sbin/psad --Status(PSAD).
특정 페이지(Falco, ModSecurity, Auditd, UFW 열린 포트)가 비어 있으면 sudo 관련 섹션의 목록과 대조하세요. 아마 apache2ctl, ausearch, aa-status, ss가 허용되지 않았거나, 웹 사용자가 adm/systemd-journal 그룹에 없을 것입니다(여기에서 fail2ban/auth/modsec 로그와 journalctl — Falco 및 커널 이벤트를 읽습니다).

38. 서버에 데이터가 있는데도 페이지가 비어 있음

증상: 서버에는 데이터가 있는데(shell로는 보임) 페이지에는 "데이터 없음" 또는 잘못된 상태가 표시됨 — 예를 들어 데이터베이스가 생성되었는데도 AIDE가 "초기화되지 않음"이라고 표시함.

원인은 open_basedir입니다. 많은 패널과 호스팅이 PHP-FPM 풀을 도메인 디렉터리로 제한하므로, 시스템 경로(/var/lib/aide, /var/log, /proc…)에 대한 PHP 함수 file_exists(), file_get_contents(), filemtime()가 차단됩니다. 모니터는 표준 시스템 명령(cat, test, stat)으로 이러한 경로를 읽어 이를 우회합니다.

# shell로 파일이 보이는지 확인(모니터가 읽는 방식): sudo -u www-data bash -lc 'test -e /var/lib/aide/aide.db && echo VISIBLE || echo NO' # 도메인 풀의 현재 open_basedir 값: grep -ri open_basedir /etc/php/*/fpm/pool.d/ 2>/dev/null
shell에서는 파일이 "보이는데"(VISIBLE) 페이지에서는 보이지 않는다면 open_basedir 문제입니다. 올바른 해결책은 시스템 명령으로 읽는 것입니다(AIDE와 네트워크 모니터에는 이미 적용됨). open_basedir/var, /proc로 확장할 필요는 없으며 보안상 좋지 않습니다.

39. SSL 페이지가 작동하지 않습니다

모니터는 443 포트를 통해 도메인에 직접 연결하여 인증서를 확인합니다. 서버 자체에서 도메인에 접근할 수 없거나 포트가 방화벽에 막혀 있으면 확인이 실패합니다.

# 인증서 수동 확인: echo | openssl s_client -connect monitor.example.com:443 2>/dev/null \ | openssl x509 -noout -dates # 접근 가능 여부 확인: curl -I https://monitor.example.com
모니터는 nginx 설정(/etc/nginx/sites-enabled/, /etc/nginx/conf.d/)과 Apache 설정(/etc/apache2/sites-enabled/), 그리고 HTTP_HOST의 현재 호스트에서 도메인을 자동으로 가져옵니다.
서브도메인 자동 감지. 서브도메인은 공개 Certificate Transparency 로그에서 자동으로 감지되어 네트워크로 확인되며, 다른 서버에 있어도 감지됩니다. 수동으로 추가할 필요가 없습니다.

40. 여러 DB 중 하나만 표시됨

모니터는 config.php에 설정된 사용자로 MySQL에 접속하며, 이 사용자는 자기 데이터베이스에만 접근할 수 있습니다. MySQL은 information_schema에 권한이 있는 데이터베이스만 표시하므로 나머지는 보이지 않습니다.

모니터가 모든 DB를 볼 수 있게 하려면 이 사용자에게 읽기 전용 권한을 부여하세요(root로 한 번만 실행하고, config.php의 사용자 이름을 넣으세요):

sudo mysql -u root GRANT SELECT, PROCESS, SHOW DATABASES ON *.* TO 'DB_USER'@'localhost'; FLUSH PRIVILEGES; EXIT;
SELECT ON *.*는 읽기 권한만 부여하므로 무언가를 수정, 삭제, 생성할 수 없어 모니터링에 안전합니다.
이 GRANT 없이는 대시보드가 자기 데이터베이스만 보게 되며, 이는 오류가 아니라 권한 제한입니다. 대시보드는 sudo mysql을 전혀 사용하지 않으며, 데이터베이스 목록은 자체 PDO 연결을 통해 가져옵니다.

41. “데이터베이스” 페이지에 PostgreSQL이 표시되지 않음

PostgreSQL은 postgres 사용자 수준의 접근 권한이 필요하지만, 패널의 웹 사용자에게는 이 권한이 없습니다. PHP에서 광범위한 sudo psql을 여는 것은 안전하지 않으므로, 대신 패널은 매개변수 없이 버전, 연결 수, 크기가 포함된 데이터베이스 목록만 출력하는 좁은 범위의 래퍼를 호출합니다. 다음과 같이 생성하세요:

sudo tee /usr/local/bin/monitor-pgstat >/dev/null <<'EOF' #!/bin/sh # Arciveo 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 # /etc/sudoers.d/monitor 파일에 (사용자 = PHP-FPM이 실행되는 사용자): # www-data ALL=(ALL) NOPASSWD: /usr/local/bin/monitor-pgstat
PostgreSQL을 사용하지 않는다면 sudoers에서 monitor-pgstat 줄을 제거하고(수동 설치 13단계) 스크립트 자체도 생성하지 마세요: PostgreSQL 카드가 그냥 비활성 상태로 남게 됩니다.

42. 알림이 발생했을 때 대처 방법

대시보드는 무엇이 일어나는지 보여줍니다. 아래는 일반적인 상황에서 무엇을 해야 하는지 안내합니다. 기본 원칙은 당황하지 말고, 정상 활동(본인의 작업, 업데이트, 백업)과 대조한 뒤 심각도에 따라 대응하는 것입니다.

  • 공격 지도 / fail2ban 차단 다수 — 인터넷에 연결된 서버라면 정상입니다(봇이 끊임없이 SSH/웹을 무차별 시도함). 중요한 것은 차단이 작동하는지입니다. SSH 접속이 키 전용인지(비밀번호 비활성화), 본인 IP가 ignoreip에 있는지 확인하세요.
  • ModSecurity가 요청을 차단함 — WAF가 사이트 공격을 막는 것으로, 정상 동작입니다. 정상 트래픽이 차단된다면(오탐) 상세 내역에서 rule id를 찾아 CRS 설정에 예외를 추가하세요.
  • AIDE: 파일 변경됨 — 목록을 본인이 한 작업과 대조하세요(패키지 업데이트, 설정 편집은 정상). 건드리지 않은 시스템 바이너리가 변경됐다면 경계해야 합니다. 정상적인 변경 후에는 AIDE 데이터베이스를 갱신하세요.
  • debsums: 바이너리/라이브러리 변경됨(/etc 외부, /usr/share 외부) — 변조 가능성이 있습니다. 패키지를 대조하세요: debsums PACKAGE_NAME, 의심되면 재설치하세요(apt install --reinstall).
  • ClamAV / maldet: 위협 발견 — 격리된 파일을 확인하고 열지 마세요. 사이트 디렉터리의 웹 셸이라면 서버를 격리하고 진입 지점(취약한 플러그인, 접근 정보 유출)을 찾으세요.
  • Falco: 심각한 이벤트(컨테이너 내 셸 실행, 민감 파일 접근) — 이벤트를 분석하세요: 어느 프로세스인지, 무엇이 실행했는지. 대개 정상적인 관리자 활동입니다.
  • 외부 노출: DBMS/캐시가 빨간색 — 즉시 차단하세요: 서비스를 127.0.0.1에 바인딩하거나 UFW에서 포트를 닫으세요. 실제 보안 구멍입니다.
  • SSL 만료 임박 / 만료됨 — 인증서를 갱신하세요(Let's Encrypt는 자동 갱신됨. 아니라면 certbot renew 또는 대시보드 설정을 확인하세요).
  • 보안 업데이트 대기 중 — 설치하세요: sudo apt update && sudo apt upgrade; 커널 업데이트 후에는 서버를 재부팅하세요.
실제 침해의 징후(알 수 없는 프로세스/사용자, 변경된 바이너리, 외부로 나가는 스팸, 알 수 없는 cron 작업): 서버의 외부 접근을 차단하고, 분석용 백업을 확보하세요. 데이터가 중요하다면 신뢰할 수 있는 백업으로 깨끗한 서버를 새로 구축하세요. 루트킷을 확실히 제거하기는 어렵습니다.
Arcivéo - Security Monitor © 2026