본문 바로가기
카테고리 없음

[Apache] 소스 컴파일 설치 환경에서 httpd 2.4.66 → 2.4.68 업그레이드 (feat. 사라진 mod_http2의 행방)

by 인 체리 2026. 6. 29.

들어가며

지난번 dev 서버에서 Apache httpd를 2.4.37 → 2.4.68로 업그레이드했던 작업에 이어, 이번엔 staging 서버(이하 webstg)의 httpd를 2.4.66 → 2.4.68로 올리는 CVE 대응 작업을 진행했다.

dev 서버 작업과 가장 큰 차이는 두 가지였다.

  1. RPM이 아니라 외부 소스를 직접 받아서 컴파일 설치한 환경이었다.
  2. 작업 중간에 "분명히 없는 파일을 Apache가 로드하고 있다"는 미스터리에 부딪혔고, 이를 추적하는 과정이 이번 글의 핵심이 되었다.

결과적으로는 별 문제 없이 업그레이드를 마쳤지만, 그 과정에서 겪은 디버깅 흐름을 최대한 그대로 남겨본다.


1. 환경 파악 — httpd -V부터 시작

업그레이드든 뭐든 일단 현재 빌드 정보부터 확인한다.

 
 
bash
$ ./httpd -V
Server version: Apache/2.4.66 (Unix)
Server built:   Feb 15 2026 18:43:44
Server's Module Magic Number: 20120211:141
Server loaded:  APR 1.7.4, APR-UTIL 1.6.3, PCRE 8.45 2021-06-15
Architecture:   64-bit
Server MPM:     event
 -D HTTPD_ROOT="/etc/httpd"
 -D SUEXEC_BIN="/etc/httpd/bin/suexec"
 -D SERVER_CONFIG_FILE="conf/httpd.conf"

httpd -V에는 configure 시 사용한 --enable-xxx 류의 모듈 옵션은 나오지 않는다. 그래서 기존 빌드 설정을 그대로 가져가려면 빌드 당시 생성된 config.nice 파일을 찾는 게 핵심이다.

 
 
bash
$ find / -name "config.nice" 2>/dev/null
/etc/httpd/httpd-2.4.66/srclib/apr/config.nice
/etc/httpd/httpd-2.4.66/srclib/apr-util/config.nice
/etc/httpd/httpd-2.4.66/config.nice
 
 
bash
$ cat /etc/httpd/httpd-2.4.66/config.nice
"./configure" \
"--prefix=/etc/httpd" \
"--with-included-apr" \
"--with-pcre=/etc/httpd/pcre/bin/pcre-config" \
"$@"

옵션은 단순했다.

  • --prefix=/etc/httpd : 설치 경로
  • --with-included-apr : APR/APR-UTIL을 srclib로 동봉해서 빌드
  • --with-pcre=... : 시스템 PCRE가 아니라 별도 설치된 PCRE 8.45 사용

--with-pcre로 지정된 경로가 여전히 살아있는지도 확인했다.

 
 
bash
$ /etc/httpd/pcre/bin/pcre-config --version
8.45

그대로 재사용 가능. 여기까지는 dev 서버 작업과 거의 동일한 흐름이라 순조로웠다.


2. systemd가 없는 운영 구조

dev 서버 때는 PIDFile 경로가 systemd 유닛 파일과 안 맞아서 고생했던 기억이 있어서, 이번에도 미리 확인해봤다.

 
 
bash
$ systemctl status httpd
Unit httpd.service could not be found.

$ ps -ef | grep httpd
root      326295       1  0 Feb16 ?        00:08:52 /etc/httpd/bin/httpd -k start
daemon    170481  326295  0 Jun18 ?        00:01:31 /etc/httpd/bin/httpd -k start
...

$ find /etc/httpd -name "httpd.pid"
/etc/httpd/logs/httpd.pid

