WordPress 사이트를 운영하는 서버에 정적 웹앱을 서브도메인으로 추가해야 할 때가 있습니다. 플러그인 캐시 문제(FlyingPress, WP Rocket 등)를 피하거나 WordPress 의존성 없이 독립 실행하려는 경우가 그 예입니다.
목표는 WordPress 애플리케이션과 별도의 DocumentRoot로 정적 서브도메인을 제공하는 것입니다. 같은 Apache 프로세스와 서버 자원을 공유하므로, VHost·인증서 설정 변경이나 서비스 재시작이 기존 WordPress에 영향을 주지 않는다는 뜻은 아닙니다.
새 서브도메인의 연결 경로와 기존 WordPress origin의 접근 경계를 함께 검토해야 합니다. 정적 콘텐츠이거나 WordPress 캐시 플러그인을 피한다는 이유만으로 직접 연결이 안전하다고 판단할 수 없습니다. 아래 표는 검토할 선택지이며 자동 적용 권고가 아닙니다.
직접 연결을 허용하기 전에는 같은 IP의 다른 Host/SNI가 기존 WordPress에 도달하는지, 기본 VHost와 origin 접근 제한이 어떻게 구성되는지 별도 승인을 받아 검토해야 합니다. IP 주소 요청의 200/403, Server 헤더 또는 DNS 이름만으로 WAF 경유·우회 차단이나 올바른 TLS 검증을 입증할 수 없습니다. -k는 인증서 검증을 생략하므로 안전한 연결의 증거가 아닙니다. 이 글은 현재 서버의 WAF 우회 가능 여부를 검증한 결과가 아닙니다.
# WAF 경유 확인
curl -sI http://www.datainhands.com | grep "Server"
# Server: ZENEDGE → OCI WAF 경유
# 서버 직접 접속 가능 여부
curl -v http://서버IP --connect-timeout 10
# Connection refused → 80 차단 (WAF만 통과)
curl -v https://서버IP -k --connect-timeout 10
# 200/403 → 443 직접 접속 가능
| 조건 | 검토할 경로 | 이유 |
|---|---|---|
| 정적 HTML, 민감 데이터 없음 | 직접 연결 (A레코드) | certbot 사용, 갱신 예약 확인 필요 |
| 동적 앱, 보안 필수 | WAF 경유 (CNAME) | DDoS 방어, 봇 차단 |
| FlyingPress 등 캐시 회피 | 직접 연결 | WordPress 요청 처리와 분리 (서버 자원은 공유) |
Apache Listen은 방화벽 규칙이 아니라 서비스가 연결을 받을 주소·포트 설정입니다. 직접 HTTPS 연결에 항상 80 포트가 필요한 것은 아닙니다. HTTP 서비스나 HTTP-01 인증 검증 경로가 필요한 경우에는 80 포트의 도달성을 별도로 설계합니다. 아래 포트 개방·재시작 예제는 일괄 실행 절차가 아닙니다. 실제 VNIC의 Security Lists와 NSGs, 호스트 방화벽 및 기존 WordPress 보호 경계를 함께 검토한 뒤 필요한 최소 접근 범위를 결정해야 합니다.
# Apache 리스닝 포트 확인
sudo ss -tlnp | grep -E ":80 |:443 "
# 443만 있으면 80 추가 필요
# httpd.conf에서 Listen 80 주석 해제
grep "Listen 80" /etc/httpd/conf/httpd.conf
# #Listen 80 ← 주석 처리됨
sudo sed -i 's/^#Listen 80$/Listen 80/' /etc/httpd/conf/httpd.conf
sudo apachectl configtest && sudo systemctl restart httpd
DNS 관리(name.com, Cloudflare 등)에서 studio A레코드 → 서버 IP. TTL 300.
dig studio.datainhands.com @8.8.8.8 +short
# <서버IP>
sudo mkdir -p /var/www/studio
echo "<h1>studio OK</h1>" | sudo tee /var/www/studio/index.html
sudo chown -R apache:apache /var/www/studio
sudo tee /etc/httpd/conf.d/studio.conf << 'EOF'
<VirtualHost *:80>
ServerName studio.datainhands.com
DocumentRoot /var/www/studio
<Directory /var/www/studio>
Require all granted
</Directory>
</VirtualHost>
EOF
sudo apachectl configtest && sudo systemctl reload httpd
sudo certbot --apache -d studio.datainhands.com
Apache 플러그인은 인증서 발급·설치를 지원하며, HTTPS·리다이렉트 설정 결과는 옵션과 기존 설정에 따라 달라집니다. 자동갱신 예약은 설치 방식에 따라 제공되는 cron 또는 systemd timer 등 별도 구성에 달려 있으므로, 이 발급 명령만으로 등록·실행 여부를 단정할 수 없습니다. 예약 작업은 갱신 필요 여부를 점검하며 매번 인증서를 교체하지는 않습니다. renew --dry-run은 시험 갱신으로 실제 인증서 갱신과 다르고, 웹서버 reload나 pre/post hook이 실행될 수 있습니다. deploy hook은 보통 실제 갱신 성공 후 실행되며 dry-run에서는 별도 옵션 없이 실행되지 않습니다. 갱신된 인증서 반영을 위한 reload 등은 installer/hook과 서비스 구성에 따라 달라집니다. 이 글은 현재 서버의 예약·hook·reload 동작을 확인한 기록이 아닙니다.
certbot이 생성한 studio-le-ssl.conf에 보안 헤더와 캐시 정책 추가:
캐시 예제의 적용 조건: 아래의 1년 immutable 설정은 캐시 신선도 기간 동안 내용이 바뀌지 않는 URL에만 해당합니다. 이 글의 동일 파일명 덮어쓰기 배포에 그대로 적용하지 마세요. 콘텐츠 해시나 버전이 포함된 새 URL로 에셋을 배포하고 HTML 참조도 함께 바꾼 경우에만 장기 캐시를 검토합니다.
같은 URL로 갱신하는 파일과 HTML은 재검증 가능한 정책을 별도로 설계합니다. 예를 들어 no-cache는 저장 금지가 아니라 재사용 전 검증을 요구합니다. 기존 브라우저의 신선한 immutable 응답은 서버 헤더를 바꾸거나 CDN 캐시를 지워도 즉시 회수되지 않을 수 있습니다. 실제 헤더 우선순위와 배포·복구 방식은 환경 검토 후 결정하세요.
# 보안 헤더
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set Strict-Transport-Security "max-age=63072000"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# 정적 에셋 1년 캐시
<FilesMatch "\.(js|css|png|jpg|svg|woff2?)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
# Gzip 압축
AddOutputFilterByType DEFLATE text/html text/css application/javascript
sudo cp -r studio-files/* /var/www/studio/
sudo chown -R apache:apache /var/www/studio/
아래 헤더 조회는 응답 상태를 확인하는 예시입니다. 코드 주석의 “기존 사이트 영향 없음”은 이 조회만으로 입증되지 않습니다. HTTP 200만으로 기존 사이트의 기능·성능이나 배포·복구의 무영향을 보장할 수 없습니다.
# studio 정상
curl -sI https://studio.datainhands.com | grep -E "HTTP|X-|Strict|Cache"
# HTTP/1.1 200 OK + 보안 헤더 + 캐시 헤더
# 기존 사이트 영향 없음
curl -sI https://datainhands.com | head -3
# HTTP/1.1 200 OK
WordPress 플러그인 JS가 wp_localize_script로 설정을 받는 경우:
// JS에서 이렇게 사용 중
const cfg = window.PatternStudioConfig || {};
const persistEnabled = !!cfg.persistInput;
const aiCfg = cfg.ai || {};
|| {}는 설정 객체가 없을 때 빈 객체를 사용하는 폴백이며, 앱이 WordPress 없이 동작한다는 보장은 아닙니다. REST/Ajax 요청, nonce·인증, 서버 데이터와 다른 플러그인 의존성을 먼저 확인하세요. 아래 설정 삽입은 외부 의존성이 없고 해당 설정 형식을 지원하는 앱에 적용할 수 있는 예시입니다. 일반 설정 데이터 전달에는 wp_add_inline_script도 검토합니다.
<script>
window.PatternStudioConfig = {
version: '2.5.3',
persistInput: false,
locale: navigator.language,
ai: { enabled: false }
};
</script>
<script src="assets/js/pattern-studio.js"></script>
VHost 파일 제거만으로 배포 전 상태가 모두 복원되지는 않습니다. 아래 삭제·재시작 예제는 완전한 롤백 절차가 아니며, 그대로 실행하지 마세요. 같은 Apache 프로세스를 공유하는 WordPress에도 영향이 있을 수 있습니다.
배포 전에 변경 항목별 이전 값·백업·복구 순서를 기록하세요: DNS 레코드와 프록시 경로, Apache VHost와 공유 Listen 설정, 네트워크·호스트 방화벽, 배포 파일과 소유권, 인증서 참조와 Certbot 갱신 설정입니다. 비밀키 내용은 기록에 포함하지 않습니다.
복구 시에는 이번 배포에서 바꾼 항목만 대상으로 다른 서비스의 의존성을 확인합니다. VHost를 백업한 뒤 비활성화하고 구문 검사를 거쳐 환경에 맞는 reload/graceful 또는 restart를 선택하며, 기존 WordPress와 새 서브도메인을 각각 검증합니다. VHost 제거가 인증서나 갱신 설정을 자동 정리하지 않으며, DNS·캐시 전파가 끝나기 전에는 복구 완료를 단정할 수 없습니다.
설명용 예제 — 실행하지 마세요. 아래 두 줄에는 백업·의존성 확인·구문 검사·실패 시 중단 단계가 없습니다. 첫 줄은 VHost 파일을 삭제하며, 둘째 줄은 첫 줄의 성공 여부를 조건으로 연결하지 않은 Apache 재시작 명령입니다. 이 블록을 복사해 실행하는 것으로 위 복구 절차를 대신할 수 없습니다.
sudo rm /etc/httpd/conf.d/studio.conf /etc/httpd/conf.d/studio-le-ssl.conf
sudo systemctl restart httpd