컴퓨터활용팁

서버 실시간 트래픽 대시보드 만들기 — GoAccess 설치부터 자동 갱신까지 (실전 삽질기 포함)

서버 실시간 트래픽 대시보드 만들기 — GoAccess 설치부터 자동 갱신까지 (실전 삽질기 포함)

Ubuntu 22.04 · Nginx 환경에서 GoAccess로 웹 트래픽 대시보드 붙이기

📚 GoAccess 트래픽 대시보드 시리즈 (전 3편)

  1. 1편 — 설치 & 기본 대시보드 구축
  2. 2편 — 도메인별 대시보드 자동화
  3. 3편 — 봇 차단으로 트래픽 방어

리눅스 서버에 GoAccess로 웹 트래픽 대시보드를 붙인 실전 기록입니다. “접속자도 없는데 왜 트래픽이 이렇게 많지?”라는 의문에서 시작해, 브라우저에서 바로 확인되는 통계 페이지를 완성하기까지의 전 과정을 담았습니다.

중간에 막혔던 부분(WebSocket 실시간 모드 실패 → 정적 방식 전환)도 그대로 실었으니, 같은 벽에 부딪힌 분들께 도움이 되길 바랍니다.

※ 이 글의 도메인, IP, 계정명 등은 모두 예시 값으로 교체했습니다. 본인 환경에 맞게 바꿔서 적용하세요.

왜 GoAccess인가

서버를 운영하다 보면 이런 궁금증이 생깁니다.

  • 우리 사이트에 실제로 몇 명이나 들어오지?
  • 어떤 페이지가 제일 많이 열릴까?
  • 분명 방문자는 없는데 트래픽 통계 숫자는 왜 계속 올라갈까?

이런 걸 알려면 웹서버 접속 로그(access log)를 분석해야 합니다. 하지만 로그 파일은 이렇게 생겼습니다.

123.45.67.89 - - [01/Jul/2026:10:22:31 +0900] "GET / HTTP/1.1" 200 5312 "-" "Mozilla/5.0 ..."

이런 줄이 수천, 수만 개. 사람이 눈으로 읽을 물건이 아니죠.

GoAccess는 이 로그를 읽어서 방문자 수, 인기 페이지, 국가, 브라우저, 트래픽 용량 등을 한눈에 보이는 대시보드로 만들어 줍니다. 무료에 가볍고, 무거운 모니터링 스택(Prometheus/Grafana 같은)을 깔 필요도 없습니다.

이번 목표는 명확합니다.

브라우저에서 https://example.com/stats 주소로 접속하면, 아이디·비밀번호를 통과한 뒤 트래픽 대시보드가 뜨게 만들기.

전체 구성은 이렇습니다.

GoAccess 구성도

방문자가 웹서버에 접속하면 → 웹서버가 로그를 남기고 → GoAccess가 그 로그를 분석해 HTML을 만들고 → 운영자는 브라우저로 그 결과를 확인하는 흐름입니다.

1단계 — 필요한 도구 준비

먼저 설치 과정에서 필요한 유틸리티부터 준비합니다.

apt update && apt install -y gnupg wget apache2-utils

각각의 용도는 이렇습니다.

  • gnupg : 저장소 서명 키 검증용
  • wget : 파일 다운로드용
  • apache2-utils : 이 안에 들어있는 htpasswd 도구를 씁니다. 나중에 대시보드 페이지에 아이디·비밀번호를 거는 데 사용합니다.
💡 apache2-utils라는 이름 때문에 “아파치 웹서버가 깔리나?” 걱정할 수 있는데, 아닙니다. 이건 웹서버 본체와 별개인 유틸리티 모음이고, 우리가 쓰는 건 htpasswd 하나뿐입니다. Nginx 환경에 아무 영향 없습니다.

2단계 — GoAccess 공식 저장소 등록

우분투 기본 저장소에도 GoAccess가 있지만 버전이 조금 오래됐습니다. 실시간 HTML 대시보드 기능을 온전히 쓰려면 공식 저장소로 설치하는 편이 좋습니다.

