헷갈리는 HTTP 헤더 총정리: Host, Origin, Referer

헷갈리는 HTTP 헤더 총정리: Host, Origin, Referer

웹 개발을 하다 보면 브라우저 개발자 도구에서 HTTP 요청 헤더를 뜯어볼 일이 종종 있잖아요? 그때 Host, Origin, Referer… 이 세 녀석이 나란히 있는 걸 보면 “이거 다 비슷한 거 아닌가?” 싶을 때가 있는데요. 헤더 자체가 처음이라면 HTTP 메시지가 어떻게 생겼는지부터 보고 오시면 흐름이 더 잘 잡힙니다.

셋 다 URL이나 도메인 정보를 담고 있으니까 대충 같은 역할처럼 보이거든요. 그런데 막상 CORS 문제를 디버깅하거나 보안 설정을 건드려야 할 때 이 헤더들의 차이를 정확히 모르면 삽질을 피할 수가 없더라고요. 😅

이번 포스팅에서는 세 헤더가 각각 무엇을 담고 있고, 언제 붙고, 왜 필요한지 실제 요청 예시와 함께 정리해보겠습니다. X-Forwarded-ForX-Forwarded-Host 같은 프록시 관련 헤더도 같이 다뤄보려고 합니다.

Host 헤더

Host는 HTTP/1.1에서 유일하게 필수인 요청 헤더입니다. 요청이 도착해야 할 서버의 도메인과 포트 번호를 나타냅니다.

GET /api/users HTTP/1.1
Host: api.example.com

왜 이런 헤더가 필요할까요? 하나의 서버(같은 IP 주소)에서 여러 개의 웹사이트를 운영하는 경우를 생각해보세요. blog.example.comshop.example.com이 같은 서버에 있다면, 서버는 어떤 사이트에 대한 요청인지 구분할 방법이 필요합니다. Host 헤더가 바로 그 역할을 해주는 거죠.

이런 방식을 가상 호스팅(Virtual Hosting)이라고 하는데요. Nginx나 Apache 같은 웹 서버에서 설정하는 server_name이나 ServerName 지시어가 이 Host 헤더 값을 기반으로 어떤 사이트를 서빙할지 결정합니다.

nginx.conf
server {
    listen 80;
    server_name blog.example.com;
    root /var/www/blog;
}

server {
    listen 80;
    server_name shop.example.com;
    root /var/www/shop;
}

정리하면 Host는 모든 HTTP/1.1 요청에 반드시 포함되는 헤더로, 요청의 “목적지”를 나타냅니다. 브라우저가 요청할 때 자동으로 설정하고요. 포트가 기본 포트(HTTP 80, HTTPS 443)가 아닌 경우에는 Host: example.com:8080처럼 포트 번호까지 붙습니다.

참고로 HTTP/2에서는 Host 헤더 대신 :authority라는 가상 헤더(pseudo-header)가 이 역할을 합니다. 기능은 동일하지만 HTTP/2의 프레임 기반 프로토콜에 맞게 이름이 바뀐 것이죠.

Origin 헤더

Origin 헤더는 요청을 보낸 페이지의 출처, 즉 프로토콜 + 도메인 + 포트를 나타냅니다.

POST /api/users HTTP/1.1
Host: api.example.com
Origin: https://app.example.com

Host가 “요청이 어디로 가는지”를 나타낸다면, Origin은 “요청이 어디서 왔는지”를 알려줍니다. 이 차이가 핵심입니다.

Origin 헤더가 필요한 가장 큰 이유는 보안인데요. 서버가 “이 요청이 허용된 출처에서 온 것인지” 판단할 수 있게 해주기 때문입니다.

Origin 헤더는 아무 요청에나 붙는 게 아니라 특정 조건에서만 브라우저가 자동으로 추가합니다. 교차 출처 요청(Cross-Origin Request)을 보낼 때, <form> 태그로 POST 요청을 보낼 때, 그리고 fetch()XMLHttpRequest로 요청을 보낼 때 주로 붙습니다.

