정본 호스트 강제 (Canonical Host)
DNS 에만 등록된 호스트(대표적으로 www.)가 앱까지 도달하면 같은 사이트가 두 호스트에서 각자 자기를 정본이라 선언하게 됩니다 —
canonical·og:url·hreflang 은 물론 robots.txt 의 Sitemap: 과 sitemap <loc> 까지 그 호스트로 생성되기 때문입니다
(Laravel 의 route(…, absolute: true)·url() 은 현재 요청의 host 를 상속합니다). 검색엔진이 그중 어느 쪽을 정본으로 고를지는
운영자가 통제할 수 없고, 제출한 sitemap 과 다른 쪽이 선택되면 제출한 URL 들이 「크롤링됨–미색인」으로 남습니다.
CanonicalHostMiddleware 는 허용되지 않은 호스트로 들어온 요청을 프로젝트 정본 host(APP_URL)로 영구 리다이렉트합니다.
| 요청 | 응답 |
|---|---|
| 미등록 host 의 GET / HEAD | 301 → https://<정본>/<같은 경로>?<같은 쿼리> · Cache-Control: public, max-age=<cache_max_age> |
| 미등록 host 의 POST / PUT / PATCH / DELETE | 308 (메서드·본문 보존 — 301 은 GET 으로 바뀌어 폼을 잃습니다) |
| 허용 host | 변경 없음 |
허용 호스트는 어떻게 정해지나
목록을 따로 관리하지 않습니다. 도메인 라우팅이 이미 쓰는 것과 같은 해석기로 판정합니 다.
APP_URL의 host (정본)CORE_CANONICAL_HOST_ADDITIONAL— Core 가 알 수 없는 프로젝트 고유 호스트 (예: 별도 표면·진입 전용 서브도메인)- 미러 도메인 (
settings.mirror_domains) - SaaS / Tenant 의
settings.domains(→ Tenant Domains & Sites) - Tenant 의
primary_domain·domain_aliases· 선언된subdomain_mapping
⚠️ 5 에서 선언되지 않은 서브도메인은 허용하지 않습니다. Tenant 의 primary_domain 이 정본과 같은 경우 www.<정본> 도
"그 tenant 의 서브도메인"으로 해석되는데, 그것을 허용하면 이 기능이 막으려던 바로 그 호스트가 통과합니다.
항상 통과하는 것 (과잉 차단 방지)
- 기능이 꺼져 있거나(
enabled=false)APP_URL이 비어 정본을 모를 때 — 아무것도 하지 않습니다 localhost·*.localhost· IP 리터럴 Host — 도커 헬스체크·업타임 모니터·내부 프로브가 이 Host 로 옵니다CORE_CANONICAL_HOST_EXCLUDE경로 (기본up,health,healthz,ping,status)- 서버가 거부하는 이상한 Host 헤더
설정
CORE_CANONICAL_HOST_ENABLED=false # 기본 OFF — 프로젝트별로 켭니다
CORE_CANONICAL_HOST_ADDITIONAL= # 콤마 구분. 예: eink.<정본>,read.<정본>
CORE_CANONICAL_HOST_EXCLUDE=up,health,healthz,ping,status
CORE_CANONICAL_HOST_CACHE_MAX_AGE=86400
켜기 전 필수 — 누락하면 정상 호스트가 301 되어 사이트가 죽습니다
- 프로젝트가 자기 방식으로 인식하는 호스트(별도 표면·진입 전용 서브도메인 등)를 전부
CORE_CANONICAL_HOST_ADDITIONAL에 적습니다. Core 의settings.domains/primary_domain에 없는 호스트는 여기 없으면 301 됩니다. - 켜기 전에 실제 호스트 전부에 대해 판정을 dry-run 으로 확인합니다 (tinker):
$r = app(\App\Core\Base\Routing\Services\CanonicalHostResolver::class);
foreach (['<정본>', 'www.<정본>', '<별도표면>.<정본>'] as $h) {
echo $h, ' → ', $r->redirectTargetFor($h) ?? 'PASS', PHP_EOL; // 허용 host 는 PASS 여야 합니다
} - 켠 뒤
curl -I로 미등록 host 의 301 과 허용 host 의 불변을 함께 확인하고, 하나라도 어긋나면 즉시 끕니다.
끄기 / 되돌리기
CORE_CANONICAL_HOST_ENABLED=false. 301 은 브라우저가 캐시하므로 cache_max_age(기본 1일)만큼은 이전 방문자에게 남을 수 있습니다.
동작 확인
curl -skI https://www.<정본>/some/path?x=1 # 301, Location: https://<정본>/some/path?x=1
curl -sk -X POST -o /dev/null -w '%{http_code}\n' https://www.<정본>/some/path # 308
curl -skI https://<정본>/some/path # 정본은 그대로
관련: Tenant Domains & Sites · Core Base/Routing/README.md