PHP와 Nginx로 비공개 파일 직접 접근 막기
파일 업로드 기능을 만들 때 가장 흔한 실수는 저장 폴더를 그대로 공개 URL 아래에 두는 것입니다. 주소만 알면 누구나 파일을 내려받을 수 있게 되기 때문입니다. 이 글은 Nginx(웹사이트 요청을 받아 나눠 주는 프로그램)에서 직접 접근을 막고, PHP 라우터(요청을 검사한 뒤 파일을 대신 읽어 주는 중간 스크립트)가 인증을 확인한 뒤 필요한 파일만 읽어주는 구조를 설명합니다.
기본 원칙
- 원본 파일 폴더는 URL로 직접 열리지 않아야 합니다.
- 파일명은 사용자가 보낸 값을 그대로 믿지 않고 realpath(경로의 실제 위치를 확인해 주는 PHP 함수)로 검사합니다.
- 인증 실패나 경로 오류는 403보다 404로 숨기는 편이 안전할 수 있습니다.
- 검색엔진에는 X-Robots-Tag 응답 헤더로 색인(검색 결과 등록)을 막습니다.
PHP 라우터 예시
<?php
session_start();
if (empty($_SESSION["user_id"])) {
http_response_code(404);
exit;
}
$base = realpath(__DIR__ . "/protected_files");
$rel = ltrim($_GET["file"] ?? "", "/");
$path = realpath($base . "/" . $rel);
if (!$base || !$path || !str_starts_with($path, $base . DIRECTORY_SEPARATOR)) {
http_response_code(404);
exit;
}
if (!is_file($path)) {
http_response_code(404);
exit;
}
header("Content-Type: " . mime_content_type($path));
header("X-Robots-Tag: noindex, nofollow");
readfile($path);
Nginx 차단 예시
location ~ ^/(protected_files|logs|data)/ {
deny all;
return 404;
}
location = /media.php {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
}
운영 체크
설정 후에는 파일 폴더 직접 URL이 404인지, PHP 라우터가 로그인 상태에서만 응답하는지, 로그에 실제 파일 경로가 과하게 남지 않는지 확인해야 합니다.
파일 폴더만 robots.txt로 막으면 충분한가요?
아니요. robots.txt는 착한 크롤러에게만 안내하는 파일이라 직접 접근 차단이 아닙니다. Nginx나 웹서버 레벨에서 막아야 합니다.
403과 404 중 무엇이 좋나요?
관리 정책에 따라 다르지만, 비공개 파일 존재 여부를 숨기고 싶다면 404가 더 조용합니다.
PHP에서 경로 검사가 왜 필요한가요?
../ 같은 경로 조작으로 허용 폴더 밖 파일을 읽는 문제를 막기 위해 realpath와 루트 경로 비교가 필요합니다.