infra
Platform

모듈 맵

[Infra Ops] httpd VirtualHost 설정과 리버스 프록시 운영

0 / 52 완료

펼치기
0 / 52 완료0%

Infra-ops · 15 / 52

[Infra Ops] httpd VirtualHost 설정과 리버스 프록시 운영

VirtualHost, DocumentRoot, mod_proxy, access/error 로그 분석까지 — Nginx와 함께 현장에서 가장 많이 쓰이는 Apache httpd 운영 실무

🚨INCIDENT ALERT
HIGH

고객사 서버 점검 의뢰를 받아 SSH로 접속했습니다. 웹 서버 프로세스를 확인했더니 nginx가 아닌 httpd가 떠 있습니다. 레거시 Java EE 프로젝트가 Tomcat과 붙어 있고, Apache가 앞단에서 요청을 받아 프록시하는 구조입니다. VirtualHost 설정이 어디 있는지, mod_proxy는 어떻게 확인하는지, 로그는 어디를 봐야 하는지 — Nginx만 써왔다면 당황스러운 순간입니다.

Apache httpd는 여전히 엔터프라이즈 환경에서 광범위하게 쓰입니다. 레거시 유지보수, 고객사 서버 진단, Nginx 도입 전 마이그레이션 계획 — 어떤 상황에서든 Apache를 읽고 운영할 수 있어야 현장에서 막히지 않습니다.

이번 챕터에서 배울 것
  • 1Apache MPM 모델과 Nginx 이벤트 기반 모델의 차이를 설명할 수 있다
  • 2VirtualHost 블록을 작성하고 apachectl configtest로 검증할 수 있다
  • 3mod_proxy와 mod_proxy_http를 확인·활성화하고 ProxyPass 설정을 작성할 수 있다
  • 4access_log 포맷 필드(%h, %t, %>s, %T, %D)를 해석할 수 있다
  • 5graceful restart와 일반 restart의 차이를 이해하고 상황에 따라 선택할 수 있다

Apache 구조와 Nginx와의 차이

💡개념

Apache MPM — 프로세스/스레드 기반 처리 모델

Apache와 Nginx가 동시 요청을 처리하는 방식은 근본적으로 다릅니다. 이 차이를 모르면 "Apache가 왜 느린가"라는 질문에 답하지 못하고, 레거시 서버를 최적화할 때 잘못된 판단을 하게 됩니다.

Apache MPM — 프로세스/스레드 기반 처리 모델

Apache는 Multi-Processing Module(MPM) 방식으로 요청을 처리합니다. 어떤 MPM을 쓰느냐에 따라 프로세스/스레드 사용 방식이 달라집니다.

MPM방식특징권장 상황
prefork요청당 독립 프로세스안정적, 메모리 많이 씀mod_php 같은 비스레드세이프 모듈
worker멀티스레드 + 멀티프로세스메모리 효율적PHP 없이 정적/프록시만 할 때
eventworker + 비동기 Keep-Alive고동시 연결에 유리Apache 2.4 기본값, 현재 권장
로컬 터미널
# 현재 사용 중인 MPM 확인
httpd -V | grep -i mpm
# 또는
apachectl -V | grep MPM

# 출력 예시:
# Server MPM:     event
# httpd compiled with: -D APR_HAS_SENDFILE ...

Nginx와의 핵심 차이:

Nginx는 마스터 프로세스 하나와 소수의 worker 프로세스가 이벤트 루프로 수만 개의 연결을 처리합니다. Apache event MPM도 비슷하게 진화했지만, 역사적으로 prefork가 기본이었고 모듈 생태계가 프로세스 기반을 전제로 만들어진 것이 많습니다.

현장에서 Apache가 여전히 쓰이는 이유:

  • mod_php: PHP를 Apache 프로세스 내에서 직접 실행 — 공유 호스팅 환경 다수
  • mod_jk / mod_proxy_ajp: Tomcat과의 AJP 연결 — 레거시 Java EE 인프라
  • 모듈 호환성: mod_rewrite, mod_auth_*, .htaccess — 기존 설정 자산
  • 레거시 유지: 수십 년 된 배포를 건드리지 않는 조직 다수

