405 에러가 알려 주는 것
405 Method Not Allowed는 서버가 그 URL의 리소스를 찾았고, 보낸 HTTP 메서드도 이해했지만, 그 리소스에는 그 메서드를 적용하지 않겠다는 응답입니다. 가장 흔한 경우는 GET만 제공하는 곳에 POST를 보낸 것입니다.
생각보다 구체적인 신호입니다. “페이지 없음”(404)도, “권한 없음”(403)도, “이 서버는 그 메서드를 모름”(501)도 아닙니다. 주소는 맞았고, 그 주소에 쓴 동사가 틀렸습니다.
RFC 9110(§15.5.6)에는 405를 디버깅하기 쉽게 만드는 규칙이 하나 있습니다. 서버는 그 리소스가 받는 메서드를 나열한 Allow 헤더를 반드시 보내야 합니다. 제대로 된 405는 답을 같이 줍니다.
HTTP/1.1 405 Method Not Allowed
Allow: GET, HEAD
Content-Type: text/html
모든 서버가 이 규칙을 지키지는 않습니다. Allow 없이 405만 보내는 경우도 있습니다. 그때는 받는 메서드를 직접 알아내야 하고, 아래 명령들이 그 용도입니다.
사이트를 이용하는 중이라면
내 쪽에서 고칠 수 있는 경우는 거의 없습니다. 방문자가 보는 405는 보통 폼을 제출하거나, 버튼을 누르거나, 앱을 쓰다가 “405 Not Allowed”나 “Method Not Allowed” 화면을 받는 경우입니다.
한 번쯤 해 볼 만한 것은 있습니다. 폼이 있던 페이지로 돌아가 새로고침한 뒤 다시 제출하세요. 몇 시간 열어 둔 폼이나 즐겨찾기로 다시 연 폼은 사이트가 더는 처리하지 않는 주소로 전송될 수 있습니다. 그래도 같은 오류가 나면 그 기능이 사이트 쪽에서 고장 난 것입니다. 어느 페이지의 어느 버튼에서, 언제 났는지를 운영자에게 알려 주세요.
아래 내용은 사이트를 운영하거나 API를 호출하는 쪽을 위한 것입니다.
curl로 재현하기
이 문제에서는 브라우저가 너무 많은 것을 가립니다. 실패하는 메서드를 직접 보내고 상태 줄과 헤더를 보세요.
curl -i https://example.com/contact -d 'name=test'
HTTP/2 405
allow: GET, HEAD
server: nginx
content-type: text/html
그다음 리소스에 무엇을 받는지 물어봅니다. 많은 서버와 프레임워크가 OPTIONS에 메서드 목록으로 답합니다.
curl -i -X OPTIONS https://example.com/contact
일반 GET 결과와도 비교합니다. URL 자체가 정상인지 알 수 있습니다.
curl -sS -D - -o /dev/null https://example.com/contact
-I는 GET이 아니라 HEAD를 보냅니다. 위 명령은 GET을 보내고 응답 본문만 버립니다. Windows PowerShell에서는 curl.exe -sS -D - -o NUL https://example.com/contact를 사용하세요.
볼 헤더는 세 가지입니다.
Allow: 그 리소스가 받는 메서드입니다. 내 메서드가 없다면 엉뚱한 핸들러를 겨냥했거나, 핸들러가 그 메서드를 처리하도록 연결되지 않은 것입니다.Server와 CDN·WAF 헤더: 응답을 만들거나 중계한 계층을 추정하는 단서입니다. 프록시가 앱의 오류 페이지나 헤더를 바꿀 수 있으므로 nginx식 화면만으로 앱에 도달하지 않았다고 확정하지 마세요. 요청 ID와 시각을 프록시·애플리케이션 로그에서 대조하세요.Location: POST에 대한 첫 응답이 405가 아니라 301·302라면 아래에서 설명할 리다이렉트 함정입니다.
HTTP 점검은 바깥에서 본 서버 배너, 리다이렉트 체인, 응답 헤더를 보여 줍니다. 일반 GET 요청을 보내므로 페이지 자체가 정상인지, 앞에 어떤 서버와 CDN이 있는지 확인하는 데 쓰고, 실패하는 메서드 자체는 curl이나 API 클라이언트로 시험해야 합니다.
흔한 원인
1. 폼이나 API 호출이 정적 파일을 가리킴
폼의 action이 contact.html, /thanks/ 같은 정적 경로를 가리키는 경우입니다. 정적 파일 서비스는 GET과 HEAD에만 의미가 있어서, nginx와 대부분의 정적 호스팅·CDN은 일반 파일로 온 POST에 405로 답합니다. nginx는 짧게 “405 Not Allowed” 페이지를 보여 줍니다.
사이트를 정적 호스팅으로 옮긴 뒤에 많이 생깁니다. 예전 서버에서는 그 폼 뒤에 PHP나 CGI 스크립트가 있었는데, 새 호스팅에는 실행할 코드가 없으니 POST가 갈 곳이 없습니다.
해결: 폼을 실제로 처리하는 곳으로 보내세요. 직접 만든 엔드포인트, 서버리스 함수, 폼 처리 서비스 등입니다. 인터넷에 error_page 405 =200 $uri;로 “해결”하는 nginx 설정이 돌아다니는데, POST에 정적 페이지를 그대로 돌려줄 뿐입니다. 오류는 가려지지만 사용자가 입력한 내용도 버려집니다. 중요한 폼에는 쓰지 마세요.
2. 경로는 있지만 그 메서드용 핸들러가 없음
애플리케이션에 그 경로의 핸들러는 있는데 이 메서드는 처리하지 않는 경우입니다. 프레임워크마다 표시 방식이 달라서 알아 두면 도움이 됩니다.
- Flask:
methods=["POST"]가 없는 라우트는Allow헤더와 함께 405를 돌려줍니다. - Django: 클래스 기반 뷰는 정의하지 않은 메서드에 405를 주고,
@require_POST같은 데코레이터도 마찬가지입니다. - Laravel: “The POST method is not supported for route contact. Supported methods: GET, HEAD.”
- Spring: “Request method ‘POST’ is not supported.”
- FastAPI:
{"detail":"Method Not Allowed"}. - Next.js App Router:
GET만 export한route.ts는 다른 메서드에 405로 답합니다. - Express와 Rails는 이 경우에 405를 쓰지 않습니다. Express는
Cannot POST /contact와 함께 404를, Rails는 라우팅 오류(“No route matches [POST]“)를 냅니다. 이 스택인데 405가 보인다면 체인의 다른 곳에서 온 응답입니다.
해결: 그 메서드의 핸들러를 추가하거나, 라우트가 원래 받도록 만든 메서드로 호출하세요. 추측하지 말고 라우트 목록을 보세요. php artisan route:list, flask routes, 또는 쓰는 프레임워크의 같은 기능입니다.
3. 리다이렉트가 POST를 GET으로 바꿈
시간을 가장 많이 잡아먹는 원인입니다. http://example.com/api/orders로 POST를 보냈는데, 서버가 301이나 302로 https://…로 보내거나 끝에 슬래시를 붙입니다. 역사적인 이유로 브라우저와 대부분의 HTTP 클라이언트는 301·302를 따라갈 때 POST를 GET으로 바꾸고 본문을 버립니다. 그 GET이 POST만 받는 핸들러에 도착하면, GET을 보낸 적도 없는 URL에서 405를 받게 됩니다.
curl에서는 -d가 POST를 선택하게 두고, 먼저 -L 없이 보내 첫 응답을 보세요.
curl -i http://example.com/api/orders -d '{}'
301이나 302라면 내가 관리하는 테스트 엔드포인트에서 리다이렉트 경로를 비교하세요.
curl -i -L http://example.com/api/orders -d '{}'
이 명령은 301·302에서 POST를 GET으로 바꾸고 본문을 버리며, 307·308에서는 POST와 본문을 유지합니다. 이 예시에 -X POST를 추가하지 마세요. 메서드 이름을 명시적으로 고정하므로 -L -X POST -d '{}'는 301·302 뒤에도 POST를 보내 브라우저의 메서드 전환을 재현하지 않습니다. 테스트 서버 로그에서 실제 메서드를 확인하고, 무관한 운영 서비스에 시험 POST를 보내지 마세요. curl 설명서를 참고하세요.
해결: 정식 URL(https, 맞는 호스트, 정확한 슬래시 형태)을 직접 호출하세요. 내가 통제하지 못하는 클라이언트가 옛 주소로 POST를 보낸다면 그 리다이렉트를 307이나 308로 바꾸세요. 둘 다 클라이언트가 같은 메서드와 본문으로 다시 요청하도록 규정합니다. 리다이렉트 코드별 차이는 301과 302의 차이에 정리돼 있습니다.
4. CORS preflight(OPTIONS)를 처리하지 않음
curl이나 Postman에서는 되는데 브라우저에서만 실패하는 경우입니다. 브라우저는 PUT·DELETE·PATCH, 사용자 정의 헤더, Content-Type: application/json을 쓰는 다른 출처 요청을 보내기 전에 preflight를 먼저 보냅니다.
OPTIONS /api/orders
Origin: https://app.example.com
Access-Control-Request-Method: PUT
preflight는 알맞은 Access-Control-Allow-* 헤더와 함께 2xx 응답을 받아야 합니다. 라우트에 OPTIONS 처리가 없으면 서버가 405를 돌려주고, Chrome은 “Response to preflight request doesn’t pass access control check: It does not have HTTP ok status.” 같은 메시지를 냅니다. 실제 PUT은 나가지도 않습니다. 서버 로그에 OPTIONS 한 줄만 남는 이유입니다.
브라우저와 같은 방식으로 재현해 보세요.
curl -i -X OPTIONS https://api.example.com/api/orders \
-H 'Origin: https://app.example.com' \
-H 'Access-Control-Request-Method: PUT'
해결: 프레임워크의 CORS 미들웨어를 켜면 OPTIONS에 알아서 답합니다. 프록시에서 답하게 해도 됩니다. 인증 미들웨어가 OPTIONS를 통과시키는지도 확인하세요. preflight에는 인증 정보가 실리지 않으므로, 이를 거부하는 인증 계층은 메서드 문제처럼 보이는 방식으로 CORS를 깨뜨립니다.
5. IIS: WebDAV나 핸들러 매핑이 PUT·DELETE를 가로챔
IIS에서 ASP.NET API가 GET·POST는 되는데 PUT·DELETE에만 “HTTP Error 405.0 - Method Not Allowed”를 내는 것은 잘 알려진 패턴입니다. WebDAV 모듈이 설치돼 있으면 앱보다 먼저 그 메서드들을 가져갑니다. 사이트에서 WebDAV를 빼면 대개 해결됩니다.
<system.webServer>
<modules>
<remove name="WebDAVModule" />
</modules>
<handlers>
<remove name="WebDAV" />
</handlers>
</system.webServer>
WebDAV가 원인이 아니라면 앱을 처리하는 핸들러 매핑(기존 ASP.NET이라면 보통 ExtensionlessUrlHandler-Integrated-4.0)을 보세요. verb 속성이 처리할 메서드를 정하는데, GET,HEAD,POST,DEBUG처럼 적혀 있으면 PUT과 DELETE는 처리할 핸들러가 없습니다. *로 바꾸거나 메서드를 추가하면 됩니다.
서버 모듈이 앱 앞에 앉아 일부 메서드를 먼저 가져가는 구조라면 어디서든 같은 이야기입니다.
6. HTML 폼은 GET과 POST만 보냄
<form method="PUT">이나 method="DELETE"는 HTML에서 유효하지 않습니다. 브라우저는 GET으로 대신 보내고, DELETE만 받는 라우트는 405로 답합니다. 프레임워크들은 POST 폼에 숨은 필드(Rails·Laravel의 _method, Blade의 @method('DELETE'))를 넣어 이를 우회합니다. 숨은 필드가 빠졌거나, 프록시나 미들웨어 순서 때문에 프레임워크가 그 필드를 읽지 못하면 405가 납니다.
일부러 막은 경우
405가 버그가 아니라 정책일 때도 있습니다. 특정 메서드만 노출하는 API 게이트웨이나, 흔치 않은 메서드를 거부하는 WAF 규칙입니다. 응답이 어디서 왔는지 보면 구별됩니다. Server 헤더와 오류 페이지가 게이트웨이의 것이고, 애플리케이션 로그에는 아무것도 없습니다.
참고로 nginx와 Apache에서 흔히 쓰는 메서드 제한(limit_except, <LimitExcept>에 거부 규칙)은 405가 아니라 403을 돌려줍니다. 그래서 405는 접근 규칙보다 “이 메서드를 처리할 곳이 없음”을 가리키는 경우가 더 많습니다. 403이 보인다면 403 Forbidden을 참고하세요.
맞는 서버로 가고 있는지 확인
스테이징에서는 되는 메서드가 운영에서만 실패한다면, 운영 트래픽이 생각한 코드로 가고 있는지 확인하세요. 호스트명이 아직 옛 서버나 정적 호스팅을 가리키면 쓰기 메서드마다 405가 납니다. DNS 조회로 지금 그 이름이 어디로 가는지, HTTP 점검의 Server 헤더로 거기서 무엇이 응답하는지 볼 수 있습니다.
고친 다음에
- 실패하던 요청을
curl -i -X <메서드>로 그대로 다시 보내, 405도 리다이렉트도 아닌 기대한 상태 코드가 오는지 확인합니다. - 브라우저 앱이라면 개발자 도구 → Network에서 “Preserve log”를 켜고, OPTIONS preflight와 실제 요청이 모두 성공하는지 봅니다.
- 일부러 틀린 메서드를 보내
Allow헤더를 확인합니다. 라우트가 지원하는 메서드가 정확히 나열돼 있으면 다음 디버깅이 짧아집니다.
관련 글
- 301과 302의 차이: 리다이렉트가 POST를 조용히 GET으로 바꾸는 이유.
- 403 Forbidden: 메서드는 괜찮지만 접근이 거부된 경우.
- 404 Not Found: URL 자체가 서버에 없는 경우.
내 도메인에서 바로 확인
무료, 가입 불필요. 이 가이드가 다루는 항목을 바로 검사하고 조치 방법을 알려드립니다.