① 서명 키 등록

wget -O - https://deb.goaccess.io/gnugpg.key | gpg --dearmor | tee /usr/share/keyrings/goaccess.gpg >/dev/null

출력 없이 조용히 끝나면 성공입니다.

② 저장소 추가

echo "deb [signed-by=/usr/share/keyrings/goaccess.gpg arch=$(dpkg --print-architecture)] https://deb.goaccess.io/ $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/goaccess.list

실행하면 아래처럼 추가된 저장소 한 줄이 출력됩니다.

deb [signed-by=/usr/share/keyrings/goaccess.gpg arch=amd64] https://deb.goaccess.io/ jammy main

arch(amd64)와 우분투 코드네임(jammy = 22.04)이 자동으로 잡히는 게 포인트입니다.

3단계 — GoAccess 설치

apt update && apt install -y goaccess

설치가 끝나면 버전을 확인합니다.

goaccess --version | head -1

GoAccess - 1.x.x 형태로 나오면 성공입니다.

✅ 설치 로그 끝에 “No services need to be restarted”가 뜨면, 기존에 돌던 웹서버 등을 전혀 건드리지 않고 GoAccess만 깔끔하게 추가됐다는 뜻입니다.

4단계 — 로그 파일 위치 확인

GoAccess가 분석할 접속 로그 파일의 정확한 경로를 확인해야 합니다. 환경마다 다르지만, 보통 아래 같은 위치에 도메인별로 로그가 쌓입니다.

ls -la /var/log/nginx/

이 글의 예시에서는 도메인별 접속 로그가 다음 경로에 있다고 가정하겠습니다.

/var/log/nginx/example.com.access.log
⚠️ [본인이 확인할 부분] 로그 경로는 서버 설정에 따라 다릅니다. /var/log/nginx/access.log처럼 하나로 합쳐진 경우도 있고, 도메인별로 나뉜 경우도 있습니다. 본인 서버에서 실제 파일명을 먼저 확인하고, 이후 명령의 로그 경로를 그에 맞게 바꿔주세요.

5단계 — 대시보드에 비밀번호 걸기

/stats 페이지가 아무나 볼 수 있으면 곤란하니, 아이디·비밀번호를 겁니다.

htpasswd -c /etc/nginx/conf.d/stats.htpasswd admin

실행하면 비밀번호를 두 번 입력하라고 나옵니다(화면엔 안 보이지만 정상). 여기서 정한 admin + 비밀번호가 나중에 대시보드 접속용 계정이 됩니다.

💡 -c 옵션은 파일을 새로 만드는 것이라 처음 한 번만 씁니다. 나중에 계정을 추가할 땐 -c를 빼고 실행하세요.

6단계 — 실시간 대시보드 실행 (그리고 첫 번째 벽)

이제 GoAccess를 실시간 HTML 모드로 돌려봅니다. 실시간 모드는 WebSocket을 이용해 대시보드가 자동으로 갱신되는 방식입니다.

goaccess /var/log/nginx/example.com.access.log \
  -o /home/myuser/web/example.com/public_html/stats.html \
  --log-format=COMBINED \
  --real-time-html \
  --ws-url=wss://example.com:443/ws-stats \
  --port=7890 \
  --daemonize
  • --real-time-html : 실시간 자동 갱신 모드
  • --ws-url : 브라우저가 접속할 WebSocket 주소
  • --daemonize : 백그라운드 실행

실행하면 Daemonized GoAccess: [PID] 메시지가 뜨고 백그라운드로 돕니다. 여기까지는 순조로웠습니다.

그리고 이어서 웹서버에 /stats(대시보드)와 /ws-stats(실시간 연결용) 경로를 연결한 뒤 브라우저로 접속했더니…

UNABLE TO AUTHENTICATE WEBSOCKET

대시보드 껍데기는 뜨는데, 실시간 갱신용 WebSocket 연결이 계속 실패했습니다. 여기서 한참을 헤맸습니다.

7단계 — WebSocket 삽질 기록 (원인 추적)

같은 증상을 겪는 분들을 위해, 시도한 것들을 순서대로 남깁니다.