반면에 같은 출처에 대한 단순 GET 요청이나 주소창에 URL을 직접 입력해서 페이지를 여는 경우에는 Origin 헤더가 붙지 않습니다.

Origin 헤더에 대해서 한 가지 주목할 점은 경로(path) 정보를 포함하지 않는다는 것입니다. https://app.example.com/dashboard/settings에서 요청을 보내도 Origin 헤더에는 https://app.example.com만 들어갑니다. 이건 사용자의 프라이버시를 보호하면서도 출처 검증이라는 목적을 달성하기 위한 설계입니다.

CORS에서 Origin 헤더가 얼마나 중요한 역할을 하는지는 이미 잘 알려져 있죠. 브라우저는 교차 출처 요청을 보낼 때 Origin 헤더를 자동으로 추가하고, 서버는 Access-Control-Allow-Origin 응답 헤더로 해당 출처의 접근을 허용할지 결정합니다.

요청
OPTIONS /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: POST
응답
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST

Referer 헤더

Referer 헤더는 현재 요청을 보내게 된 이전 페이지의 URL을 담고 있습니다. 이름에 오타가 있다는 것이 유명한데요. 영어 단어 “Referrer”에서 r이 하나 빠져있죠. 1996년에 HTTP 표준이 만들어질 때 이렇게 잘못 표기된 채로 굳어져 버렸습니다. 😂

GET /posts/http-headers HTTP/1.1
Host: blog.example.com
Referer: https://www.google.com/search?q=http+headers

위 예시처럼 Google에서 “http headers”를 검색한 뒤 블로그 글을 클릭하면, 브라우저는 Referer 헤더에 Google 검색 결과 페이지의 URL을 담아서 보냅니다.

OriginReferer의 결정적인 차이는 담고 있는 정보의 범위입니다. Origin은 출처(프로토콜 + 도메인 + 포트)만 포함하는 반면, Referer는 경로(path)와 쿼리 파라미터(query string)까지 포함할 수 있습니다.

헤더값 예시
Originhttps://app.example.com
Refererhttps://app.example.com/dashboard?tab=settings

RefererOrigin보다 더 다양한 상황에서 전송됩니다. 링크를 클릭해서 다른 페이지로 이동할 때, 이미지/CSS/JS 같은 리소스를 로드할 때, 폼을 제출할 때 모두 Referer가 붙습니다.

그런데 Referer 헤더에 전체 URL이 그대로 노출되면 프라이버시 문제가 생길 수 있겠죠? 예를 들어 https://mail.example.com/inbox?msg=secret-token 같은 URL이 외부 사이트로 그대로 전달되면 곤란할 수 있습니다.

이 문제를 해결하기 위해 Referrer-Policy라는 응답 헤더가 있습니다. 이름이 재밌는 게, 이쪽은 오타가 수정되어서 Referrer로 올바르게 표기되어 있어요.

Referrer-Policy 헤더를 사용하면 Referer 헤더에 얼마나 많은 정보를 포함할지 세밀하게 제어할 수 있습니다.

Referrer-Policy: strict-origin-when-cross-origin

주요 정책은 다음과 같습니다.

  • no-referrerReferer 헤더를 아예 보내지 않음
  • origin — 출처(프로토콜 + 도메인 + 포트)만 보냄 (경로 제거)
  • same-origin — 같은 출처 요청에만 전체 URL을 보내고, 다른 출처 요청에는 보내지 않음
  • strict-origin-when-cross-origin — 같은 출처에는 전체 URL, 다른 출처에는 출처만 보냄 (대부분의 브라우저 기본값)
  • no-referrer-when-downgrade — HTTPS에서 HTTP로 이동할 때만 Referer를 제거
  • unsafe-url — 항상 전체 URL을 보냄 (비권장)

HTML에서 개별 링크나 요소에 대해서도 referrerpolicy 속성으로 정책을 지정할 수 있습니다.