이 서버는 systemd로 관리되지 않고, /etc/httpd/bin/httpd -k start를 직접 실행해서 띄우는 구조였다. PID 파일도 기본 경로에 정상적으로 잡혀 있어서, dev 서버 때 겪었던 PIDFile 충돌 이슈는 해당사항이 없었다. 오히려 이번엔 더 단순한 케이스였다.


3. 외부망 차단 확인 → 소스 직접 반입

 
 
bash
$ curl -sI https://dlcdn.apache.org/httpd/httpd-2.4.68.tar.gz
(응답 없음 — 차단)

예상대로 외부 인터넷이 막혀 있어서, httpd-2.4.68.tar.gz를 외부에서 받아 직접 서버로 전송하는 방식으로 진행했다.

APR / APR-UTIL은 새 버전으로 갈지, 기존 버전(1.7.4 / 1.6.3)을 유지할지 고민했는데, 이번엔 httpd 버전 업그레이드 자체에 집중하기로 하고 기존 srclib을 그대로 재사용하는 쪽으로 결정했다.

 
 
bash
# 새 소스 압축 해제
$ cd /etc/httpd
$ tar xzf /tmp/httpd-2.4.68.tar.gz

# 기존 srclib 복사
$ cp -a /etc/httpd/httpd-2.4.66/srclib/apr      /etc/httpd/httpd-2.4.68/srclib/
$ cp -a /etc/httpd/httpd-2.4.66/srclib/apr-util /etc/httpd/httpd-2.4.68/srclib/

# 이전 빌드 캐시 제거 (그대로 복사하면 옛 config.status가 남아서 충돌 위험)
$ cd /etc/httpd/httpd-2.4.68/srclib/apr      && make distclean
$ cd /etc/httpd/httpd-2.4.68/srclib/apr-util && make distclean

여기서 한 가지 배운 점: srclib 디렉토리를 단순 복사만 하면 이전 빌드 산물(Makefile, config.status 등)이 같이 묻어 들어와서 새 configure가 옛 정보를 잘못 참조할 위험이 있다. make distclean으로 한 번 정리해주는 게 안전하다.


4. "분명히 없는데 로드되고 있다" — mod_http2 미스터리

빌드를 본격적으로 시작하기 전에, configure 의존성을 점검하면서 이상한 점을 발견했다.

4-1. 발견의 시작

현재 로드된 모듈을 보니 http2_module이 떠 있었다.

 
 
bash
$ ./httpd -M
...
 ssl_module (shared)
 autoindex_module (shared)
 dir_module (shared)
 alias_module (shared)
 rewrite_module (shared)
 http2_module (shared)

그런데 mod_http2는 보통 **nghttp2 라이브러리의 헤더(devel 패키지)**가 빌드 시점에 있어야 컴파일된다. 헤더가 있는지 확인해봤다.

 
 
bash
$ pkg-config --modversion libnghttp2
(출력 없음)

$ rpm -qa | grep nghttp2
libnghttp2-1.33.0-6.el8_10.1.x86_64

$ dnf list libnghttp2-devel
Error: No matching Packages to list

런타임 라이브러리(.so)는 있는데 헤더(devel)는 레포에 없는 상태였다. 만약 이 상태로 새로 2.4.68을 빌드하면 mod_http2가 빠진 채로 빌드될 가능성이 컸다. 그런데 지금 운영 중인 2.4.66은 이미 mod_http2를 로드하고 있으니 일단 빌드 시점엔 헤더가 있었을 것이라 추정했다.

4-2. 그런데 정작 .so 파일이 없다

확인 차 실제 모듈 파일을 찾아봤는데, 여기서부터 본격적으로 이상해졌다.

 
 
bash
$ ldd /etc/httpd/modules/mod_http2.so
ldd: /etc/httpd/modules/mod_http2.so: No such file or directory

$ stat /etc/httpd/modules/mod_http2.so
stat: cannot statx '/etc/httpd/modules/mod_http2.so': No such file or directory