① WebSocket 주소 명시 → 실패

--ws-url로 정확한 주소를 지정했지만 그대로였습니다.

② 인증(auth) 충돌 의심 → 실패

브라우저 JavaScript가 WebSocket을 열 때는 로그인 정보를 자동으로 못 싣는 경우가 있어, WebSocket 경로의 비밀번호 인증을 제거해 봤지만 여전히 실패.

③ 표준 헤더 매핑 추가 → 그래도 실패

WebSocket 프록시의 정석인 map 지시어를 추가했습니다.

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

설정 자체는 완벽하게 로드됐는데도 브라우저에선 계속 연결 실패.

④ 서버 로그를 봤더니… 결정적 단서

브라우저 개발자도구(F12) 콘솔엔 이렇게만 떴습니다.

WebSocket connection to 'wss://example.com/ws-stats' failed:

이유도 없이 그냥 failed. 그리고 서버의 웹서버 에러 로그를 확인했더니 — /ws-stats 관련 에러가 아예 찍히지 않았습니다.

이게 핵심이었습니다.

요청이 서버의 에러 로그에 흔적조차 안 남긴다는 건, 요청이 우리 서버에 도달하기 전에 앞단에서 차단되고 있다는 뜻입니다.

범인은 앞단 프록시(리버스 프록시)였습니다. 도메인이 웹서버 앞에 놓인 프록시 계층을 거쳐 들어오는 구조였는데, 이 앞단이 WebSocket 연결을 프록시하면서 막고 있었던 겁니다. WebSocket을 이런 앞단 뒤에서 살리려면 추가 설정이 복잡하고 성공도 장담하기 어렵습니다.

💡 교훈: 웹서버 앞에 프록시 계층이 있다면, WebSocket 기반 실시간 기능은 기본적으로 막힐 수 있습니다. F12 콘솔 에러에 구체적 코드(502/400 등)조차 없고, 서버 로그에도 안 남으면 앞단 차단을 의심하세요.

8단계 — 정적 방식으로 전환 (해결)

실시간(초 단위) 갱신이 꼭 필요한 게 아니라면, WebSocket을 아예 쓰지 않는 정적 방식이 훨씬 안정적입니다. 목적이 “트래픽 확인”이라면 5분 간격 갱신으로 충분하니까요.

먼저 기존 실시간 데몬을 종료합니다.

pkill -f goaccess

그리고 이번엔 실시간 관련 옵션을 모두 빼고 HTML 한 장을 생성합니다.

goaccess /var/log/nginx/example.com.access.log \
  --log-format=COMBINED \
  -o /home/myuser/web/example.com/public_html/stats.html

GoAccess 로고가 잠깐 뜨고 Cleaning up resources... 후 끝나면 성공입니다. WebSocket을 아예 안 쓰니 앞단 프록시 문제가 통째로 사라집니다.

이제 브라우저에서 https://example.com/stats로 접속(강력 새로고침: Ctrl+Shift+R)하면 — 드디어 대시보드가 제대로 뜹니다.

GoAccess 대시보드 예시

9단계 — “접속자 없는데 왜 트래픽?”의 정체

대시보드를 열자마자 처음의 의문이 풀렸습니다. 위 예시 화면을 보면:

  • Total Requests 2,365 vs Unique Visitors 215 — 방문자 215명이 2,365번 요청. 사람치고 요청이 지나치게 많습니다.
  • Not Found (404) 398건 — 존재하지도 않는 페이지를 398번이나 두드렸습니다.
  • Requested Files 목록/.env, /wp-config.php.bak, /.git/config, /wp-login.php 같은 게 상위에 등장.

이건 전형적인 자동화 봇/스캐너입니다.

  • .env → 환경변수(DB 비밀번호, API 키) 유출을 노림
  • wp-config → 워드프레스 설정 파일 탈취 시도
  • .git/config → 소스코드 저장소 노출 노림