<a href="https://external.com" referrerpolicy="no-referrer">외부 링크</a>

세 헤더 비교

지금까지 살펴본 Host, Origin, Referer 세 헤더를 한눈에 비교해보겠습니다.

실제 시나리오를 생각해볼까요? 사용자가 https://app.example.com/dashboard에서 https://api.example.com/users로 API를 호출하는 상황입니다.

POST /users HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Referer: https://app.example.com/dashboard
Content-Type: application/json

세 헤더가 나란히 있으니 차이가 한눈에 보이시죠?

비교 항목HostOriginReferer
담는 정보목적지 도메인(:포트)출처 (프로토콜+도메인+포트)이전 페이지 전체 URL
경로 포함
필수 여부HTTP/1.1에서 필수조건부조건부
주요 용도가상 호스팅, 라우팅CORS, CSRF 방어분석, 로깅, 캐싱
누가 설정브라우저 자동브라우저 자동브라우저 자동

프록시 관련 헤더

실제 운영 환경에서는 클라이언트와 서버 사이에 로드 밸런서나 리버스 프록시가 끼어 있는 경우가 많죠. 두 종류의 프록시가 왜 이름은 같고 방향은 반대인지 헷갈리신다면 프록시 서버와 리버스 프록시 서버의 차이 글에서 개념 정리부터 해두시면 아래 헤더들의 의미가 훨씬 또렷해집니다. 이때 원래 요청 정보가 유실될 수 있어서 이를 보존하기 위한 헤더들이 필요합니다.

X-Forwarded-Host는 클라이언트가 원래 요청한 호스트 정보를 보존합니다. 프록시가 요청을 내부 서버로 전달할 때 Host 헤더를 내부 서버 주소로 변경하는 경우가 많은데요. 이때 원래 클라이언트가 요청한 호스트를 알 수 있도록 X-Forwarded-Host 헤더에 원본 값을 저장해둡니다.

프록시가 내부 서버로 전달하는 요청
GET /api/users HTTP/1.1
Host: internal-server:3000
X-Forwarded-Host: api.example.com
X-Forwarded-Proto: https
X-Forwarded-For: 203.0.113.50

X-Forwarded-For는 클라이언트의 원래 IP 주소를 보존합니다. 프록시를 거치면 서버가 보는 IP 주소가 프록시의 IP가 되어버리기 때문에, 원래 클라이언트의 IP를 알 수 있도록 이 헤더를 사용합니다. 프록시를 여러 개 거치는 경우 쉼표로 구분해서 IP 주소가 차례로 추가됩니다.

X-Forwarded-For: 203.0.113.50, 70.41.3.18, 150.172.238.178

첫 번째 IP가 원래 클라이언트의 IP이고, 그 뒤로는 경유한 프록시들의 IP가 순서대로 붙습니다.

X-Forwarded-Proto는 클라이언트가 원래 사용한 프로토콜(HTTP 또는 HTTPS)을 보존합니다. 로드 밸런서에서 SSL/TLS를 종료하고 내부적으로는 HTTP로 통신하는 구조에서 원래 HTTPS 요청이었는지 확인할 때 유용합니다.

이런 X-Forwarded-* 헤더들은 사실상 표준(de facto standard)이었는데요. 최근에는 이들을 하나로 통합한 Forwarded라는 표준 헤더(RFC 7239)가 정의되어 있습니다.

Forwarded: for=203.0.113.50;host=api.example.com;proto=https

다만 아직까지는 X-Forwarded-* 헤더들이 훨씬 널리 쓰이고 있습니다.

보안 관점에서 보기

이 헤더들은 보안적으로도 중요한 역할을 합니다.

우선 Origin 헤더는 CSRF(Cross-Site Request Forgery) 공격을 방어하는 데 활용할 수 있습니다. 서버가 요청의 Origin 헤더를 확인해서 신뢰할 수 있는 출처에서 온 요청만 처리하는 방식이죠.

Express 미들웨어 예시
const ALLOWED_ORIGINS = [
  "https://app.example.com",
  "https://admin.example.com",
];