$ find /etc/httpd -iname "*http2*"
(소스 디렉토리 안의 .c, .h, 문서 파일만 나오고 실제 .so는 없음)

LoadModule http2_module modules/mod_http2.so라는 설정이 있고, httpd -M에도 떠 있는데, 정작 /etc/httpd/modules/ 디렉토리엔 해당 .so 파일이 존재하지 않았다.

4-3. 메모리에 남은 유령 모듈인가?

처음 세운 가설은 "런타임에 이미 dlopen으로 메모리에 매핑된 모듈이라, 파일이 사라진 뒤에도 프로세스는 계속 살아있는 것"이었다. Linux에서는 실행 중인 프로세스가 이미 열어둔 파일을 디스크에서 지워도 프로세스 자체는 영향받지 않기 때문이다.

 
 
bash
$ cat /proc/<마스터PID>/maps | grep "(deleted)"
7faa43fc4000-7faa44019000 rw-s 00000000 00:01 736171   /dev/zero (deleted)

하지만 결과는 /dev/zero (deleted) — APR이 쓰는 익명 공유메모리 흔적일 뿐, mod_http2와는 무관했다. 가설 폐기.

4-4. 진짜 재시작으로 검증

추론만으로는 답이 안 나와서, 가장 정확한 방법으로 갔다. 실제로 stop/start를 해보는 것.

 
 
bash
$ /etc/httpd/bin/httpd -k stop
$ /etc/httpd/bin/httpd -k start
 
 
[ssl:warn] AH01873: Init: Session Cache is not configured [hint: SSLSessionCache]
[mpm_event:notice] AH00489: Apache/2.4.66 (Unix) OpenSSL/1.1.1k configured -- resuming normal operations

깨끗하게 기동됐다. mod_http2 관련 에러는 전혀 없었다. 즉 mod_http2는 실제로 정상 로드되고 있다는 게 확실해졌다. 그렇다면 파일이 없다는 게 모순인데, 정답은 /proc/<PID>/maps를 다시 확인하면서 나왔다.

 
 
bash
$ cat /proc/<새PID>/maps | grep "\.so"
...
7fe2412bb000-7fe2412e0000 r-xp ... /usr/lib64/libnghttp2.so.14.17.0
7fe2414e2000-7fe24151d000 r-xp ... /usr/lib64/httpd/modules/mod_http2.so
7fe241720000-7fe241732000 r-xp ... /etc/httpd/modules/mod_rewrite.so
...

mod_http2.so는 소스 빌드 트리(/etc/httpd/modules/)가 아니라 /usr/lib64/httpd/modules/에서 로드되고 있었다. 즉 우리가 보던 경로가 틀렸던 것이다.

4-5. 범인 확정 — RPM 패키지

 
 
bash
$ history | grep http2
616  dnf install -y mod_http2

dnf install -y mod_http2 RPM 설치 흔적이 남아 있었다. RPM이 실제로 깐 파일 목록을 확인했다.

 
 
bash
$ rpm -ql mod_http2
/etc/httpd/conf.modules.d/10-h2.conf
/etc/httpd/conf.modules.d/10-proxy_h2.conf
/usr/lib64/httpd/modules/mod_http2.so
/usr/lib64/httpd/modules/mod_proxy_http2.so

RHEL의 mod_http2 RPM 패키지는 표준적으로 .so는 /usr/lib64/httpd/modules/에, conf 조각 파일은 /etc/httpd/conf.modules.d/에 설치한다. 그런데 우리 서버는 소스 컴파일 설치라서 ServerRoot도 똑같이 /etc/httpd로 잡혀 있었고, 이 디렉토리 구조가 RPM의 표준 레이아웃과 우연히 겹쳤던 것이다.

다만 conf.modules.d/10-h2.conf의 내용은 상대경로였다.

 
 
bash
$ cat /etc/httpd/conf.modules.d/10-h2.conf
LoadModule http2_module modules/mod_http2.so

