Arcivéo Monitor의 설치, 설정, 유지 관리 안내서입니다. 섹션은 전체 개요, 패널 배포, 보안 도구 연결, 내장 모듈, 진단으로 그룹화되어 있습니다. 명령어는 오른쪽 버튼으로 복사할 수 있습니다.
패널 설치는 단계별 안내 페이지에 있습니다. 방식을 선택하세요:
Arcivéo Monitor는 서버 보안 대시보드입니다. 설치된 도구(Fail2ban, UFW, Lynis, ModSecurity, AIDE, ClamAV, Auditd, CrowdSec, Suricata, Falco 등)에서 데이터를 수집해 대시보드, 공격 지도, 도구별 상세 페이지를 갖춘 단일 인터페이스에 표시합니다.
Monitor는 능동적 방어 수단이 아니며 스스로 공격을 차단하지 않습니다. 이미 작동 중인 도구의 정보를 취합해 보기 쉽게 제공하는 것이 목적입니다.
모니터는 로컬에서만 작동합니다. 즉, 모니터링 대상 서버와 동일한 서버에 설치해야 합니다. SSH나 원격 API는 사용하지 않습니다.
모든 명령(fail2ban-client, ufw status, ipset list 등)은 웹 서버 사용자(보통 www-data, 호스팅 패널에서는 사이트 계정) 권한으로 실행되며, sudo의 제한된 권한 집합, 즉 특정 유틸리티에만 허용되고 전체 root 접근은 없는 상태로 실행됩니다. 결과는 파싱되어 브라우저에 표시됩니다.
점수는 최댓값에서 시작하며 발견된 문제마다 차감됩니다:
PermitRootLogin yes) — −20결과: 80+ = 보호됨, 60–79 = 주의, <60 = 위험.
WebAuthn은 하드웨어 키를 이용한 비밀번호 없는 인증 표준입니다. YubiKey, Touch ID, Face ID, Windows Hello, Passkey를 지원합니다.
비밀번호로 로그인한 후 시스템이 등록된 키로 확인을 요청합니다. 비밀번호가 유출되더라도 물리적 키나 생체 인증 없이는 로그인할 수 없습니다.
설정하려면 측면 메뉴에서 WebAuthn 키를 열고 “키 등록”을 누르세요. 키를 두 개 등록해 두세요. 유일한 키를 분실하거나 고장 나면 그 키로는 대시보드에 로그인할 수 없습니다.
대시보드는 보안 보고서를 Telegram과 이메일로 보낼 수 있습니다(버튼 및 예약 전송). “설정” 섹션에서 구성합니다.
Telegram. 봇 토큰과 chat id가 필요합니다:
@BotFather에게 /newbot을 보내면 123456:ABC... 형식의 토큰을 받습니다.@userinfobot에게 메시지를 보내거나 https://api.telegram.org/bot<TOKEN>/getUpdates를 열어 "chat":{"id":...}를 찾습니다.Email. “설정” → Email에서 두 가지 방식 중 선택:
re_...)와 인증된 발신 도메인을 입력합니다.보고서 상태: “주의” 또는 “정상”. 제목은 실제 문제나 대기 중인 작업이 있을 때만 “주의”가 됩니다: ClamAV 위협 발견, AIDE 파일 변경, Falco 심각 이벤트(최근 24시간 내 Emergency/Alert/Critical), Monit의 다운된 서비스, 재부팅 필요, SSL 만료 임박(≤14일) 또는 대기 중인 보안 업데이트. 봇의 SSH 무차별 대입, fail2ban으로 차단된 IP, Suricata 경보, Lynis 경고, 이미 막아낸 ModSecurity 요청 같은 배경 잡음은 상태를 올리지 않으므로, 이런 수치가 보고서에 있다고 해서 그 자체로 “주의”를 의미하지는 않습니다.
세부 모니터링 모듈(Lynis, UFW, ModSecurity, 공격 지도, AIDE, ClamAV 등)은 유효한 라이선스가 있어야 열립니다. 라이선스가 없으면 대시보드, 설정, 계정은 작동하지만 모듈에는 “라이선스 필요” 카드가 표시됩니다.
개인 계정에서 구매를 마치면 ARCIVEO-XXXX-XXXX-XXXX-XXXX 형식의 활성화 코드가 발급됩니다. 이 코드를 패널 도메인에 “활성화”해야 하며, 그러면 코드가 서명된 라이선스 파일([license] 블록)로 변환되어 패널에 붙여 넣을 수 있습니다.
활성화 방법(3단계):
my.arciveo.com → “라이선스” / “라이선스 활성화” 섹션에서 ARCIVEO-… 코드를 복사하세요.monitor.example.com)을 입력하세요. 활성화를 누르면 시스템이 해당 도메인에 연결된 라이선스 파일을 생성해 “복사” 버튼이 있는 입력란에 표시합니다.패널은 키를 암호학적으로 검증합니다: 서명, 도메인 연결, 유효 기간을 확인합니다.
config.php의 APP_URL 상수에서 값을 가져와 호스트 이름만 입력하세요 — https://와 www 접두사는 빼야 합니다. 활성화는 1회성입니다: 코드는 입력한 도메인의 라이선스로 변환되며 다시 활성화되지 않습니다 — 도메인을 잘못 입력하면 키가 패널에 맞지 않고 코드도 소모됩니다. 따라서 도메인을 신중하게 입력하세요.
패널의 모든 주요 매개변수는 루트(public/ 폴더 옆)에 있는 하나의 파일 config.php에 일반 상수 define()로 지정됩니다. 파일은 설치 시 생성되며, 수동으로 편집할 일은 드뭅니다 — 주로 도메인 변경, 이전, 다른 데이터베이스 연결 시에만 필요합니다. 편집 후에는 반드시 PHP-FPM을 재시작하세요(그렇지 않으면 OPcache 때문에 변경 사항이 적용되지 않습니다).
강조 표시된 곳에 자신의 값을 넣고, 나머지는 그대로 두세요:
데이터베이스. MySQL/MariaDB 연결 정보:
DB_HOST — DBMS 호스트, 거의 항상 localhost;DB_NAME — 패널 데이터베이스 이름;DB_USER — DB 사용자(자신의 데이터베이스에만 접근);DB_PASS — 해당 사용자의 비밀번호;DB_CHARSET — 연결 인코딩, utf8mb4로 그대로 두세요.애플리케이션.
APP_URL — 패널의 전체 주소(예: https://monitor.example.com). 라이선스가 활성화된 도메인과 일치해야 하며, 그렇지 않으면 키가 거부됩니다(“라이선스” 섹션 참조);TIMEZONE — PHP 시간대: 패널이 날짜와 시간을 표시하는 방식에만 영향을 줍니다. cron 작업 실행 시간에는 영향을 주지 않으며, 거기서는 시스템 시간대가 적용됩니다(“모든 cron 작업” 참조).세션 시간. SESSION_LIFETIME — 세션 유휴 타임아웃(초 단위, 슬라이딩 방식: 활동 시 갱신). 기본값은 28800 = 8시간이며, 이 시간 동안 활동이 없으면 패널이 재로그인을 요청합니다. 예를 들어 3600 = 1시간, 86400 = 하루입니다.
오류 로깅. 오류는 방문자에게 절대 표시되지 않고 logs/php_errors.log에 기록됩니다 — “애플리케이션 로그” 페이지에서 확인할 수 있습니다. 이 줄들(display_errors=0, log_errors=1, error_log 경로)은 보통 변경할 필요가 없습니다 — 설정은 파일에 직접 지정되어 있으며 php.ini에 의존하지 않습니다.
public/ 옆)에 있으며, 이 패널의 웹 루트(DocumentRoot)는 바로 패널 루트이지 public/가 아닙니다. 파일 자체가 “유출”되지는 않습니다: 루트의 .htaccess에 이 파일에 대한 명시적 거부(Require all denied)가 설정되어 있어 서버가 403을 반환합니다. 이 규칙이 없더라도 소스는 유출되지 않습니다: PHP이므로 서버가 텍스트로 내보내지 않고 실행합니다. 만약을 위해: 공개 저장소에 올리지 말고 실제 비밀번호가 담긴 채로 지원팀에 전달하지 마세요. 파일 권한은 640입니다.
UFW(Uncomplicated Firewall)는 nftables/iptables를 위한 간단한 인터페이스입니다. 명시적으로 허용한 포트를 제외한 모든 수신 포트를 차단합니다. “UFW 방화벽” 페이지에서 상태와 규칙을 확인할 수 있습니다.
ufw enable 전에 반드시 SSH를 허용하세요(ufw allow OpenSSH). 그렇지 않으면 서버 접근을 잃게 됩니다.
deny 규칙으로 차단된 포트는 외부에서 접근 가능한 것으로 간주되지 않습니다.
Skipping adding existing rule은 오류가 아닙니다. UFW가 동일한 규칙이 이미 존재하여 다시 추가하지 않는다는 것을 알리는 메시지입니다. 자동 설정을 다시 실행하면(멱등성이 있음) 나타나는 정상적인 메시지이므로 대응할 필요가 없습니다.
로그인 실패 횟수가 초과되면 IP를 자동으로 차단합니다. SSH, nginx, Apache 등 여러 서비스의 로그를 분석합니다.
기본 설치는 위에서 설명했습니다. 여기서는 수십 개의 활성 jail과 수천 건의 차단을 만들어내는 실전 구성을 다룹니다: 공통 설정, 핵심 jail, 그리고 ipsum 목록의 악성 IP 자동 차단입니다.
/etc/fail2ban/jail.local 파일 — 공통 설정과 핵심 jail:
ignoreip에 반드시 본인 IP와 신뢰하는 네트워크를 넣으세요. 그러지 않으면 자기 자신을 차단할 수 있습니다. 수정 후: sudo fail2ban-client reload.
ipsum 차단 목록 자동 로드는 root 크론(sudo crontab -e)에 등록합니다: level 1(IP 10만+ 개)이 ipsum 세트에 로드되고, 이 세트는 방화벽에서 차단됩니다(자세한 내용은 “IPset 차단 목록” 섹션 참고):
ipsum이어야 합니다 — 대시보드(“IPset ipsum” 카드)가 바로 이 이름을 읽습니다. 레벨: levels/1.txt는 최대 범위, levels/3.txt는 더 정확함(출처 3개 이상).
“보안 모니터”가 두 영역으로 나뉜 이유. 방어는 두 단계로 작동하며, 대시보드는 이를 섞지 않습니다:
sshd, apache-*, nginx-* 등)와 악성 재범자(jail recidive — 이미 여러 번 차단된 대상). 이들은 실제로 침입을 시도한 IP이며, 공격 지도와 “타임라인”에 표시됩니다.ipset ipsum으로, DROP 규칙에 의해 방화벽에서 차단됩니다. 이 주소들은 대부분 서버에 접근조차 하지 않았지만 사전에 차단됩니다. “IPset ipsum” 카운터는 예방적으로 차단된 건수를 보여줍니다.차이는 간단합니다: 대응형은 “공격해서 차단당한 대상”, 선제형은 “시도하기도 전에 차단된 대상”입니다. 예전에는 recidive에 ipsum list-3를 인위적으로 밀어 넣었습니다(여기서 예전 구분인 “목록 기반 recidive”가 나왔습니다). 이제 recidive는 실제 재범자만 담고, 선제형 차단은 전부 방화벽에서 처리합니다.
ipsum — 매일 갱신되는 공개 악성 IP 목록입니다. 모니터는 대시보드와 공격 지도에 로드된 주소 수를 표시하며, 보안 점수에도 반영합니다(세트가 로드되지 않으면 −10).
fail2ban 없이 쓰는 최소 구성 — iptables로 차단하는 별도 세트 ipsum:
@reboot에 걸어 두세요. 이때 create … -exist 명령이 maxelem 300000 한도를 지정합니다(기본값 65536으로는 level 1이 들어가지 않아 “Hash is full”이 발생합니다):
ipsum 세트에 로드하며, 방화벽을 설치 프로그램이 관리하는 경우(새 VPS — “전체”/“경량” 프로필) DROP 규칙으로 세트를 UFW에 연결합니다 — 해당 IP의 트래픽이 실제로 차단됩니다. 규칙은 ESTABLISHED,RELATED 뒤에 위치하므로 현재 연결(SSH 포함)은 끊기지 않고, 목록에 있는 새 연결만 차단됩니다. 세트는 ipsum-load.service 서비스가 방화벽보다 먼저 로드될 때 복원되며(그렇지 않으면 UFW가 올라오지 못합니다), 04:00 크론으로 갱신됩니다. 이미 구성된 서버(패널, 자체 방화벽)에서는 설치 프로그램이 방화벽을 건드리지 않으며, 이때 ipsum은 대시보드와 공격 지도용 목록으로 남고 DROP 규칙은 원하면 수동으로 추가합니다(iptables … --match-set ipsum … -j DROP 최소 구성은 위 참고). 자동 설치에서는 수동으로 할 일이 없습니다.
집단 위협 인텔리전스를 갖춘 최신 Fail2ban 대체 도구입니다. 커뮤니티의 차단 정보와 자체 규칙을 함께 사용합니다. 방화벽에 차단을 적용하려면 별도의 bouncer가 필요합니다.
systemctl is-active crowdsec로 확인). 시작하려면 sudo systemctl enable --now crowdsec, 실패 시 sudo journalctl -u crowdsec -n 30을 확인하세요. “실행 안 됨” 상태의 다른 서비스(Suricata, Falco, Monit, MySQL)에도 동일하게 적용됩니다.
stream halted / 차단이 적용되지 않음. 고아 상태가 된 api 키 문제입니다. bouncer가 cscli bouncers list에서 삭제되었지만 오래된 키가 /etc/crowdsec/bouncers/*.yaml에 남아 있습니다. bouncer를 다시 등록하고 새 키를 기입하세요:
AIDE (Advanced Intrusion Detection Environment)는 파일 시스템의 스냅샷을 만들어 검사할 때마다 /etc, /bin, /usr의 변경 사항을 알려줍니다. 설치 후에는 데이터베이스 초기화(aideinit)가 필수입니다.
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 설정 스니펫에서 알려진 버그입니다. 데이터베이스가 생성되지 않습니다. 문제가 있는 스니펫을 옮기고 다시 시도하세요:
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 모드로 로그를 기록하므로 모니터가 추가 그룹 없이 읽을 수 있습니다:
chmod 755 /var/lib/aide와 02:00 검사 크론 — 수동으로 할 것이 없습니다.
sudo aideinit && sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db.
Linux용 바이러스 검사기입니다. 특히 /var/www의 PHP 셸과 악성 코드를 검사하는 데 유용합니다.
enable --now 이후에도 “비활성”으로 표시되나요? 일반적인 원인은 세 가지입니다:
1. 설정 파일에 Example 줄이 남아 있음 — 이 줄이 있는 한 clamd는 시작을 거부합니다:
2. 시그니처 데이터베이스가 다운로드되지 않음 — 이것 없이는 clamd가 시작되지 않습니다:
3. 그저 로딩 중 — clamd는 약 800만 개의 시그니처를 30~60초에 걸쳐 메모리에 올립니다. 잠시 기다린 뒤 확인하세요: systemctl is-active clamav-daemon (상태가 activating이면 → 아직 로딩 중).
sudo journalctl -u clamav-daemon -n 30 --no-pager.
clamd 데몬은 시그니처를 메모리에만 유지할 뿐, 자체적으로 예약 검사를 수행하지는 않습니다. 대시보드는 예약 검사 결과를 표시하므로, 검사를 실행하고 로그를 기록하는 크론이 필요합니다. 자동 설치는 래퍼 /usr/local/bin/clamav-scan.sh와 01:30 크론을 설정합니다 — 첫 실행 후 “검사한 파일”과 “마지막 검사”가 채워집니다. 예약 시각을 기다리지 않고 바로 실행하려면: sudo /usr/local/bin/clamav-scan.sh.
Linux Malware Detect(LMD)는 PHP 셸, 웹 백도어, 다운로더 등 웹 위협을 겨냥한 악성코드 스캐너입니다. ClamAV 엔진을 사용하며 자체 시그니처로 이를 보완합니다.
maldet --report.
update-rc.d: error: unable to read /etc/init.d/maldet 줄이 나타날 수 있는데, 이는 무해합니다. maldet은 init.d를 사용하지 않으며, 시그니처 업데이트와 스캔은 /etc/cron.daily/maldet를 통해 실행됩니다. 아래에 installation completed가 보이면 모두 설치된 것입니다.
apt가 아니라 /usr/local/maldetect에 설치되며, open_basedir가 켜져 있으면 shell을 통해 설치 여부를 확인합니다. "데이터는 서버에 있는데 페이지가 비어 있음" 섹션을 참고하세요.
네트워크 침입 탐지 시스템: 패킷 수준에서 트래픽을 분석하며 수천 개의 공격 시그니처를 알고 있습니다. ModSecurity를 보완합니다(ModSecurity는 HTTP 수준에서, Suricata는 TCP/IP 수준에서 동작).
/var/log/suricata/eve.json을 root 권한, 디렉터리 750 모드로 기록하므로 웹 서버(www-data)가 읽을 수 없습니다. 디렉터리에 진입 권한을 열어 주세요 — 내부 파일은 계속 보호됩니다:
eBPF/커널 모듈을 통해 시스템 호출을 가로채 실시간으로 이상 징후를 탐지합니다: nginx에서 실행된 shell, 웹 프로세스의 /etc/passwd 읽기, /bin 쓰기 등.
journalctl -u falco로 Falco 이벤트를 읽습니다(sudo 없이 systemd-journal 그룹을 통해). www-data가 이 그룹에 속해 있는지 확인하세요 — 수동 설치 페이지의 “sudo 설정”(2항) 참조.
/etc/passwd 읽기, 시스템 디렉터리 쓰기). 조용한 서버에서 하루 동안 심각한 이벤트가 0건이라면 건강한 상태입니다.
journalctl 읽기는 저널 권한이 필요합니다. 대시보드가 이벤트를 안정적으로 보도록, 자동 설치는 Falco의 file_output → /var/log/falco/falco.log를 켜고 서비스에 UMask=0022를 설정합니다(로그를 웹 서버가 읽음). 새로 설치한 경우 수동으로 설정할 필요는 없습니다.
ModSecurity — Apache 또는 Nginx용 웹 방화벽(WAF)입니다. SQL 인젝션, XSS, 경로 우회, 스캐너 등 애플리케이션 계층 공격을 차단합니다.
IncludeOptional /etc/modsecurity/*.conf 줄로 설정을 불러오지만, 패키지는 modsecurity.conf-recommended만 넣어 두므로 *.conf 마스크에 해당하지 않습니다. 이 파일을 modsecurity.conf로 복사하지 않으면 SecRuleEngine은 Off로 남습니다. 모듈은 로드되고 CRS 규칙도 로드되지만, 트래픽은 검사되지 않고 감사 로그도 생성되지 않습니다. 중간 모드 DetectionOnly는 요청을 차단하지 않고 이벤트만 로그에 기록하며 — 대시보드는 이를 노란색으로 표시합니다.
대시보드의 감사 로그 접근. 로그 /var/log/apache2/modsec_audit.log는 root 소유(권한 640)이므로 웹 사용자는 읽을 수 없습니다. 대시보드는 래퍼를 통해 데이터를 가져오니 — 다음과 같이 생성하세요:
SecRuleEngine 지시문을 가져옵니다. 들여쓰기된 줄은 <LocationMatch>/<Directory> 블록(예: phpMyAdmin용 WAF 비활성화) 안에 있어 전역 모드를 결정하지 않습니다.www-data이고, HestiaCP에서는 사이트 풀이 사이트 소유자(예: admin)로 실행되니 — grep -E '^user' /etc/php/*/fpm/pool.d/monitor.example.com.conf로 확인하세요.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 명령으로 실제 경로를 찾아 목록에 넣으세요. 래퍼가 오래되어 이 섹션이 없으면 — 해당 섹션은 “사용 불가” 경고만 표시하고, 나머지 페이지는 이전과 동일하게 작동합니다.
Auditd(Linux Audit Daemon)는 커널 수준에서 시스템 호출을 기록합니다: 로그인 및 로그아웃, sudo 명령, 인증 실패 시도, 파일 변경. 모니터는 오늘의 로그인, 실패한 시도, sudo 명령을 표시합니다.
ausearch(/usr/sbin/ausearch)로, 필요한 경우 tail 명령으로 /var/log/audit/audit.log에서 이벤트를 읽습니다. 둘 다 sudoers에 등록되어야 합니다.
서비스(nginx, php-fpm, mysql 등)를 감시하고 중단되면 재시작합니다. 이메일로 알림을 보낼 수 있습니다.
monit status를 통해 서비스 목록을 가져옵니다. /etc/monit/monitrc에서 HTTP 인터페이스가 활성화되어 있어야 하며(allow localhost가 포함된 set httpd 블록), 그렇지 않으면 monit status가 오류를 반환합니다.
monitrc의 set httpd 줄이 주석 처리되어 있습니다(기본값은 # set httpd port 2812 …). 블록의 주석을 해제하고 localhost를 허용하세요. (2) httpd를 켠 것만으로는 아무것도 감시하지 않습니다 — Monit은 check 구문에 정의된 것만 계산하며, 이것이 없으면 인터페이스가 작동해도 목록은 비어 있습니다. 최소 작동 설정:
conf.d를 바로 배치합니다 — 새로 설치한 경우 수동으로 설정할 필요가 없습니다.
PSAD는 iptables 로그를 분석하여 포트 스캔과 네트워크 공격을 탐지하고, 각 출처에 위협 수준(1~5)을 부여합니다. fail2ban 및 Suricata를 보완합니다.
psad --Status를 통해 데이터를 읽습니다(sudoers에 등록 필요). iptables 로깅이 없으면 페이지가 비어 있습니다 — 스캔이 없었다면 정상입니다.
Mandatory Access Control은 프로그램이 침해되더라도 접근할 수 있는 파일과 리소스를 제한합니다. Ubuntu/Debian에서는 기본적으로 AppArmor를 사용합니다(보통 이미 설치되어 활성화되어 있습니다).
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)에는 이 모드가 없어 숫자가 항상 일치합니다.
unconfined로 남겨둔 프로파일을 enforce로 전환하는 것은 신중하게 판단해야 합니다. 실수로 꺼둔 것이 아니라, 켜면 프로그램 자체의 동작이 깨지기 때문입니다. complain 모드의 프로파일은 다릅니다. 규칙이 이미 작성되어 있고 단지 적용되지 않을 뿐입니다.
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/에 보고서를 기록합니다.
래퍼 debsums-scan.sh는 패널의 data/를 스스로 찾으므로 경로를 지정할 필요가 없습니다.
/etc/(설정)와 /usr/share/(리소스)의 변경은 대체로 정상이며, 패널은 이를 별도 색상으로 표시합니다. 위험한 것은 바이너리와 라이브러리의 변경(/bin, /sbin, /usr/lib 등)으로, “바이너리 / 라이브러리” 카드가 바로 이것들을 보여줍니다.
Lynis는 수동으로 또는 cron으로 실행됩니다. 보고서는 프로젝트의 data/lynis/ 폴더에 저장되어야 하며, 모니터는 lynis-report.dat 파일을 읽습니다.
lynis-scan.sh를 백그라운드로 실행합니다. “스캔 중…”을 표시하고 완료되면 보고서를 자동으로 갱신합니다. 이를 위해 웹 사용자에게 스크립트 실행용 sudoers 항목이 필요하며, 설치 프로그램이 /etc/sudoers.d/monitor에 자동으로 추가합니다. 패널을 수동으로/이전에 설치했다면, 파일에 이미 지정된 동일한 사용자로 항목을 추가하세요:
Logwatch는 매일 생성되는 보고서를 프로젝트의 data/logwatch/ 폴더에 .txt 형식으로 저장해야 합니다. 모니터는 최신 보고서와 보관된 기록을 표시합니다.
네트워크 모니터는 설치가 필요 없습니다 — 대시보드에 내장된 페이지입니다. 로컬 소스에서 서버의 네트워크 상태를 보여줍니다:
/proc/net/dev에서;ip를 통해;ss를 통해;journalctl -k를 통해.앞의 세 소스는 sudo 없이 동작하므로 인터페이스, 트래픽, 연결, 포트는 즉시 표시됩니다. “커널 이벤트” 블록은 journalctl -k를 사용하며, systemd-journal 그룹을 통해 읽습니다(“sudo 설정”, 2번 항목), sudo는 필요 없습니다. 웹 사용자에게 모두 접근 가능한지 확인:
UFW BLOCK 기록은 여기에 포함되지 않습니다 — “UFW 방화벽”과 “공격 지도” 페이지에 있습니다. 초록색 체크와 함께 비어 있는 블록 = 하루 동안 네트워크 장애가 없었음.
내장 페이지는 세 가지를 보여줍니다:
df); ≥90%면 막대가 빨갛게 표시됩니다;lsblk), 실제 장치만(loop/snap는 숨김);smartctl).용량과 장치 목록은 설정 없이 바로 작동합니다. SMART에는 smartmontools 패키지가 필요합니다. 웹 프로세스는 디스크 장치에 직접 접근할 수 없으므로, SMART는 cron으로 data/disk/smart.txt 파일에 기록되고 대시보드가 이를 읽습니다.
작업은 root 크론에 등록합니다(sudo crontab -e). 준비된 래퍼 smart-scan.sh는 /usr/local/bin/에 넣으면 되며(chmod +x; cron 작업 요약 참고), 대시보드의 data/disk/에 직접 기록합니다.
래퍼 smart-scan.sh는 대시보드의 data/를 스스로 찾으므로 경로를 지정할 필요가 없습니다. 내부적으로 lsblk -e7,11이 loop/cdrom을 제외합니다.
이 페이지는 최근 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시간이 지난 데이터 지점은 기록할 때마다 자동으로 삭제됩니다.
래퍼 collect-metrics-all.sh(cron 작업 요약 참조)는 서버에 설치된 모든 패널 인스턴스를 스스로 찾아 각각의 cron/collect_metrics.php를 사이트 소유자 권한으로 실행합니다.
부하 알림(“설정” → “부하 알림” 섹션) — CPU/RAM/디스크/inode 임계값을 초과하면 패널이 Telegram/Email로 알림을 보내고(일일 보고서와 같은 채널이므로 알림용으로 따로 켤 필요 없음), 지표가 정상으로 돌아오면 다시 하나를 보냅니다. 임계값이 유지되는 동안 반복 스팸은 하지 않습니다: 다음 알림은 “복구됨 → 다시 초과” 주기 이후에만 옵니다.
collect_metrics.php가 실행될 때마다(5분마다) 확인합니다 — 별도의 cron은 필요 없습니다. “이미 알림함 / 아직 아님” 상태는 data/alerts_state.json에, 임계값은 패널 설정에 저장됩니다.
“공격 지도” 페이지는 geoiplookup 명령으로 IP의 국가를 확인합니다. GeoIP 패키지가 없으면 국가가 확인되지 않아 지도에 점이 표시되지 않습니다:
/usr/share/GeoIP/GeoIP.dat 데이터베이스는 누구나 읽을 수 있고 결과는 tmp/geoip_cache.json에 캐시됩니다. 지도 자체(Leaflet + OpenStreetMap 타일)는 브라우저에서 로드되므로 대시보드를 연 컴퓨터에 인터넷이 필요합니다.
도구의 “켜짐/꺼짐”이 아니라 서버의 실제 보안 상태를 보여주는 두 개의 내장 대시보드 카드입니다. 설치가 필요 없고 sudo 없이 로컬에서 읽습니다.
외부 노출 — 모든 인터페이스(0.0.0.0/[::])를 수신하며 외부에서 접근 가능한 서비스의 수입니다. DBMS나 캐시(MySQL, PostgreSQL, Redis, MongoDB, Memcached, Elasticsearch)가 외부로 드러나 있으면 빨간색으로 강조합니다 — 이는 직접적인 구멍입니다(보안 점수 −10). 출처: ss -tuln.
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 서비스는 포함하지 않습니다.
/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 파일. 상세 목록은 “보안 업데이트” 페이지에 있습니다.
update-notifier-common)에서 작동합니다. apt-check가 없으면 모니터는 apt-get -s upgrade로 패치를 계산합니다.
보안 자동 업데이트(unattended-upgrades) — “보안 업데이트” 페이지의 별도 카드에서 보안 패치 자동 설치가 켜져 있는지, 마지막으로 언제 실행되었는지 보여줍니다. sudo가 필요 없으며 상태는 apt-config dump로 읽습니다.
백업은 가장 확실한 안전장치입니다. 데이터 손실은 어떤 침해보다 치명적입니다. 두 가지가 필요합니다 — 서버/사이트 백업과 별도의 패널 DB 백업(사용자, WebAuthn 키, 설정, 라이선스가 들어 있습니다).
방법 A — HestiaCP: 사용자의 Backup 탭 → 백업 생성 버튼(또는 서버 설정의 예약 실행). 백업에는 사이트와 해당 DB가 포함됩니다.
방법 B — 수동(cron): DB 덤프 + 패널의 data/ 디렉터리 아카이브:
새 버전으로 업데이트. 먼저 백업을 하세요. 그런 다음 데이터를 유지한 채 코드 파일을 다시 업로드하세요:
public/, includes/, assets/, cron/, database/, 그리고 루트의 .htaccess(프런트 컨트롤러 — 라우팅을 이전 버전 것으로 두면 안 됩니다), manifest.json, sw.js;config.php(DB 정보), data/(보고서), logs/, tmp/(세션 및 캐시).SSH_FX_PERMISSION_DENIED — Permission denied라고 표시합니다. 패널 파일의 소유자는 www-data이고(설치 시 그렇게 설정됨), SFTP 클라이언트는 쓰기 권한이 없는 본인 계정으로 접속합니다. 패널 전체를 www-data에 넘겨 “작동하게” 만드는 것이 바로 이 오류를 일으키는 원인입니다; 아래 세 가지 방법 중 어느 것이든 문제를 해결합니다.
data/(보고서), tmp/(세션 및 캐시), logs/; 이들은 www-data 소유로 남습니다. 나머지는 코드이며, 웹 서버는 읽기 권한만 필요한데 이는 644 권한의 www-data 그룹으로 제공됩니다. 부수적 이점: PHP 취약점이 있어도 패널 파일을 덮어쓸 수 없습니다. 호스팅 패널(HestiaCP 등)에서는 방법 A가 필요 없습니다: 거기서는 사이트 파일이 이미 SFTP로 접속하는 계정 소유이고, 웹 서버는 그룹으로 파일을 읽습니다.
chmod를 실행하면 ACL 마스크가 초기화되어 접근 권한이 조용히 사라집니다. “권한 정리” 후 업로드가 다시 Permission denied에 막히면 — 두 setfacl 명령을 다시 실행하세요.
2 비트가 setgid입니다: SFTP로 올린 파일이 www-data 그룹에 남으며, 그렇지 않으면 패널이 덮어쓸 수 없습니다. 방법 C 이후에는 FileZilla에서 다시 연결하세요 — 새 그룹은 새로 로그인할 때만 적용됩니다. 확인: id deploy(www-data 그룹이 나타나야 함) 및 ls -ld /path/to/monitor(drwxrwsr-x — s는 setgid가 설정되었다는 뜻).
다른 서버로 이전:
config.php, data/와 함께 복사하세요.mysqldump → 새 서버로 가져오기; config.php의 DB 정보를 수정하세요.adm 그룹 소속, cron 작업.로그인할 수 없다면 서버의 DB에서 직접 해결할 수 있습니다. DB를 여세요(이름은 config.php에서 확인):
WebAuthn 보안 키 분실(2차 인증 통과 불가) — 2FA를 끄고 비밀번호로 로그인한 뒤 새 키를 등록하세요:
비밀번호 분실 — 새 해시를 설정하세요(서버에서 생성해 넣으세요):
IP 필터로 본인을 차단함 — 제한을 끄세요:
sudo mysql, 또는 phpMyAdmin / 호스팅 패널의 DB 섹션. 복구 후에는 WebAuthn과 IP 필터를 다시 켜세요.
작업 모음은 서버의 root cron에 있습니다(추가는 sudo crontab -e로). 사용하는 도구의 줄만 남기고, 경로는 자신의 서버에 맞게 수정하세요.
sudo crontab -l 및 cron 서비스가 활성 상태인지.
config.php의 TIMEZONE이 아닙니다. TIMEZONE 상수는 PHP에만 영향(패널이 날짜를 표시하는 방식)을 주고, cron 데몬은 OS 시스템 시간으로 작업을 실행합니다. 서버 시간대가 여러분의 것과 다르면 “08:00” 보고서가 엉뚱한 시각에 옵니다. 예: 서버는 다른 시간대(Asia/Shanghai, UTC+8)이고 여러분은 서울(UTC+9) → “08:00” 보고서가 여러분 기준 09:00에 옵니다. 확인하고 필요하면 시스템 시간대를 자신의 것으로 맞추세요:
0 8 * * * 줄이 현지 시간 08:00에 실행됩니다. 그렇지 않으면 cron 자체를 옮겨야 하는데, 겨울/여름 시간 전환 때 다시 어긋나므로 시스템 시간대를 설정하는 편이 옳습니다.
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로 입력하세요./usr/local/bin/에 넣는 방법. FileZilla에서 그곳에 직접 쓸 수 없습니다 — 디렉터리가 root 소유라 SFTP 클라이언트는 SSH_FX_PERMISSION_DENIED를 받습니다. 순서는 이렇습니다: 먼저 파일을 /tmp에 올리고(그곳은 누구나 쓸 수 있음), 명령 한 줄로 제자리로 옮깁니다:
/tmp가 필요합니다 — /var/tmp나 패널 내부의 tmp/가 아닙니다(후자는 www-data 소유이며 여러분 사용자에게는 닫혀 있음). FileZilla 트리에서 /tmp는 var 옆의 최상위 가지이며, 그 안에 있지 않습니다.
/usr/local/bin/에, 로그는 /var/log/arciveo-cron.log) — 수동으로 할 일은 없습니다.
/home/*/web/*/public_html과 /var/www/*를 훑어 패널 설치를 찾고, 보고서를 각각의 data/에 넣습니다. 패널이 다른 경로에 있으면 스크립트 안 for app in … 줄에 추가하세요. 그렇지 않으면 Lynis/SMART/debsums/Logwatch 보고서가 패널에 들어오지 않습니다.
logs/cron.log 파일은 root cron이 처음 만들면 root 소유가 되어, 패널의 “cron 로그” 탭이 이를 읽거나 비울 수 없습니다. 파일을 미리 웹 사용자 이름으로 만드세요(사이트 디렉터리 소유자; HestiaCP에서는 예: admin 계정) — 그러면 root cron은 소유자를 바꾸지 않고 덧붙이기만 합니다:
stat -c %U /path/to/monitor.
sudo crontab -e). 그렇지 않으면 두 번 실행됩니다.
sudo crontab이 아니라(그것은 패널 세션 접근권을 얻은 누구든 root로 직접 상승시키게 됨), 서비스 주석 사이의 자기 블록만 건드리는 두 명령(list/set)의 좁은 스크립트입니다. 한 번만 설치하세요:
www-data와 다를 수 있습니다 — 사이트의 PHP-FPM 풀이 어느 사용자로 동작하는지 확인하고(ps -o user= -C php-fpm) sudoers 줄에 넣으세요.
public/crontab_monitor.php 파일이 사이트의 다른 파일과 다른 시스템 사용자(예: root)로 FTP/SFTP로 업로드되면 웹 서버가 이를 읽을 수 없습니다. 옆 파일과 소유자·권한을 비교해 맞추세요:
모니터는 APT 패키지 데이터베이스인 dpkg-query를 통해 도구 설치 여부를 확인합니다. apt를 거치지 않고 설치한 경우(수동, snap, 소스 빌드) dpkg는 이를 인식하지 못합니다.
500 오류 — PHP, nginx, 모니터 자체의 로그를 확인하세요:
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 aa-status는 프로필을 보여주지만 “AppArmor” 페이지는 “비활성”). 원인: 웹 사용자에게 이 모듈 명령에 대한 sudo 권한이 없기 때문입니다. 위 목록에서 해당 명령을 확인하세요. 비밀번호를 요구하면 /etc/sudoers.d/monitor에 빠진 줄을 추가하세요(“sudo 설정”). 자주 나오는 “새” 명령: /usr/sbin/aa-status(MAC), /usr/sbin/psad --Status(PSAD).
apache2ctl, ausearch, aa-status, ss가 허용되지 않았거나, 웹 사용자가 adm/systemd-journal 그룹에 없을 것입니다(여기에서 fail2ban/auth/modsec 로그와 journalctl — Falco 및 커널 이벤트를 읽습니다).
증상: 서버에는 데이터가 있는데(shell로는 보임) 페이지에는 "데이터 없음" 또는 잘못된 상태가 표시됨 — 예를 들어 데이터베이스가 생성되었는데도 AIDE가 "초기화되지 않음"이라고 표시함.
원인은 open_basedir입니다. 많은 패널과 호스팅이 PHP-FPM 풀을 도메인 디렉터리로 제한하므로, 시스템 경로(/var/lib/aide, /var/log, /proc…)에 대한 PHP 함수 file_exists(), file_get_contents(), filemtime()가 차단됩니다. 모니터는 표준 시스템 명령(cat, test, stat)으로 이러한 경로를 읽어 이를 우회합니다.
open_basedir 문제입니다. 올바른 해결책은 시스템 명령으로 읽는 것입니다(AIDE와 네트워크 모니터에는 이미 적용됨). open_basedir를 /var, /proc로 확장할 필요는 없으며 보안상 좋지 않습니다.
모니터는 443 포트를 통해 도메인에 직접 연결하여 인증서를 확인합니다. 서버 자체에서 도메인에 접근할 수 없거나 포트가 방화벽에 막혀 있으면 확인이 실패합니다.
/etc/nginx/sites-enabled/, /etc/nginx/conf.d/)과 Apache 설정(/etc/apache2/sites-enabled/), 그리고 HTTP_HOST의 현재 호스트에서 도메인을 자동으로 가져옵니다.
모니터는 config.php에 설정된 사용자로 MySQL에 접속하며, 이 사용자는 자기 데이터베이스에만 접근할 수 있습니다. MySQL은 information_schema에 권한이 있는 데이터베이스만 표시하므로 나머지는 보이지 않습니다.
모니터가 모든 DB를 볼 수 있게 하려면 이 사용자에게 읽기 전용 권한을 부여하세요(root로 한 번만 실행하고, config.php의 사용자 이름을 넣으세요):
sudo mysql을 전혀 사용하지 않으며, 데이터베이스 목록은 자체 PDO 연결을 통해 가져옵니다.
PostgreSQL은 postgres 사용자 수준의 접근 권한이 필요하지만, 패널의 웹 사용자에게는 이 권한이 없습니다. PHP에서 광범위한 sudo psql을 여는 것은 안전하지 않으므로, 대신 패널은 매개변수 없이 버전, 연결 수, 크기가 포함된 데이터베이스 목록만 출력하는 좁은 범위의 래퍼를 호출합니다. 다음과 같이 생성하세요:
monitor-pgstat 줄을 제거하고(수동 설치 13단계) 스크립트 자체도 생성하지 마세요: PostgreSQL 카드가 그냥 비활성 상태로 남게 됩니다.
대시보드는 무엇이 일어나는지 보여줍니다. 아래는 일반적인 상황에서 무엇을 해야 하는지 안내합니다. 기본 원칙은 당황하지 말고, 정상 활동(본인의 작업, 업데이트, 백업)과 대조한 뒤 심각도에 따라 대응하는 것입니다.
ignoreip에 있는지 확인하세요./etc 외부, /usr/share 외부) — 변조 가능성이 있습니다. 패키지를 대조하세요: debsums PACKAGE_NAME, 의심되면 재설치하세요(apt install --reinstall).127.0.0.1에 바인딩하거나 UFW에서 포트를 닫으세요. 실제 보안 구멍입니다.certbot renew 또는 대시보드 설정을 확인하세요).sudo apt update && sudo apt upgrade; 커널 업데이트 후에는 서버를 재부팅하세요.