본문 바로가기
삵
Nginx

GoAccess로 Nginx 접근 로그 실시간 분석

강강철지그·2026년 9월 27일·조회 1

접속 통계 하나 보자고 유료 APM을 붙이기는 부담스럽고, 그렇다고 grep과 awk로 매번 로그를 훑기도 번거롭다. 이럴 때 제가 서버마다 하나씩 얹어 두는 도구가 GoAccess다. 이미 쌓이고 있는 Nginx 접근 로그만 있으면 방문자, 상위 URL, 응답 코드, 봇 트래픽을 바로 그림으로 볼 수 있어서, 새 에이전트나 외부 SaaS 없이 현황 파악이 끝난다.

결론부터 말하자면, goaccess /var/log/nginx/access.log -o /var/www/goaccess/index.html --log-format=COMBINED --real-time-html 한 줄이면 실시간으로 갱신되는 HTML 대시보드가 만들어진다. GoAccess는 자체 웹소켓 서버로 브라우저에 변경분을 밀어 주고, 리포트 HTML 자체는 Nginx가 서빙한다. 이 글에서는 설치, 로그 포맷 지정, 실시간 대시보드 서빙, systemd 상시 구동, 자주 나오는 오류 순서로 다룬다.

1. GoAccess가 무엇인가

GoAccess는 터미널과 브라우저에서 동작하는 오픈소스 웹 로그 분석기다. C로 작성돼 의존성이 가볍고, 로그를 파싱해 방문자 수, 요청 경로, HTTP 상태 코드, 유입 경로, 운영체제, 브라우저, 봇 같은 지표를 패널별로 집계한다.

여기서 말하는 실시간 HTML 대시보드는 정적 리포트와 다르다. GoAccess가 로그 파일 끝을 계속 따라 읽으면서(tail), 새 요청이 들어올 때마다 웹소켓(WebSocket)으로 브라우저에 갱신 데이터를 보낸다. 웹소켓은 서버와 브라우저가 연결을 열어 두고 양방향으로 데이터를 주고받는 통신 방식이라, 페이지를 새로고침하지 않아도 숫자가 바뀐다. 이때 리포트 HTML 파일 자체를 GoAccess가 서빙하지는 않는다. 공식 문서도 "GoAccess does NOT serve the generated html report"라고 못 박는다. HTML은 Nginx가 내보내고, 갱신만 GoAccess 웹소켓이 담당하는 구조다.

2. 설치

Ubuntu, Debian은 배포판 저장소에도 있지만 버전이 뒤처진다. 실시간 HTML과 봇 분류 같은 최신 기능을 쓰려면 공식 APT 저장소를 등록하는 편을 권한다.

wget -O - https://deb.goaccess.io/gnugpg.key | gpg --dearmor | sudo tee /usr/share/keyrings/goaccess.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/goaccess.gpg] https://deb.goaccess.io/ $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/goaccess.list
sudo apt-get update && sudo apt-get install goaccess

RHEL, Rocky, AlmaLinux 계열은 EPEL을 켠 뒤 설치한다.

sudo dnf install epel-release
sudo dnf install goaccess

설치가 끝나면 버전과 빌드 옵션을 확인한다. 실시간 HTML을 쓰려면 웹소켓 지원이 켜져 있어야 한다.

$ goaccess --version
GoAccess - 1.9.4
...
Build configure arguments:
  --enable-utf8 --enable-geoip=mmdb --with-openssl

버전 번호는 저장소 상태에 따라 다를 수 있다. --with-openssl이 보이면 wss://(TLS 웹소켓)까지 붙일 수 있다는 뜻이다.

3. 로그 포맷 맞추기

GoAccess에서 제일 많이 막히는 지점이 로그 포맷이다. 포맷이 안 맞으면 아무 패널도 안 채워지거나 파싱 에러로 바로 죽는다. Nginx 기본 combined 포맷은 GoAccess의 COMBINED 프리셋과 그대로 대응한다. Nginx 설정을 손대지 않았다면 아래 형태다.

log_format combined '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

이 경우 --log-format=COMBINED만 주면 된다. Apache HTTP Server의 기본 combined 로그도 같은 프리셋으로 읽힌다. 가상호스트 이름이 앞에 붙는 형태라면 VCOMBINED를 쓴다.

먼저 터미널 모드로 열어 파싱이 되는지 눈으로 확인하고 넘어가는 순서를 권한다.

goaccess /var/log/nginx/access.log --log-format=COMBINED