이 상대경로는 ServerRoot 기준으로 풀면 /etc/httpd/modules/mod_http2.so가 되어 여전히 없는 파일을 가리킨다. 그런데 정작 httpd.conf 안에는 이렇게 되어 있었다.

 
 
bash
$ grep -n "http2_module" /etc/httpd/conf/httpd.conf
154:LoadModule http2_module /usr/lib64/httpd/modules/mod_http2.so

httpd.conf에 절대경로로 직접 LoadModule이 박혀 있었다. 누군가 RPM 설치 후 정상 동작을 위해 conf를 수동으로 고쳐놓은 것이었다. conf.modules.d의 파일들은 Include 대상이 아니어서 (httpd.conf의 Include 목록엔 security.conf, httpd-ssl.conf, httpd-vhosts.conf만 있었음) 처음부터 무시되는 죽은 파일이었던 것이고, 실제로 모듈을 로드시키는 건 httpd.conf 154번 줄이었다.

긴 추적 끝에 결론은: mod_http2는 시스템 RPM 기반으로 별도 운영되고 있고, 이 구조는 의도된 정상 구성이라는 것. 같은 패턴이 운영 서버에도 동일하게 적용되어 있는지 추가로 비교 확인했고, 동일한 구조임을 확인했다 (RPM 버전, conf 절대경로 방식 모두 일치).


5. 빌드 및 설치

미스터리가 풀리고 나니, mod_http2 재빌드를 위한 libnghttp2-devel 설치는 불필요하다는 게 명확해졌다. 시스템 RPM에 계속 의존하면 되기 때문이다.

configure

 
 
bash
$ cd /etc/httpd/httpd-2.4.68
$ ./configure \
    --prefix=/etc/httpd \
    --with-included-apr \
    --with-pcre=/etc/httpd/pcre/bin/pcre-config
 
 
configure: summary of build options:
    Server Version: 2.4.68
    Install prefix: /etc/httpd
    C compiler:     gcc
    CFLAGS:          -g -O2 -pthread

에러 없이 완료.

make

 
 
bash
$ make 2>&1 | tee /tmp/httpd_2.4.68_make.log

빌드된 모듈 개수를 기존과 대조해서 빠진 게 없는지 확인했다.

 
 
bash
$ find /etc/httpd/httpd-2.4.68 -name "*.so" | xargs -n1 basename | sort

기존 운영 모듈 86개와 1:1로 동일하게 빌드 완료. (mod_http2, mod_proxy_http2는 당연히 빠지는데, 이건 위에서 확인했듯 시스템 RPM에서 로드되는 게 맞으므로 정상이다.)

설치 전 백업

 
 
bash
$ cp -a /etc/httpd /etc/httpd_backup_$(date +%Y%m%d_%H%M)

설치 (다운타임 발생 구간)

 
 
bash
$ /etc/httpd/bin/httpd -k stop
$ cd /etc/httpd/httpd-2.4.68
$ make install 2>&1 | tee /tmp/httpd_2.4.68_install.log

설치 후 가장 중요한 검증 — conf 보존 확인

make install이 기존 운영 설정을 덮어쓰지 않았는지가 핵심 체크포인트였다. 특히 httpd.conf 154번 줄(http2 절대경로 LoadModule)이 살아있는지가 제일 중요했다.

 
 
bash
$ grep -n "http2_module" /etc/httpd/conf/httpd.conf
154:LoadModule http2_module /usr/lib64/httpd/modules/mod_http2.so
 
 
bash
$ diff -rq /etc/httpd_backup_*/conf /etc/httpd/conf
Files .../conf/original/extra/httpd-autoindex.conf and ... differ
Files .../conf/original/extra/httpd-languages.conf and ... differ
Files .../conf/original/extra/httpd-multilang-errordoc.conf and ... differ
Files .../conf/original/httpd.conf and ... differ

