Arcivéo Security Monitor의 설치, 설정, 유지 관리 안내서입니다. 섹션은 전체 개요, 패널 배포, 보안 도구 연결, 내장 모듈, 진단으로 그룹화되어 있습니다. 명령어는 오른쪽 버튼으로 복사할 수 있습니다.
패널 설치는 단계별 안내 페이지에 있습니다. 방식을 선택하세요:
Arcivéo Security Monitor는 서버 보안 대시보드입니다. 설치된 도구(Fail2ban, UFW, Lynis, ModSecurity, AIDE, ClamAV, Auditd, CrowdSec, Suricata, Falco 등)에서 데이터를 수집해 대시보드, 공격 지도, 도구별 상세 페이지를 갖춘 단일 인터페이스에 표시합니다.
Monitor는 능동적 방어 수단이 아니며 스스로 공격을 차단하지 않습니다. 이미 작동 중인 도구의 정보를 취합해 보기 쉽게 제공하는 것이 목적입니다.
모니터는 로컬에서만 작동합니다. 즉, 모니터링 대상 서버와 동일한 서버에 설치해야 합니다. SSH나 원격 API는 사용하지 않습니다.
모든 명령(fail2ban-client, ufw status, ipset list 등)은 웹 서버 사용자(보통 www-data, 호스팅 패널에서는 사이트 계정) 권한으로 실행되며, sudo의 제한된 권한 집합, 즉 특정 유틸리티에만 허용되고 전체 root 접근은 없는 상태로 실행됩니다. 결과는 파싱되어 브라우저에 표시됩니다.
두 가지 리듬. 패널의 각 페이지는 열리는 순간 도구에 상태를 물어봅니다. 그래서 언제나 현재 상태가 보입니다. 그와 별개로 예약된 작업(cron)이 백그라운드에서 돌아갑니다.
작업의 전체 목록은 “모든 cron 작업을 한곳에서” 항목에 있습니다.
점수는 최댓값에서 시작하며 발견된 문제마다 차감됩니다:
PermitRootLogin yes) — −20PasswordAuthentication yes) — −10X11Forwarding 켜짐, MaxAuthTries가 3 초과, 또는 모든 계정이 sshd_config를 읽을 수 있음 — −5TRACE 메서드 켜짐, 또는 Strict-Transport-Security나 X-Frame-Options 헤더 없음 — −5결과: 80+ = 보호됨, 60–79 = 주의, <60 = 위험.
WebAuthn은 하드웨어 키를 이용한 비밀번호 없는 인증 표준입니다. YubiKey, Touch ID, Face ID, Windows Hello, Passkey를 지원합니다.
비밀번호로 로그인한 후 시스템이 등록된 키로 확인을 요청합니다. 비밀번호가 유출되더라도 물리적 키나 생체 인증 없이는 로그인할 수 없습니다.
설정하려면 측면 메뉴에서 WebAuthn 키를 열고 “키 등록”을 누르세요. 키를 두 개 등록해 두세요. 유일한 키를 분실하거나 고장 나면 그 키로는 대시보드에 로그인할 수 없습니다.
채널은 네 가지입니다. Telegram, Email, webhook, 그리고 브라우저로 오는 푸시입니다. 서로 독립적이므로 원하는 것만 켜도 되고 네 가지를 모두 켜도 됩니다. 매일의 보안 보고서, 부하 알림, 이벤트 센서가 찾아낸 내용도 모두 같은 채널로 나갑니다. 설정은 여기 “설정”에서 한 번만 하면 됩니다.
Telegram. 봇 토큰과 chat id가 필요합니다:
@BotFather에게 /newbot을 보내면 123456:ABC... 형식의 토큰을 받습니다.@userinfobot에게 메시지를 보내거나 https://api.telegram.org/bot<TOKEN>/getUpdates를 열어 "chat":{"id":...}를 찾습니다.Email. “설정” → Email에서 두 가지 방식 중 선택:
re_...)와 인증된 발신 도메인을 입력합니다.Webhook. POST를 받는 주소 하나면 Slack, Discord, Mattermost, n8n은 물론 직접 만든 처리기까지 모두 감당합니다. “설정” → Webhook: 주소를 붙여 넣고 스위치를 켜십시오.
{"text": …}가, Discord에는 {"content": …}가 전송됩니다 (Discord는 2000자를 넘는 메시지를 거부하므로 본문이 잘립니다);text, event, severity, source, count, host, ts — 메시지 본문을 다시 파싱할 필요가 없습니다.푸시 알림. 메시지가 브라우저로 바로 도착하고, 패널을 휴대폰 홈 화면에 추가해 두었다면 평범한 앱 알림처럼 뜹니다. 중간에 봇도 메일 서버도 필요 없습니다. “설정” → 푸시 알림: 스위치를 켜고, 알림을 받아야 하는 기기마다 “이 기기 연결”을 누르십시오. “연결된 기기” 카운터가 몇 대인지 보여 줍니다.
보고서 상태: “주의” 또는 “정상”. 제목은 실제 문제나 대기 중인 작업이 있을 때만 “주의”가 됩니다: 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입니다.
패널은 비밀번호 로그인으로, 그리고 켜 두었다면 IP 필터로도 닫혀 있습니다. 프로그램에는 둘 다 맞지 않으므로 외부 시스템을 위한 별도의 입구가 있습니다. 토큰으로 보호되는 읽기 전용 주소 두 개입니다. 토큰은 서버에서 아무것도 바꾸지 않으며, 오직 수치를 가져올 수만 있습니다.
켜기 — “설정” → “읽기 전용 API”: “API 켜기” 스위치. 토큰은 처음 저장할 때 자동으로 만들어집니다. “새 토큰” 버튼은 다른 토큰을 발급하고, 그 즉시 이전 토큰을 쓰던 쪽을 모두 끊습니다.
/api/status.php — JSON: 보안 점수, 모듈 상태, 상위 프로세스를 포함한 최근 부하 측정, 최근 24시간의 보안 이벤트;/api/metrics.php — 같은 수치를 Prometheus 텍스트 형식으로 (arciveo_* 지표: arciveo_security_score, arciveo_fail2ban_banned, arciveo_cpu_busy_pct 등).클라이언트가 헤더를 넣지 못한다면 토큰을 매개변수로 전달해도 됩니다 — ?token=…. 다만 이 형태에서는 토큰이 웹 서버 접근 로그에 남으므로 헤더 쪽이 낫습니다.
Prometheus. scrape_configs에 바로 쓸 수 있는 블록입니다.
api disabled 문구와 함께 나오는 404 — 설정에서 API가 꺼져 있습니다. 거부된 요청은 보낸 쪽 IP와 함께 애플리케이션 로그에 기록되므로, 토큰을 추측하려는 시도는 “애플리케이션 로그” 페이지에서 확인할 수 있습니다.
패널은 서버마다 따로 설치되며 “이 서버는 어떤가”라는 물음에는 잘 답합니다. 그러나 “내 서버들이 전부 어떤가”에는 답하지 못합니다. 그래서 각 패널이 짧은 요약을 개인 계정으로 보낼 수 있고, 거기서는 서버들이 하나의 목록으로 보입니다.
개인 계정에서 가져올 것 my.arciveo.com → “내 서버” 항목: 여기서 토큰(fk_… 형태)이 발급되고 수신 주소 https://my.arciveo.com/api/fleet.php가 표시됩니다. 토큰은 계정당 하나이며, 모든 서버가 같은 토큰을 씁니다.
패널에서 설정할 것 — “설정” → “서버 요약”: 수신 주소, 토큰, 서버 이름(목록에 표시될 이름. 비워 두면 패널의 도메인이 쓰입니다), 그리고 “요약 보내기” 스위치. “지금 보내기” 버튼은 일정을 기다리지 않고 즉시 연결을 확인합니다.
이후 요약은 cron으로 15분마다 전송됩니다.
정확히 무엇이 전송되는가: 서버 이름과 도메인, 운영체제, 가동 시간, PHP 버전. 보안 점수. 모듈 상태(UFW, Fail2ban jail 수, 오늘의 차단, CrowdSec·Suricata·Falco 실행 여부, ClamAV 위협, AIDE 변경, Lynis 지수, 대기 중인 업데이트, 재부팅 필요 여부, 인증서 만료까지 남은 일수, 위험하게 열린 포트 수). 최근 부하 측정(load, CPU, RAM, 디스크, inode). 최근 24시간의 이벤트 카운터와 마지막 심각 이벤트. 요약에는 로그도, 비밀번호도, 파일 내용도 들어가지 않습니다.
sudo crontab -l), 그리고 “지금 보내기” 버튼의 응답. 전송 오류는 FLEET 표시와 함께 애플리케이션 로그에 기록되며, 거기서 토큰이 거부되었는지(HTTP 401) 아니면 연결 자체가 이루어지지 않았는지 확인할 수 있습니다.
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 연결 및 프로세스 수입니다. 기본값은 24시간 구간이며, 위쪽 버튼으로 7일 또는 30일로 바꿀 수 있습니다.
데이터는 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 권한 없이 읽습니다.
보관 깊이는 두 단계입니다. 5분 간격의 “원본” 스냅샷은 하루 동안 유지됩니다 (여기에 한 시간의 여유를 더해, 그래프에서 가장 오래된 점에도 계산용 직전 값이 남도록 합니다). 끝난 시간마다 한 줄로 접히고, 이 시간 단위 계열은 30일 보관되며 “7일”과 “30일” 보기가 읽는 것이 바로 이 계열입니다. 덕분에 한 달치 기록이 약 8600행이 아니라 약 720행이면 충분합니다.
래퍼 collect-metrics-all.sh(cron 작업 요약 참조)는 서버에 설치된 모든 패널 인스턴스를 스스로 찾아 각각의 cron/collect_metrics.php를 사이트 소유자 권한으로 실행합니다.
상위 프로세스. 그래프 아래에는 CPU를 가장 많이 쓰는 프로세스 다섯 개를 보여 주는 표가 두 개 있습니다.
그래프가 한 달을 보여 주더라도 두 표는 언제나 최근 24시간을 설명합니다. 프로세스 스냅샷은 5분 간격 행에만 있고, 그 행은 하루를 넘겨 보관되지 않기 때문입니다.
ps, 수집용 PHP 스크립트, 그 위의 cron/sudo 사슬)는 목록에서 제외됩니다. 그러지 않으면 모든 스냅샷의 첫 줄을 차지하기 때문입니다. ps는 시작하는 순간 CPU를 거의 100 % 쓰는 것으로 보입니다. 같은 분에 실행된 다른 cron 작업은 그대로 나타나며, 표가 있는 이유가 바로 그것입니다.
부하 알림 (“설정” → “부하 알림” 항목) — CPU/RAM/디스크/inode 임계값을 넘으면 패널이 켜져 있는 모든 채널로 알림을 보냅니다 (Telegram, Email, webhook, 푸시 — 일일 보고서와 같은 채널이며, 알림을 위해 따로 켤 것은 없습니다). 그리고 값이 정상으로 돌아오면 한 번 더 보냅니다. 임계값을 넘긴 상태가 이어지는 동안에는 반복해서 보내지 않습니다. 다음 알림은 “정상으로 회복 → 다시 초과”라는 주기를 지난 뒤에야 옵니다.
collect_metrics.php가 실행될 때마다(5분마다) 확인합니다 — 별도의 cron은 필요 없습니다. “이미 알림함 / 아직 아님” 상태는 data/alerts_state.json에, 임계값은 패널 설정에 저장됩니다.
일일 보고서는 “서버 상태가 어떤가”라는 물음에 답합니다. 센서가 답하는 물음은 다릅니다 — “방금 무슨 일이 있었나”입니다. 5분마다 빠른 출처들을 조회해 직전 점검 이후 달라진 것만 기록하며, 메뉴의 “보안 이벤트” 페이지가 발견 목록을 보여 줍니다.
느린 점검 — Lynis, debsums, 패키지 업데이트, SSL 만료 — 은 센서가 건드리지 않습니다. 그 숫자들은 어차피 하루에 한 번 바뀌며 일일 보고서의 몫으로 남습니다.
무엇을 알아채는가 — 아홉 가지 출처:
[Priority: 1] 표시는 발견 항목을 심각으로 올립니다;FOUND 줄;root로 성공한 로그인(심각)과 이전에 본 적 없는 주소에서의 로그인(경고);설치되지 않은 도구, 또는 패널이 로그를 읽을 수 없는 도구는 그저 침묵합니다. 그것은 오류도 아니고 이벤트도 아닙니다.
data/secwatch_state.json 파일), 첫 실행은 그 표시만 기록합니다. 그렇지 않으면 감시를 켜는 순간 로그가 몇 달 동안 쌓아 둔 모든 것에 대한 알림이 쏟아질 것입니다.
설정 — “설정” → “이벤트 감시”:
23-7 형식의 구간으로, 그동안에는 경고를 보내지 않습니다.알림은 일일 보고서와 같은 채널(Telegram, Email, webhook, 푸시)로 나갑니다. 따로 켤 것은 없습니다. 모든 발견 항목은 알림이 나갔는지와 상관없이 목록에 기록되며 90일 보관됩니다.
이벤트 목록. 필터: 기간(24시간, 7일, 30일, 90일), 중요도, 출처. 최근 500건이 표시됩니다. “지금 점검” 버튼은 cron과 똑같은 점검을 실행합니다. 설정 직후 5분을 기다리지 않아도 되어 편리하며, 알림이 나가지 않은 경우 그 이유도 알려 줍니다(“중요도 기준 미만”, “조용한 시간대”, “알림 채널이 설정되지 않음”). 메뉴 항목 옆의 빨간 카운터는 최근 24시간의 심각 이벤트 건수입니다.
주소 카드. 이벤트 목록의 행이나 연관된 주소 블록의 행을 클릭하면 한 주소에 대해 알려진 모든 것이 열립니다. 해당 이벤트의 시간 순서, 국가, 그리고 첫 줄에 결론입니다. 이벤트가 정확히 한 주소에 관한 것일 때 목록의 행이 카드를 열고, 주소가 여러 개면 행 안의 주소들이 링크가 됩니다. 결론은 네 가지입니다:
시간 순서 아래에는 같은 시간 서버에서 일어난 일 블록이 있습니다. 애초에 주소가 없는 이벤트들입니다. AIDE는 누가 파일을 바꿨는지 모르고, ClamAV는 누가 감염된 파일을 가져왔는지 모릅니다. 주소가 활동한 시간과 그 이후 6시간에 해당하는 것들을 보여줍니다. 로그인과 그 뒤에 일어난 일의 결합이야말로 시도와 실제 접근을 가릅니다.
일정. 작업은 root의 crontab에 넣습니다(sudo crontab -e). 지표 수집과 동시에 시작되지 않도록 2분 어긋나게 잡았습니다.
php cron/security_watch.php를 직접 호출하지 마십시오. 래퍼는 서버에 설치된 모든 패널을 찾아 사이트 소유자 권한으로 점검을 실행합니다. 패널이 웹에서 사용하는 것과 똑같은 sudo 권한입니다. root의 crontab에서 직접 호출하면 상태 파일 data/secwatch_state.json의 소유자가 root가 되고, 패널의 “지금 점검” 버튼이 동작하지 않게 됩니다.
“웹 서버” 페이지는 접근 로그를 분석해 요청이 몇 건이었고, 그중 얼마가 오류로 끝났으며, 누가 보냈는지 보여 줍니다. ModSecurity가 “무엇이 차단되었나”에 답한다면, 이 페이지는 “애초에 사이트로 무엇이 들어왔나”에 답합니다.
보이는 것: 요청 수, RPS, 오류 비율, 별도의 5xx 카운터, 전송한 데이터 양. 시간별 요청 그래프, 2xx/3xx/4xx/5xx 코드 분포, 자주 나오는 주소, 자주 나오는 IP, 자주 발생한 오류(코드와 경로), 클라이언트(User-Agent). 기간은 24시간 또는 7일입니다.
출처는 combined 형식의 접근 로그이므로 Nginx와 Apache 모두 적합하고 추가 모듈도 필요 없습니다. 패널은 다음 표준 파일 가운데서 로그를 찾습니다.
/var/log/nginx/access.log/var/log/apache2/access.log/var/log/apache2/other_vhosts_access.log/var/log/httpd/access_log이 밖에도 패널은 /var/log/nginx/, /var/log/apache2/, /var/log/httpd/에 있는 *access*.log와 *requests*.log 파일도 확인합니다. 덕분에 사이트가 CustomLog 지시어로 자체 파일에 기록하는 로그도 찾을 수 있습니다. 예를 들어 Arcivéo 설치 프로그램이 만드는 arciveo-monitor_requests.log가 그렇습니다.
로그는 하나만 읽습니다. Nginx가 Apache 앞에 있는 구성에서는 두 로그를 합치면 모든 요청이 두 번 세어지기 때문입니다. 위 목록에서 최근 하루 안에 기록된 첫 번째 로그를 사용하며(이름 패턴으로 찾은 파일은 그다음에 최신 순으로 시도합니다), 하루 동안 어느 로그에도 기록이 없으면 비어 있지 않은 로그 중 가장 최근 것을 사용합니다. 그래서 logrotate가 비운 로그나 멈춘 웹 서버가 남긴 로그가 실제로 쓰이는 로그를 가리지 않습니다. 로그의 마지막 조각(3 MB)을 읽고 결과는 5분간 캐시되므로, 방문이 많은 사이트에서는 선택한 기간이 온전히 덮이지 않을 수 있습니다. 실제로 포함된 범위는 그래프 위에 표시됩니다.
adm 그룹 소유인데, 웹 사용자가 이 그룹에 없는 것입니다. Arcivéo 설치 프로그램은 웹 사용자를 이 그룹에 추가합니다. 수동으로 설치했다면 로그 출력이라는 단 한 가지 동작만 허용한 root 소유의 작은 래퍼로 해결됩니다(ModSecurity 페이지도 같은 방식입니다). 래퍼는 패널과 같은 규칙으로 로그를 고릅니다. 명령은 해당 페이지에도 표시되지만, 여기에도 적어 둡니다. 하나의 명령이므로 통째로 붙여 넣으십시오.
웹 사용자는 www-data가 아닐 수도 있습니다. 사이트의 PHP-FPM 풀이 어떤 사용자로 도는지 확인해(ps -o user= -C php-fpm) 그 이름을 sudoers 줄에 넣으십시오. 로그 이름에 access도 requests도 없다면, 래퍼 안의 L=… 줄 맨 앞에 그 경로를 추가하십시오.
알려진 취약점 공격 시도. 패널은 로그의 요청을 널리 쓰이는 익스플로잇 약 20종의 시그니처와 대조합니다. PHP-CGI, Apache 디렉터리 탐색, PHPUnit, Log4Shell, 라우터·카메라·VPN 게이트웨이의 취약점 등입니다. 시도마다 이 서버에서 성공할 수 있는지 사실에 근거해 알려 줍니다. 근거는 설치된 Apache와 PHP 버전, Java 설치 여부, 파일 요청에 대한 서버의 응답 코드입니다. 빨간색은 실제로 성공할 수 있는 항목에만 표시됩니다.
웹 서버 보안 강화. 패널은 서버의 각 사이트에 127.0.0.1을 통해 요청을 보내고 방문자가 받는 내용을 확인합니다. 웹 서버와 PHP 버전이 보이는지, TRACE 메서드가 켜져 있는지, 보호 헤더 Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, Content-Security-Policy가 있는지입니다. 사이트 목록은 패널을 연 주소와, 패널이 읽을 수 있다면 가상 호스트 파일에서 가져옵니다. 결과는 10분마다 갱신됩니다.
빠진 항목이 있으면 표 아래에 명령 하나가 나타납니다. 별도 파일 zz-arciveo-hardening.conf를 만들고 설정 테스트를 통과한 경우에만 웹 서버를 다시 불러옵니다. 사이트가 이미 보내는 헤더는 중복되지 않습니다. CSP는 한 줄로 제안하지 않습니다. 정책은 사이트마다 만들어야 하며, 그렇지 않으면 사이트가 자체 스크립트, 스타일, 글꼴을 불러오지 못합니다. Apache에서 변경을 되돌리려면:
ServerSignature)은 표시하지 않습니다. 버전이 없으면 서명에는 서버 이름과 호스트 이름만 나옵니다.
“공격 지도” 페이지는 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 작업.패널은 이미 실행 중이고 같은 서버에 사이트가 하나 더 필요한 경우입니다 — 모니터 옆에 자체 도메인으로 둡니다. 자동 설정을 마친 서버는 평범한 Apache + PHP-FPM이므로 사이트에는 세 가지가 필요합니다. 자체 디렉터리, 자체 vhost, 자체 인증서입니다. 패널은 건드리지 않습니다. 패널은 서버 전체를 감시하므로 두 번째 도메인을 위해 두 번째 사본을 둘 필요가 없습니다.
site2.example.com은 새 사이트의 주소입니다. 별도 도메인(mycompany.com)일 수도, 서브도메인(shop.mycompany.com)일 수도 있습니다. Apache에는 차이가 없고 DNS 레코드(1단계)와 인증서 발급(5단계)만 달라집니다. site2는 단지 사이트의 짧은 이름으로, 디렉터리 /var/www/site2와 설정 파일 site2.conf의 이름에 쓰입니다. 도메인과는 아무 관련이 없으며 Apache는 주소를 오직 ServerName에서 가져옵니다. 이름은 무엇이든 되며, 예를 들어 shop.mycompany.com이라면 shop이 편리합니다(디렉터리 /var/www/shop, 설정 shop.conf).
1. DNS. 도메인 등록기관의 DNS 화면에서 이 서버의 IP를 가리키는 A 레코드를 만듭니다.
몇 분 뒤 확인하십시오. dig +short site2.example.com이 서버의 IP를 반환해야 합니다.
2. 사이트 디렉터리.
3. vhost. 블록은 통째로 터미널에 붙여 넣습니다. PHP-FPM 소켓 경로는 자동으로 확인됩니다.
4. 사이트를 활성화하고 다시 읽어 들이기 전에 설정을 확인합니다.
5. SSL. certbot은 자동 설정이 이미 설치했습니다. 이 시점에는 DNS가 서버를 가리키고 있어야 합니다.
세 번째 이후의 도메인도 같은 방식으로 추가합니다. 도메인과 디렉터리를 바꿔 1~5단계를 반복하십시오.
Host가 어떤 ServerName과도 일치하지 않으면(IP로 접속하거나 다른 사람의 도메인이 서버를 가리키는 경우) Apache는 /etc/apache2/sites-enabled/에서 알파벳순으로 첫 번째 활성 사이트를 제공합니다. 기본 000-default는 자동 설정이 비활성화하므로 이 “당번” 역할은 패널의 vhost(arciveo-monitor.conf)가 계속 맡습니다. 각 사이트를 고유한 이름으로 여는 데에는 영향이 없습니다. IP로 다른 사이트가 열리게 하려면 그 파일 이름을 알파벳순에서 앞서도록 지으십시오(예: 000-site2.conf).
/var/log/apache2/의 site2_error.log와 site2_access.log.
.htaccess(프런트 컨트롤러)로 제어되므로 중첩된 사이트는 예상과 다른 결과를 내놓습니다. 사이트는 나란히 두십시오: /var/www/monitor, /var/www/site2.
로그인할 수 없다면 서버의 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는 이미 패널에 포함되어 별도 설치 불필요;security-watch-all.sh → /usr/local/bin/ (chmod +x) — 패널의 cron/security_watch.php를 사이트 소유자 권한으로 실행합니다 (“보안 이벤트” 페이지, 위 crontab의 2-57/5 줄). security_watch.php는 이미 패널에 포함되어 있어 따로 설치할 필요가 없습니다;fleet-push-all.sh → /usr/local/bin/ (chmod +x) — 패널의 cron/fleet_push.php를 통해 서버 요약을 개인 계정으로 보냅니다 (위의 3,18,33,48 줄);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; 커널 업데이트 후에는 서버를 재부팅하세요.