이 명령을 처음 실행하면 방문자, 요청 파일, 상태 코드 패널이 텍스트 UI로 뜬다. 여기서 화면이 정상적으로 채워지면 포맷이 맞은 것이다. 커스텀 log_format을 쓰는 서버라면 프리셋으로 안 맞을 수 있는데, 그때는 --log-format, --date-format, --time-format 세 가지를 직접 지정한다. 매번 옵션을 나열하기 번거로우면 /etc/goaccess/goaccess.conf나 ~/.goaccessrc에 적어 둔다.

4. 정적 HTML 리포트부터 만들어 보기

실시간으로 넘어가기 전에, 한 번 찍고 끝나는 정적 리포트로 결과물을 먼저 확인한다.

goaccess /var/log/nginx/access.log -o /tmp/report.html --log-format=COMBINED

/tmp/report.html을 브라우저로 열면 방문자 추이, 상위 URL, 상태 코드 분포, 상위 유입, 봇 목록이 패널로 정리돼 있다. 로그가 여러 파일이거나 압축돼 있으면 표준 입력으로 이어 붙여 넘길 수 있다.

zcat /var/log/nginx/access.log.*.gz | goaccess - -o /tmp/report.html --log-format=COMBINED

파일 인자 대신 -를 쓰면 표준 입력을 읽는다. 여기까지 정상이면 실시간으로 넘어가면 된다.

5. 실시간 대시보드 서빙

실시간 모드는 --real-time-html을 붙이는 순간 켜진다. 리포트는 Nginx 문서 루트 아래에 두고, GoAccess는 백그라운드에서 로그를 계속 따라 읽게 한다.

sudo mkdir -p /var/www/goaccess
goaccess /var/log/nginx/access.log \
  -o /var/www/goaccess/index.html \
  --log-format=COMBINED \
  --real-time-html \
  --addr=127.0.0.1 --port=7890

GoAccess의 웹소켓 서버는 기본으로 7890 포트를 연다. 위에서는 --addr=127.0.0.1로 로컬에만 바인딩했다. 외부에 7890을 직접 노출하는 대신, Nginx가 /ws 경로로 들어온 웹소켓 연결을 이 포트로 프록시하도록 붙인다. 브라우저 쪽 리포트가 어디로 접속할지는 --ws-url로 알려 준다.

goaccess /var/log/nginx/access.log \
  -o /var/www/goaccess/index.html \
  --log-format=COMBINED \
  --real-time-html \
  --addr=127.0.0.1 --port=7890 \
  --ws-url=wss://stats.example.com:443/ws

Nginx에는 리포트를 서빙할 location과 웹소켓을 프록시할 location /ws를 함께 둔다.

server {
    listen 443 ssl;
    server_name stats.example.com;

    ssl_certificate     /etc/letsencrypt/live/stats.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/stats.example.com/privkey.pem;

    root /var/www/goaccess;
    index index.html;

    # 리포트는 관리자만 보게 Basic 인증으로 막는다
    auth_basic "GoAccess";
    auth_basic_user_file /etc/nginx/.htpasswd;

    location /ws {
        proxy_pass http://127.0.0.1:7890;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 7d;
    }
}

--ws-url의 경로(/ws)와 Nginx location /ws가 일치해야 브라우저가 웹소켓을 제대로 잡는다. TLS를 안 쓰는 내부망이라면 --ws-url=ws://호스트:포트로 ws 스킴을 쓰면 된다. 리포트는 통계라도 공개하면 곤란한 정보가 많으니 auth_basic이나 IP 제한을 꼭 걸어 둔다.

6. systemd로 상시 갱신

터미널을 닫으면 GoAccess도 같이 죽는다. 재부팅 후에도 자동으로 뜨고, 죽으면 되살아나게 systemd 서비스로 등록한다. --daemonize 대신 포그라운드로 두고 systemd가 프로세스를 관리하게 하는 편이 로그 추적과 재시작에 깔끔하다.

sudo tee /etc/systemd/system/goaccess.service <<'EOF'
[Unit]
Description=GoAccess real-time HTML report
After=network.target nginx.service

[Service]
User=www-data
SupplementaryGroups=adm
ExecStart=/usr/bin/goaccess /var/log/nginx/access.log \
  -o /var/www/goaccess/index.html \
  --log-format=COMBINED \
  --real-time-html \
  --addr=127.0.0.1 --port=7890 \
  --ws-url=wss://stats.example.com:443/ws
Restart=on-failure
RestartSec=3

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now goaccess
systemctl status goaccess

여기서 한 번 걸린다. www-data로 실행하면 /var/log/nginx/access.log를 읽을 권한이 없어 서비스가 바로 실패한다. 데비안, 우분투 계열에서 Nginx 로그는 adm 그룹이 읽을 수 있으므로 위처럼 SupplementaryGroups=adm을 넣어 해결한다. RHEL 계열은 로그 소유가 다르니 ls -l /var/log/nginx/로 그룹을 확인해 맞춘다.

