PHP / Ubuntu / Apache / HTML 확장자에도 PHP 코드 인식되도록 설정하는 방법

Apache에서 .html 파일의 PHP 코드를 실행하려면 해당 파일을 PHP 처리기로 보내는 설정이 필요합니다. PHP-FPM(FastCGI Process Manager, PHP 요청을 별도 프로세스로 처리하는 관리자)을 사용한다면 Apache의 SetHandler뿐 아니라 FPM 풀의 security.limit_extensions에도 .html을 허용해야 합니다. 필요한 레거시 코드 디렉터리에만 적용하고, 가능하면 PHP 파일은 .php로 구분하는 방식을 유지하세요.

현재 PHP 실행 방식과 적용 범위 확인

아래 FPM 예제는 Ubuntu 24.04 LTS·Apache 2.4·PHP 8.3-FPM이 설치되어 작동하는 환경을 기준으로 합니다. root 또는 sudo 권한과 서버 설정 편집 권한이 필요합니다. Ubuntu 24.04 릴리스 설명의 기본 PHP는 8.3 계열입니다. 보안 업데이트가 적용된 PHP 패키지를 준비하세요. 다른 버전이라면 실제 바이너리·서비스·설정 경로에 맞춰 바꾸어야 하며, 8.3이라는 경로만 다른 버전 서버에 복사하지 마세요.

sudo apache2ctl -M
sudo apache2ctl -S

Apache 모듈 목록에서 php_module은 Apache에 PHP를 직접 넣는 방식, proxy_fcgi_module은 FPM 같은 FastCGI 서버로 전달할 수 있는 모듈입니다. 모듈이 있다는 사실만으로 모든 가상 호스트의 실행 방식이 같지는 않으므로 해당 사이트 설정도 확인합니다. PHP 명령행의 php -v만으로 웹 요청의 실행 방식을 판단할 수는 없습니다.

새 구성은 PHP-FPM을 우선 검토하세요. PHP 공식 Apache 설치 안내도 새 설치에서 FPM을 권장합니다. PHP 설치부터 필요하다면 Ubuntu PHP 구성 문서를 먼저 확인하세요. 기존 사이트 전체의 PHP 실행 방식을 이 작업 때문에 임의로 전환하지는 않습니다.

예제는 기존 사이트와 구분하기 위해 /srv/legacy-html-demo/public과 로컬 주소 127.0.0.1:8080을 사용합니다. 해당 디렉터리·설정 파일·포트가 이미 사용 중이면 덮어쓰거나 중복 등록하지 말고 다른 실습 이름과 포트를 정하세요. 기존 설정을 고치는 경우에는 변경 전 원본과 실제 적용 파일을 먼저 보관합니다.

PHP-FPM: 전용 풀에서 확장자 허용

서버의 www 풀을 공유하는 모든 사이트에 일괄 허용하기보다, 이 디렉터리용 FPM 풀을 별도로 구성합니다. 다음 명령으로 새 파일을 열고 아래 설정 전체를 저장하세요. 이미 파일이 있으면 내용을 덮어쓰지 말고 소유한 기존 설정인지부터 확인합니다.

sudoedit /etc/php/8.3/fpm/pool.d/legacy-html-demo.conf
[legacy_html_demo]
user = www-data
group = www-data
listen = /run/php/php8.3-fpm-legacy-html-demo.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = ondemand
pm.max_children = 3
pm.process_idle_timeout = 10s
security.limit_extensions = .php .html

Apache가 사용할 소켓의 이름·소유자·권한을 지정하고, 이 풀에서만 .php·.html을 메인 스크립트로 해석하도록 합니다. pm.max_children=3 등은 작은 실습용 선택이며 운영 부하에 대한 권장값이 아닙니다. 같은 www-data 계정을 쓴다는 점에서 별도 OS 사용자로 격리한 구성과도 다릅니다.

PHP-FPM 설정 문서에서 security.limit_extensions는 잘못된 웹 서버 설정 때문에 다른 확장자가 실행되는 것을 막는 보호 장치입니다. 기본 허용값은 .php .phar이며 여기서는 신뢰할 수 있는 기존 HTML 코드 실행이라는 요구에 맞춰 전용 풀에서 명시적으로 .html을 추가했습니다. 제한값을 빈 값으로 만들거나 업로드 파일을 실행할 수 있는 공용 풀에 무작정 적용하면 안 됩니다.