인터넷에 도메인을 올려두면 예외 없이 모든 사이트가 이런 스캔을 당합니다. 특정 사이트가 표적이 된 게 아니라, 봇이 IP 대역을 무작위로 훑으며 “혹시 열린 문이 있나” 자동으로 두드리는 것이죠. 트래픽 숫자의 상당 부분은 사람이 아니라 이 봇들이 만든 것이었습니다.

다행인 점: 노려지는 파일들이 실제로는 웹에서 안 열리면(404가 뜨면) 피해가 없습니다.
확인 방법: curl -sI https://example.com/.env | head -1
404403이 나오면 안전, 200이면 실제 유출이니 즉시 조치가 필요합니다.

10단계 — 5분마다 자동 갱신 (마무리)

정적 방식은 한 번 만들면 그 시점에서 멈춰 있으니, 주기적으로 다시 생성해줘야 최신 상태가 유지됩니다. 이걸 자동화합니다.

① 갱신 스크립트 만들기

여러 줄로 만들면 줄바꿈이 깨질 수 있어, 명령을 한 줄로 넣는 게 안전합니다.

printf '#!/bin/bash\ngoaccess /var/log/nginx/example.com.access.log --log-format=COMBINED -o /home/myuser/web/example.com/public_html/stats.html\n' > /usr/local/bin/goaccess-example.sh
chmod +x /usr/local/bin/goaccess-example.sh
⚠️ 주의: 스크립트를 여러 줄로 나눠 붙여넣다 --log-format 옵션이 깨지면, GoAccess가 로그 포맷 선택 화면(파란 대화형 창)을 띄웁니다. 그 화면이 뜨면 q를 눌러 빠져나온 뒤, 위처럼 한 줄 명령으로 다시 만드세요.

② 스크립트 동작 확인

/usr/local/bin/goaccess-example.sh && echo "성공"

로고가 뜨고 마지막에 성공이 출력되면 정상입니다. (대화형 화면이 안 뜨는 게 포인트)

③ 크론에 등록 (5분 간격)

echo "*/5 * * * * root /usr/local/bin/goaccess-example.sh >/dev/null 2>&1" > /etc/cron.d/goaccess-example

이제 5분마다 대시보드가 최신 로그로 자동 갱신됩니다. 브라우저에서 /stats를 열고 5분 뒤 Last Updated 시각이 바뀌는지 확인하면 완성입니다.

완성된 구성 정리

항목 내용
대시보드 주소 https://example.com/stats
접속 계정 admin + 설정한 비밀번호
방식 정적 HTML (WebSocket 미사용)
자동 갱신 5분마다 크론으로 재생성
보안 .htpasswd 비밀번호 보호

마치며 — 삽질에서 얻은 것

처음엔 “실시간 대시보드”에 욕심을 냈지만, 앞단 프록시 때문에 WebSocket이 막혔습니다. 하지만 목적을 다시 생각해보니 “트래픽을 확인한다”는 목표엔 5분 간격 정적 갱신으로 충분했고, 오히려 이쪽이 훨씬 안정적이었습니다.

그리고 대시보드 덕분에 막연했던 “트래픽이 왜 많지?”라는 의문이 “봇 스캔이 로그를 채우고 있었다”는 명확한 데이터로 바뀌었습니다. 서버 상태를 눈으로 본다는 건 생각보다 큰 안심을 줍니다.

👉 다음 편 예고 — 2편: 도메인별 대시보드 자동화

지금은 도메인 하나만 봤지만, 사이트가 여러 개라면? 다음 편에서는 한 주소에서 모든 사이트 트래픽을 각각 확인하고, 새 사이트가 생겨도 손 안 대는 전자동 구조를 만듭니다.

긴 글 읽어주셔서 감사합니다. 같은 삽질을 하는 분께 도움이 되었길 바랍니다. 🙂


[체크리스트] 본인 환경에서 바꿔야 할 값들

  • example.com → 실제 도메인
  • 123.45.67.89 → 실제 서버 IP
  • myuser → 실제 계정명
  • /var/log/nginx/example.com.access.log → 실제 로그 경로
  • /home/myuser/web/example.com/public_html/ → 실제 웹 루트 경로
Advertisement

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다