7. 자주 나오는 오류와 해결

파싱이 0건이거나 바로 종료된다

GoAccess - version 1.9.4
Fatal error has occurred
Error occurred at: src/goaccess.c ...
Nothing valid to process.

로그 포맷이 안 맞는다는 신호다. --log-format 프리셋을 잘못 골랐거나 커스텀 log_format과 어긋난 경우다. 실제 로그 한 줄을 열어 필드 순서를 확인하고, 프리셋으로 안 맞으면 --log-format, --date-format, --time-format을 직접 지정한다.

대시보드 우상단이 계속 "disconnected"로 뜬다

HTML은 열리는데 실시간 숫자가 안 바뀌면 웹소켓 연결이 안 잡힌 것이다. 세 가지를 확인한다. 첫째, --ws-url의 스킴과 경로가 실제 접속 URL과 맞는가(HTTPS로 열었으면 wss여야 한다). 둘째, Nginx location /ws의 Upgrade, Connection 헤더 설정이 들어갔는가. 셋째, 방화벽이나 --addr 바인딩 때문에 프록시가 7890에 못 닿는 건 아닌가. 브라우저 개발자 도구의 Network 탭에서 WS 연결이 101로 승격되는지 보면 원인이 좁혀진다.

Permission denied로 서비스가 안 뜬다

journalctl -u goaccess -n 20
... Unable to open the specified log file. Permission denied

실행 사용자가 로그 파일을 못 읽는 경우다. 6절처럼 adm 그룹을 보조 그룹으로 넣거나, 로그 파일 권한을 확인한다.

logrotate 후 갱신이 멈춘다

로그가 로테이션되면 GoAccess가 붙어 있던 파일이 바뀌면서 tail이 끊길 수 있다. logrotate의 postrotate에서 GoAccess 서비스를 재시작하도록 걸어 두면 안정적이다.

postrotate
    systemctl restart goaccess
endscript

8. 데이터 누적 보관

기본 실시간 모드는 지정한 로그 파일 범위만 집계한다. 로테이션으로 오래된 로그가 사라져도 통계를 이어서 쌓고 싶다면 --persist와 --restore로 파싱 결과를 디스크에 저장하고 다음 실행 때 불러온다.

goaccess /var/log/nginx/access.log \
  -o /var/www/goaccess/index.html --log-format=COMBINED \
  --persist --restore --db-path=/var/lib/goaccess

--db-path 기본값은 /tmp라 재부팅 때 날아갈 수 있으니, 누적 보관이 목적이면 /var/lib/goaccess처럼 영속 경로를 따로 지정한다.

자주 묻는 질문

GoAccess로 실시간 대시보드를 보려면 별도 웹서버가 필요한가?

리포트 HTML 자체는 GoAccess가 서빙하지 않는다. GoAccess는 웹소켓으로 갱신 데이터만 밀어 주고, 생성된 HTML 파일은 Nginx나 Apache 같은 웹서버가 서빙해야 한다. 웹소켓 포트(기본 7890)는 웹서버에서 리버스 프록시로 연결한다.

Nginx 기본 로그면 로그 포맷을 어떻게 지정하나?

Nginx 기본 combined 포맷은 GoAccess의 COMBINED 프리셋과 그대로 대응한다. --log-format=COMBINED만 주면 된다. Apache 기본 combined도 같은 프리셋으로 읽히고, 가상호스트 이름이 앞에 붙으면 VCOMBINED를 쓴다.

실시간 대시보드가 disconnected로만 표시되는 이유는?

웹소켓 연결이 안 잡힌 경우다. HTTPS로 열었다면 --ws-url이 wss여야 하고, Nginx location에 Upgrade와 Connection 헤더 프록시 설정이 있어야 한다. --ws-url의 경로와 Nginx location 경로도 일치해야 한다.

로그가 로테이션되면 통계가 초기화되나?

로테이션으로 파일이 바뀌면 tail이 끊길 수 있어 logrotate postrotate에서 GoAccess를 재시작하는 편이 안정적이다. 오래된 로그가 사라져도 통계를 누적하려면 --persist, --restore, --db-path로 파싱 결과를 디스크에 저장한다.

GoAccess 서비스가 Permission denied로 안 뜬다.

실행 사용자가 로그 파일을 못 읽는 경우다. 데비안, 우분투 계열 Nginx 로그는 adm 그룹이 읽을 수 있으므로 systemd 서비스에 SupplementaryGroups=adm을 넣는다. RHEL 계열은 ls -l로 로그 그룹을 확인해 맞춘다.

관련 글

댓글 0

로그인 후 댓글을 남길 수 있습니다.

아직 댓글이 없습니다.