diff 결과 차이가 난 파일들은 모두 conf/original/ 디렉토리 안의 참고용 기본 샘플 파일들로, Apache 소스 배포판이 새 버전의 기본값으로 갱신해놓은 것이었다. 실제 운영 설정(httpd.conf, conf/extra/httpd-vhosts.conf, conf/extra/httpd-ssl.conf)은 전혀 영향받지 않았다.

 
 
bash
$ diff -rq /etc/httpd_backup_*/conf/extra /etc/httpd/conf/extra
(차이 없음)

6. 기동 및 최종 검증

 
 
bash
$ /etc/httpd/bin/httpd -V
Server version: Apache/2.4.68 (Unix)
Server built:   Jun 29 2026 15:03:56
Server's Module Magic Number: 20120211:142

버전 업그레이드 확인. 다만 Module Magic Number(MMN)가 141 → 142로 바뀐 점은 짚어볼 부분이었다. MMN이 바뀌면 호환되지 않는 외부 모듈을 거부할 수 있는데, mod_http2가 시스템 RPM(2.4.66 기준 빌드로 추정) 모듈이라 혹시 충돌이 있을지 확인이 필요했다.

 
 
bash
$ /etc/httpd/bin/httpd -t
Syntax OK

$ /etc/httpd/bin/httpd -k start

$ /etc/httpd/bin/httpd -M | grep -i http2
 http2_module (shared)

$ grep -i "module magic\|MMN\|version mismatch" /etc/httpd/logs/error_log
(없음)

/proc/<PID>/maps로 최종 확인.

 
 
bash
$ cat /proc/<새PID>/maps | grep -i "http2\|nghttp2"
.../libnghttp2.so.14.17.0
.../usr/lib64/httpd/modules/mod_http2.so

MMN이 바뀌었음에도 별다른 호환성 문제 없이 정상 로드 완료. HTTP/2 응답도 정상 확인했다.


정리

항목내용
버전 Apache 2.4.66 → 2.4.68
설치 방식 소스 컴파일 유지 (--prefix=/etc/httpd --with-included-apr --with-pcre=...)
APR / APR-UTIL 1.7.4 / 1.6.3 동일 버전 유지
PCRE 8.45 기존 설치본 재사용
모듈 빌드 동적 모듈 86개 전부 정상 빌드
mod_http2 시스템 RPM(/usr/lib64/httpd/modules/) 기반, 소스 빌드와 분리 운영 — 그대로 유지
운영 설정 영향 없음 (conf/original/ 샘플만 갱신)
다운타임 stop → install → start 구간만

이번 작업에서 가장 시간을 많이 쓴 부분은 사실 버전업 자체가 아니라 "존재하지 않는 파일을 Apache가 로드하고 있다"는 모순을 추적하는 과정이었다. 결론적으로는

  • mod_http2가 시스템 RPM으로 설치되어 있고
  • .so는 RPM 표준 경로(/usr/lib64/httpd/modules/)에,
  • conf 조각 파일은 우연히 소스 빌드 트리와 겹치는 위치(/etc/httpd/conf.modules.d/)에 떨어졌고
  • 실제로는 누군가 httpd.conf에 절대경로를 직접 박아넣어 정상 동작하게 만든 구조

라는 걸 ps, find, rpm -ql, /proc/<PID>/maps 까지 동원해서 하나씩 검증했다. 비슷한 구조(외부 패키지 모듈 + 소스 컴파일 설치가 혼재된 환경)를 운영하는 곳이라면, conf.modules.d 같은 표준 위치에 파일이 있다고 해서 그게 곧 "사용 중"이라는 뜻은 아니라는 점, 그리고 Include 대상인지 먼저 확인해야 한다는 점을 다시 한번 새기게 된 작업이었다.

다음은 운영 서버(web01) 적용 차례인데, 이번 스테이징에서 구조를 완전히 검증해뒀기 때문에 동일한 절차로 진행하면 될 것 같다.