Apache: 필요한 디렉터리를 FPM 소켓에 연결

새 실습 디렉터리를 만듭니다. 첫 mkdir이 이미 존재한다는 오류를 내면 중단하고 대상 경로를 확인하세요. 웹 서버가 파일을 읽을 수 있어야 하지만, PHP 코드 디렉터리 전체를 업로드 사용자가 쓰도록 만들 필요는 없습니다.

sudo mkdir -m 0755 /srv/legacy-html-demo && sudo mkdir -m 0755 /srv/legacy-html-demo/public
sudoedit /etc/apache2/sites-available/legacy-html-demo.conf

가상 호스트 파일에 다음 전체 내용을 저장합니다. Listen은 VirtualHost 밖에 두며 로컬 인터페이스의 8080 포트만 추가합니다. 이미 같은 주소·포트의 Listen이 있는 환경에서는 새로 중복 추가하지 말고 현재 리스너 구성을 먼저 확인해야 합니다.

Listen 127.0.0.1:8080
<VirtualHost 127.0.0.1:8080>
    ServerName legacy-html-demo.local
    DocumentRoot /srv/legacy-html-demo/public
    <Directory /srv/legacy-html-demo/public>
        Options -Indexes
        AllowOverride None
        Require local
        <FilesMatch "\.(?:php|html)$">
            SetHandler "proxy:unix:/run/php/php8.3-fpm-legacy-html-demo.sock|fcgi://localhost/"
        </FilesMatch>
    </Directory>
    ErrorLog ${APACHE_LOG_DIR}/legacy-html-demo-error.log
    CustomLog ${APACHE_LOG_DIR}/legacy-html-demo-access.log combined
</VirtualHost>

FilesMatch는 이 디렉터리 안의 .php·.html에만 PHP 처리기를 지정합니다. 소켓 경로는 앞 FPM 풀의 listen과 정확히 같아야 합니다. Apache의 SetHandler와 mod_proxy_fcgi의 처리기 연결 예제를 참고하세요. 루프백 Listen의 범위는 Listen 문서에 설명되어 있습니다.

.htm도 필요하면 정규식을 \.(?:php|html|htm)$로 바꾸고 FPM 풀의 허용 목록에도 .htm을 함께 추가해야 합니다. 한쪽만 바꾸면 요청이 PHP로 전달되지 않거나 FPM에서 거절될 수 있습니다. .HTML 같은 대문자 확장자가 실제로 있다면 파일 이름 정책과 정규식 일치 조건도 별도로 검토하세요.

검사 후 적용하고 실제 응답 확인

FPM 구성에서는 아래 순서로 진행합니다. 구문 검사에 실패하면 다음 reload를 실행하지 말고 오류에 나온 파일·행을 고칩니다. 새 풀을 먼저 준비한 다음 Apache에서 연결합니다.

sudo a2enmod proxy_fcgi
sudo php-fpm8.3 -t && sudo systemctl reload php8.3-fpm
sudo a2ensite legacy-html-demo.conf
sudo apache2ctl configtest && sudo systemctl reload apache2

apache2ctl configtest의 Syntax OK는 Apache 설정 문법 검사이며 FPM 접속이나 PHP 실행 성공까지 뜻하지 않습니다. FPM 검사·서비스 상태·HTTP 응답까지 따로 확인하세요. 설정을 수정한 뒤 오류가 난 상태를 피하려고 검사를 생략하고 강제로 reload하지 않습니다.

public 안에 check.php와 check.html 두 파일을 만들고 둘 모두 다음 전체 코드를 넣습니다. sudoedit /srv/legacy-html-demo/public/check.php와 같은 방법으로 저장할 수 있습니다. 서버 정보 전체를 노출하는 phpinfo() 대신 동작 여부와 실행 방식만 확인합니다.

<?php
header('Content-Type: text/plain; charset=UTF-8');
echo 'PHP_EXECUTED', PHP_EOL;
echo 'SAPI: ', PHP_SAPI, PHP_EOL;
sudo chmod 0644 /srv/legacy-html-demo/public/check.php /srv/legacy-html-demo/public/check.html
curl --fail --show-error http://127.0.0.1:8080/check.php
curl --fail --show-error http://127.0.0.1:8080/check.html

