본문으로 건너뛰기

정본 호스트 강제 (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 / HEAD301 → https://<정본>/<같은 경로>?<같은 쿼리> · Cache-Control: public, max-age=<cache_max_age>
미등록 host 의 POST / PUT / PATCH / DELETE308 (메서드·본문 보존 — 301 은 GET 으로 바뀌어 폼을 잃습니다)
허용 host변경 없음

허용 호스트는 어떻게 정해지나​

목록을 따로 관리하지 않습니다. 도메인 라우팅이 이미 쓰는 것과 같은 해석기로 판정합니다.

  1. APP_URL 의 host (정본)
  2. CORE_CANONICAL_HOST_ADDITIONAL — Core 가 알 수 없는 프로젝트 고유 호스트 (예: 별도 표면·진입 전용 서브도메인)
  3. 미러 도메인 (settings.mirror_domains)
  4. SaaS / Tenant 의 settings.domains (→ Tenant Domains & Sites)
  5. 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 되어 사이트가 죽습니다​

  1. 프로젝트가 자기 방식으로 인식하는 호스트(별도 표면·진입 전용 서브도메인 등)를 전부 CORE_CANONICAL_HOST_ADDITIONAL 에 적습니다. Core 의 settings.domains/primary_domain 에 없는 호스트는 여기 없으면 301 됩니다.
  2. 켜기 전에 실제 호스트 전부에 대해 판정을 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 여야 합니다
    }
  3. 켠 뒤 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