VirtualHost 설정

💡개념

Name-based VirtualHost — 하나의 서버, 여러 도메인

고객사 서버를 인수인계받아 VirtualHost 파일을 처음 열었을 때 /etc/httpd/conf.d/ 아래 .conf 파일이 10개 이상 있는 경우가 있습니다. 어떤 도메인이 어느 파일에서 처리되는지 모르면 Nginx 설정이라도 짐작하겠지만, Apache 문법이 낯설면 더 막막합니다. VirtualHost를 이해하면 이 구조를 5분 안에 파악할 수 있습니다. 서버 IP가 하나뿐인데 여러 도메인이 돌아가야 할 때, HTTP의 Host 헤더를 보고 요청을 구분하는 것이 Name-based VirtualHost입니다.

VirtualHost는 Apache에서 멀티도메인 서비스를 구현하는 핵심 메커니즘입니다. 클라이언트가 HTTP 요청을 보낼 때 Host 헤더에 도메인을 담아 보내고, Apache는 그 헤더 값을 보고 어느 VirtualHost 블록이 처리할지 결정합니다.

Name-based VirtualHost — 하나의 서버, 여러 도메인

설정 파일 위치:

  • CentOS/RHEL: /etc/httpd/conf.d/*.conf
  • Ubuntu/Debian: /etc/apache2/sites-available/*.conf (활성화는 a2ensite)
APACHE
# /etc/httpd/conf.d/example.conf

<VirtualHost *:80>
    # 이 VirtualHost가 응답할 기본 도메인
    ServerName www.example.com
    # 동일 콘텐츠로 응답할 추가 도메인 (별칭)
    ServerAlias example.com

    # 정적 파일을 서빙할 루트 디렉터리
    DocumentRoot /var/www/example

    # DocumentRoot에 대한 접근 제어
    <Directory /var/www/example>
        # -Indexes: 파일 목록 노출 금지 (보안)
        Options -Indexes
        # AllowOverride None: .htaccess 무시 (성능 향상)
        AllowOverride None
        # Require all granted: 모든 클라이언트 허용
        Require all granted
    </Directory>

    # 이 VirtualHost 전용 로그 (전역 로그와 분리)
    ErrorLog /var/log/httpd/example-error.log
    CustomLog /var/log/httpd/example-access.log combined
</VirtualHost>

주요 지시어 설명:

지시어역할주의점
ServerName기본 매칭 도메인VirtualHost당 하나
ServerAlias추가 매칭 도메인 (공백 구분)와일드카드 가능 (*.example.com)
DocumentRoot파일 서빙 기준 디렉터리<Directory> 블록과 경로 일치 필요
Options -Indexes디렉터리 인덱스 비활성화운영 환경 필수 보안 설정
AllowOverride None.htaccess 파일 무시성능상 유리, 중앙 집중 관리
Require all granted접근 허용Apache 2.4 문법 (2.2의 Allow from all과 다름)

mod_proxy 리버스 프록시

💡개념

ProxyPass — Apache를 리버스 프록시로 쓰기

Apache를 단순 파일 서버로만 쓰는 시대는 지났습니다. 레거시 환경에서 Apache는 앞단에서 요청을 받아 뒤에 있는 Tomcat, Node.js, Python 앱으로 프록시하는 역할을 합니다. 이 구조를 구현하는 것이 mod_proxy입니다.

모듈 활성화가 먼저입니다. 모듈 없이 ProxyPass를 쓰면 Invalid command 'ProxyPass' 에러가 납니다.

로컬 터미널
# CentOS/RHEL: 모듈 설정 파일 확인
cat /etc/httpd/conf.modules.d/00-proxy.conf

# 아래 줄들이 주석 해제되어 있어야 함:
# LoadModule proxy_module modules/mod_proxy.so
# LoadModule proxy_http_module modules/mod_proxy_http.so
# LoadModule headers_module modules/mod_headers.so

# Ubuntu/Debian: a2enmod로 활성화
sudo a2enmod proxy proxy_http headers
sudo systemctl reload apache2

모듈이 활성화됐다면 VirtualHost 블록 안에서 ProxyPass를 사용합니다:

APACHE
<VirtualHost *:80>
    ServerName api.example.com

    # ProxyPreserveHost On: 원본 Host 헤더를 백엔드로 그대로 전달
    # Off로 하면 백엔드는 127.0.0.1을 Host로 받음
    ProxyPreserveHost On

    # /api/ 로 들어온 요청을 백엔드 8080 포트로 전달
    ProxyPass /api/ http://127.0.0.1:8080/api/

    # 백엔드가 응답에 Location 헤더를 보낼 때 URL을 클라이언트 주소로 재작성
    ProxyPassReverse /api/ http://127.0.0.1:8080/api/

    # 실제 클라이언트 IP와 프로토콜을 백엔드에 전달
    RequestHeader set X-Real-IP "%{REMOTE_ADDR}s"
    RequestHeader set X-Forwarded-Proto "http"
</VirtualHost>

ProxyPass vs ProxyPassReverse:

  • ProxyPass: 요청을 백엔드로 포워딩하는 규칙
  • ProxyPassReverse: 백엔드 응답의 Location, Content-Location, URI 헤더에 포함된 내부 URL을 외부 URL로 재작성

ProxyPassReverse가 없으면 백엔드가 302 Redirect를 보낼 때 클라이언트가 http://127.0.0.1:8080/api/login 같은 내부 주소로 리다이렉트되어 접근 실패가 납니다.

로그 분석과 운영 명령어

💡개념

access_log 포맷 해석과 응답시간 로깅

Apache 로그를 읽지 못하면 장애 원인을 찾는 데 두 배 시간이 걸립니다. 기본 combined 포맷의 각 필드 의미와, 운영에서 반드시 추가해야 하는 응답시간 항목을 익힙니다.

combined 포맷 기본 필드:

%h   %l   %u   %t          "%r"                  %>s  %b
IP  ident user  시각    "메서드 URI 프로토콜"  최종상태 바이트수

실제 로그 예시 한 줄:

OUTPUT
203.0.113.42 - jdoe [30/May/2026:14:23:05 +0900] "GET /api/users HTTP/1.1" 200 1523 "https://example.com" "Mozilla/5.0"
필드의미
%h203.0.113.42클라이언트 IP
%l-identd 사용자 (거의 항상 -)
%ujdoe인증된 사용자 (-이면 미인증)
%t[30/May/2026:...]요청 수신 시각
%rGET /api/users HTTP/1.1요청 라인 전체
%>s200최종 응답 상태코드 (%s와 달리 리다이렉트 후 최종값)
%b1523응답 바이트 수 (헤더 제외)

응답시간 필드 추가 — 슬로우 리퀘스트 탐지 필수:

APACHE
# /etc/httpd/conf/httpd.conf 또는 VirtualHost 블록의 CustomLog 수정
LogFormat "%h %l %u %t \"%r\" %>s %b %T %D" combined_time
CustomLog /var/log/httpd/example-access.log combined_time
필드단위예시설명
%T초 (정수)0150ms → 0으로 기록 (정밀도 낮음)
%D마이크로초150423150ms → 150423으로 기록 (정밀)

슬로우 리퀘스트 분석에는 %D를 사용합니다. %T는 1초 미만 요청은 모두 0으로 보여 실용성이 낮습니다.

로컬 터미널
# 응답시간 기준으로 느린 요청 상위 10개 추출 (%D가 마지막 필드인 경우)
awk '{print $NF, $7}' /var/log/httpd/example-access.log | sort -rn | head -10
💡개념

apachectl 운영 명령어 — graceful vs restart

Apache 설정을 바꿀 때마다 서비스를 완전히 껐다 켜면 처리 중인 요청이 끊깁니다. graceful은 이 문제를 해결합니다.

로컬 터미널
# 설정 문법 검사 (서비스 영향 없음)
sudo apachectl configtest
# 또는 동일하게
sudo apachectl -t

# 시작 / 중지 / 완전 재시작
sudo apachectl start
sudo apachectl stop
sudo apachectl restart        # 프로세스 완전 종료 후 재시작 (순간 중단 발생)

# 무중단 재기동 (권장)
sudo apachectl graceful       # 기존 요청 완료 후 worker 교체, 새 설정 적용

# 무중단 중지 (마지막 요청 완료 후 종료)
sudo apachectl graceful-stop

restart vs graceful 비교:

항목restartgraceful
처리 중 요청즉시 끊김완료될 때까지 대기
설정 반영즉시새 요청부터
다운타임수십ms~수초없음
권장 상황모듈 변경, 바이너리 교체일반 설정 변경
로컬 터미널
# 운영 서버 표준 절차 (검증 후 무중단 반영)
sudo apachectl configtest && sudo apachectl graceful

# error_log 레벨 설정 (httpd.conf 또는 VirtualHost)
# LogLevel warn  ← 기본값 (debug/info/notice/warn/error/crit/alert/emerg)

실습

1VirtualHost 설정 파일 작성

먼저 DocumentRoot 디렉터리를 만들고, VirtualHost 설정 파일을 /etc/httpd/conf.d/example.conf에 작성합니다. CentOS/RHEL 기준입니다. Ubuntu는 경로를 /etc/apache2/sites-available/example.conf로 바꾸고 sudo a2ensite example을 추가로 실행합니다.

APACHE
# /etc/httpd/conf.d/example.conf

<VirtualHost *:80>
    ServerName www.example.com
    ServerAlias example.com
    DocumentRoot /var/www/example

    <Directory /var/www/example>
        Options -Indexes
        AllowOverride None
        Require all granted
    </Directory>

    ErrorLog /var/log/httpd/example-error.log
    CustomLog /var/log/httpd/example-access.log combined
</VirtualHost>

파일을 저장한 뒤 apachectl configtest로 문법을 검증합니다. Syntax OK가 나오면 진행합니다.

sudo mkdir -p /var/www/example && sudo apachectl configtest
🔍VirtualHost 설정 파일 검증 결과
  • apachectl configtest 출력 마지막 줄에 Syntax OK가 나왔는가
  • /etc/httpd/conf.d/example.conf 파일이 생성됐고 ServerName/DocumentRoot 지시어가 포함됐는가
  • /var/www/example 디렉터리가 존재하는가 (ls /var/www/example)
2테스트 파일 생성 및 소유권 설정

Apache worker 프로세스는 apache 유저(Ubuntu는 www-data)로 실행됩니다. DocumentRoot 소유자가 이 유저여야 파일을 읽을 수 있습니다.

로컬 터미널
# 테스트용 index.html 생성
sudo tee /var/www/example/index.html > /dev/null << 'HTMLEOF'
<h1>Apache VirtualHost 동작 확인</h1>
HTMLEOF

# 소유권 변경 (CentOS: apache, Ubuntu: www-data)
sudo chown -R apache:apache /var/www/example
sudo chmod -R 755 /var/www/example

# Apache 프로세스 유저 확인 (불확실한 경우)
grep '^User' /etc/httpd/conf/httpd.conf
sudo chown -R apache:apache /var/www/example
🔍테스트 파일 및 소유권 설정 결과
  • /var/www/example/index.html 파일이 존재하고 내용이 올바른가 (cat /var/www/example/index.html)
  • ls -la /var/www/example/ 에서 소유자가 apache(또는 www-data)로 나왔는가
  • stat /var/www/example 에서 권한이 755로 설정됐는가
3설정 검증 후 무중단 재기동

검증이 통과하면 graceful로 재기동합니다. 운영 서버에서는 항상 이 순서를 지킵니다.

로컬 터미널
# 문법 검사
sudo apachectl configtest

# 문제 없으면 graceful 재기동
sudo apachectl graceful

# Host 헤더를 지정해 VirtualHost 라우팅 확인
curl -H "Host: www.example.com" http://localhost/
sudo apachectl configtest && sudo apachectl graceful
🔍실행 후 확인할 것
  • apachectl configtest 출력에 'Syntax OK'가 나왔는가
  • curl -H 'Host: www.example.com' http://localhost/ 응답에 'Apache VirtualHost 동작 확인'이 포함됐는가
  • /var/log/httpd/example-access.log 파일이 생성됐고 요청이 기록됐는가
  • apachectl graceful 후 httpd 프로세스가 계속 실행 중인가 (ps aux | grep httpd)
4mod_proxy 모듈 확인 및 리버스 프록시 설정

mod_proxy 관련 모듈이 로드됐는지 먼저 확인합니다.

로컬 터미널
# 로드된 모듈 중 proxy 관련 확인
httpd -M | grep proxy

# 출력 예시 (정상):
# proxy_module (shared)
# proxy_http_module (shared)

# CentOS: 모듈 설정 파일 직접 확인
cat /etc/httpd/conf.modules.d/00-proxy.conf

# Ubuntu: 모듈 활성화
sudo a2enmod proxy proxy_http headers
sudo systemctl reload apache2

모듈이 확인되면 리버스 프록시 VirtualHost를 작성합니다:

APACHE
# /etc/httpd/conf.d/api-proxy.conf

<VirtualHost *:80>
    ServerName api.example.com

    ProxyPreserveHost On
    ProxyPass /api/ http://127.0.0.1:8080/api/
    ProxyPassReverse /api/ http://127.0.0.1:8080/api/

    RequestHeader set X-Real-IP "%{REMOTE_ADDR}s"
    RequestHeader set X-Forwarded-Proto "http"

    ErrorLog /var/log/httpd/api-proxy-error.log
    CustomLog /var/log/httpd/api-proxy-access.log combined
</VirtualHost>
httpd -M | grep proxy
🔍실행 후 확인할 것
  • httpd -M | grep proxy 출력에 proxy_module과 proxy_http_module이 모두 있는가
  • apachectl configtest 후 'Syntax OK'가 나왔는가
  • curl -H 'Host: api.example.com' http://localhost/api/health 요청이 백엔드로 전달됐는가
  • 백엔드 로그에서 X-Real-IP 헤더로 실제 클라이언트 IP가 찍혔는가

트러블슈팅

원인: VirtualHost 설정 파일에 오타 또는 잘못된 지시어가 있습니다. Apache는 설정 오류 위치를 파일 경로와 줄 번호로 정확히 알려줍니다.

로컬 터미널
# apachectl configtest 전체 출력 확인
sudo apachectl configtest 2>&1

# 출력 예시:
# AH00526: Syntax error on line 8 of /etc/httpd/conf.d/example.conf:
# Invalid command 'Documnetroot', perhaps misspelled or defined by a
# module not included in the server configuration

# 해당 파일의 해당 줄 확인
sed -n '5,12p' /etc/httpd/conf.d/example.conf

# 수정 후 재검증
sudo apachectl configtest

# 포함된 모든 설정 파일 덤프 확인 (복잡한 include 구조일 때)
sudo apachectl -S 2>&1 | head -30

apachectl -S는 VirtualHost 파싱 결과를 요약해 보여줍니다. 어떤 도메인이 어느 설정 파일로 라우팅되는지 한눈에 확인할 수 있어 복잡한 멀티 VirtualHost 환경에서 유용합니다.

원인: <Directory> 블록이 없거나 Require 지시어가 누락됐습니다. Apache 2.4에서는 명시적으로 접근을 허용하지 않으면 기본 거부됩니다.

로컬 터미널
# error_log에서 정확한 경로 확인
sudo tail -20 /var/log/httpd/example-error.log

# 출력 예시:
# [Mon May 30 14:35:12 2026] [error] [pid 1234] [client 127.0.0.1:54321]
# AH01630: client denied by server configuration: /var/www/example/index.html

# 현재 설정에서 Directory 블록 확인
sudo apachectl -S 2>&1
grep -r "Directory" /etc/httpd/conf.d/example.conf

설정 파일에 <Directory> 블록을 추가합니다:

APACHE
<VirtualHost *:80>
    ServerName www.example.com
    DocumentRoot /var/www/example

    # 이 블록이 없으면 AH01630 발생
    <Directory /var/www/example>
        Options -Indexes
        AllowOverride None
        Require all granted
    </Directory>
</VirtualHost>

Apache 2.2에서 마이그레이션한 설정이라면 Order allow,deny / Allow from all 문법이 남아 있을 수 있습니다. Apache 2.4에서는 Require all granted로 교체해야 합니다.

💼
실무 맥락
현업 패턴

레거시 Java EE 환경 — Apache + Tomcat AJP 연결 구조

고객사 레거시 서버에서 가장 많이 만나는 구조는 Apache httpd가 앞단에서 요청을 받아 AJP 프로토콜로 뒤에 있는 Tomcat에 전달하는 형태입니다.

클라이언트 → Apache httpd (80/443) → AJP (8009) → Tomcat (8080)

AJP 연결 방식은 두 가지입니다:

방식모듈특징
mod_jk별도 설치 필요레거시, workers.properties 설정 파일 별도 관리
mod_proxy_ajpApache 2.2+ 내장설정 단순, 현재 권장

mod_proxy_ajp를 쓴다면 ProxyPass 경로만 ajp://로 바꾸면 됩니다:

APACHE
<VirtualHost *:80>
    ServerName legacy-app.example.com

    ProxyPreserveHost On
    # HTTP 대신 AJP 프로토콜로 Tomcat과 통신
    ProxyPass / ajp://127.0.0.1:8009/
    ProxyPassReverse / ajp://127.0.0.1:8009/
</VirtualHost>

진단 체크리스트 — 처음 보는 Apache 서버에 들어갔을 때:

로컬 터미널
# 1. 버전과 MPM 확인
httpd -V | grep -E "Server version|MPM"

# 2. 로드된 모듈 목록
httpd -M 2>/dev/null | sort

# 3. VirtualHost 파싱 결과 요약
sudo apachectl -S 2>&1

# 4. 활성화된 설정 파일 목록
ls /etc/httpd/conf.d/

# 5. 최근 에러 확인
sudo tail -30 /var/log/httpd/error_log

이 순서대로 5개 명령만 실행해도 낯선 Apache 서버의 전체 구조를 5분 안에 파악할 수 있습니다.

다음 모듈에서는 WAR 배포부터 server.xml 튜닝, 장애 대응까지 Tomcat WAS 운영 전반을 다룹니다.

지식 확인

퀴즈 — 4문제

Q1

Apache에서 VirtualHost를 사용하는 이유로 가장 적절한 것은?

Q2

mod_proxy와 mod_proxy_http를 활성화하지 않고 ProxyPass 지시어를 사용하면 어떻게 되는가?

Q3

apachectl configtest와 apachectl -t의 차이는?

Q4

Apache access_log의 CustomLog 포맷에서 %T와 %D의 차이는?

0 / 4 답변

🧪 실습으로 확인하기

Nginx 설치 및 기동

초급

Linux 서버에 Nginx를 설치하고 systemd 서비스로 등록하여 80포트에서 응답하는 상태까지 만든다.

30📋 3단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요

infra-ops중급 · 65
[Infra Ops] Tomcat 세션 클러스터링과 Redis 세션 외부화
인프라 서비스 운영 트랙 계속
linux입문 · 30
[Linux] 개발자가 왜 리눅스 서버와 커맨드라인을 반드시 배워야 하는가
Linux 트랙 시작점