SAPI(Server Application Programming Interface)는 PHP의 실행 인터페이스를 뜻합니다. FPM 구성에서는 두 응답 모두 첫 줄 PHP_EXECUTED, 둘째 줄 SAPI: fpm-fcgi여야 합니다. 응답 안 어딘가에 PHP_EXECUTED라는 글자가 있다는 것만 확인하지 마세요. PHP 코드가 그대로 반환된 경우에도 그 문자열은 들어 있습니다. <?php나 echo 등 원본 코드가 보이거나 파일이 다운로드되면 실행 설정이 잘못된 것이므로 비밀정보가 든 파일을 올리기 전에 접근을 차단하고 원인을 고칩니다.

403과 FPM 로그의 확장자 거절은 security.limit_extensions 및 실제 요청이 연결된 풀을, 503은 FPM 서비스·소켓 경로·권한을 우선 확인합니다. 다른 사이트에만 영향이 있으면 가상 호스트·Directory·FilesMatch 적용 범위와 충돌하는 기존 처리기를 검토하세요. 로그는 /var/log/apache2/legacy-html-demo-error.log와 해당 PHP-FPM 오류 로그에서 확인하되, 실제 서버의 설정 경로가 다르면 그 값을 따릅니다.

기존 Apache PHP 모듈 방식을 유지하는 경우

이미 php_module을 사용하는 기존 서버에서만 이 대안을 검토합니다. 이 방식을 쓰려고 FPM 서버에 새 mod_php를 추가 설치하거나 MPM을 임의로 바꿀 필요는 없습니다. 앞의 전용 FPM 풀 설정을 적용하지 않고, 가상 호스트의 FilesMatch 블록만 다음 내용으로 바꿉니다. 나머지 문서 루트·로컬 접근 제한·Listen 범위는 유지합니다.

<FilesMatch "\.(?:php|html)$">
    SetHandler application/x-httpd-php
</FilesMatch>

이 경우에는 FPM 관련 a2enmod·php-fpm 명령을 실행하지 않습니다. 사이트를 활성화하고 Apache 문법 검사를 통과한 후 reload한 뒤 같은 check.php·check.html을 요청합니다. SAPI는 apache2handler로 표시되어야 합니다. SetHandler는 실제 PHP 모듈이 있어야 의미가 있으므로 FPM 환경에서 이 처리기 이름만 복사하면 같은 결과를 기대할 수 없습니다.

과거의 AddType application/x-httpd-php .html 예제는 모듈·MIME 설정의 연결 방식에 의존합니다. MIME 형식 지정만으로 모든 FPM 구성에서 PHP 실행이 활성화되는 것은 아닙니다. 현재 방식에 맞는 명시적인 처리기를 해당 범위에 설정하고 응답으로 확인하는 편이 적용 조건을 분명하게 드러냅니다.

복구 순서와 적용 후 관리

이 로컬 예제를 되돌릴 때는 먼저 사이트를 비활성화하고 Apache 검사를 통과한 뒤 reload합니다. 가상 호스트 파일에 둔 Listen도 함께 적용에서 빠지므로 8080 실습 요청이 더 이상 제공되지 않는지 확인합니다. PHP 코드가 남은 .html을 공개한 채 처리기부터 제거하면 소스가 정적 파일로 노출될 수 있다는 순서를 특히 주의하세요.

sudo a2dissite legacy-html-demo.conf
sudo apache2ctl configtest && sudo systemctl reload apache2

FPM 대안을 적용했다면 이어서 자신이 새로 만든 legacy-html-demo.conf 풀 파일을 덮어쓰지 않는 별도 이름으로 보관하고, pool.d에서 .conf 확장자로 읽히지 않도록 합니다. php-fpm8.3 -t를 통과한 뒤 해당 FPM 서비스를 reload합니다. 다른 사이트가 쓰는 공용 풀이나 모듈을 함께 비활성화하지 마세요. 테스트 파일은 웹 제공 범위 밖으로 옮기거나 불필요해진 뒤 정리합니다.

운영 사이트에서는 변경 전 설정으로 돌아가는 동안 PHP 코드가 있는 .html의 접근을 먼저 차단하거나 정적 파일로 복원하는 계획이 필요합니다. 업로드·첨부 파일과 신뢰할 수 없는 HTML을 이 처리 범위에 섞지 않고, 보통의 정적 HTML까지 전부 PHP 작업자로 보내 생기는 처리 비용과 캐시 동작도 검토하세요. 확장자를 숨기는 설정만으로 PHP나 애플리케이션의 보안이 강화되는 것은 아닙니다.