app.use((req, res, next) => {
  const origin = req.headers.origin;

  // Origin 헤더가 있는 경우 허용 목록 확인
  if (origin && !ALLOWED_ORIGINS.includes(origin)) {
    return res.status(403).json({ error: "Forbidden" });
  }

  next();
});

주의할 점은 Origin 헤더가 항상 존재하는 것은 아니라는 겁니다. 같은 출처의 GET 요청 같은 경우에는 Origin이 빠질 수 있기 때문에, Origin 검사만으로 모든 CSRF 공격을 막을 수는 없습니다. 실무에서는 CSRF 토큰과 함께 사용하는 것이 좋습니다.

Host 헤더도 보안과 관련이 있는데요. 공격자가 Host 헤더를 조작해서 서버가 잘못된 URL을 생성하도록 유도하는 Host Header Injection 공격이 가능합니다. 예를 들어 비밀번호 재설정 이메일에서 서버가 Host 헤더를 기반으로 링크를 생성한다면, 공격자가 이 헤더를 자신의 도메인으로 바꿔서 피싱 링크를 만들 수 있습니다. 이를 방지하려면 서버에서 허용된 호스트 목록을 관리하는 것이 중요합니다.

Django settings.py
# 허용된 호스트만 수락
ALLOWED_HOSTS = ["example.com", "www.example.com"]

Referer 헤더는 앞서 언급한 프라이버시 문제 외에도 Open Redirect 공격에 악용될 수 있습니다. Referer 값을 검증 없이 리다이렉트 대상으로 사용하면 공격자가 사용자를 악성 사이트로 유도할 수 있거든요. Referer 값은 신뢰할 수 있는 참고 정보일 뿐 보안 판단의 근거로 사용하기에는 적합하지 않습니다.

X-Forwarded-For 헤더 역시 주의가 필요합니다. 이 헤더는 클라이언트가 직접 설정할 수 있기 때문에 위조가 매우 쉽습니다. IP 기반 접근 제어를 할 때는 반드시 신뢰할 수 있는 프록시에서 설정한 값만 사용해야 합니다.

개발자 도구로 직접 확인하기

Chrome이나 Firefox의 개발자 도구에서 Network 탭을 열고 아무 페이지나 열어보세요. 요청을 하나 클릭하면 Headers 섹션에서 Host, Origin, Referer를 직접 확인할 수 있습니다.

curl 커맨드를 사용하면 이 헤더들을 직접 설정해서 테스트해볼 수도 있습니다.

curl -H "Origin: https://app.example.com" \
     -H "Referer: https://app.example.com/dashboard" \
     https://api.example.com/users

이렇게 헤더를 직접 바꿔가면서 서버의 반응을 확인해보면 각 헤더의 역할을 체감할 수 있습니다.

마치며

Host, Origin, Referer는 모두 URL 관련 정보를 담고 있어서 얼핏 비슷해 보이지만 각자 뚜렷한 역할이 있습니다. Host는 요청의 목적지를 알려주고, Origin은 요청의 출발지를 알려주며, Referer는 이전 페이지의 전체 경로를 알려줍니다.

프록시 환경에서는 X-Forwarded-Host, X-Forwarded-For, X-Forwarded-Proto 같은 헤더들이 원본 요청 정보를 보존해주는 역할을 하고요.

이 헤더들의 차이를 정확히 이해하고 있으면 CORS 문제를 디버깅하거나 보안 설정을 할 때 훨씬 수월해질 겁니다. 웹 보안에 관심이 있으시다면 HTTP 쿠키세션에 대해서도 함께 공부해보시면 좋습니다.

더 깊이 공부하고 싶으시다면 MDN의 HTTP headers 문서를 추천드립니다.

This work is licensed under CC BY 4.0 CC BY

개발자를 위한 뉴스레터

달레가 정리한 AI 개발 트렌드와 직접 만든 콘텐츠를 전해드립니다.

Discord