워드프레스 글이 404를 반환하는데 홈페이지는 정상일 때
홈페이지는 잘 열리는데 개별 글과 페이지마다 404가 뜬다면, 워드프레스가 의존하는 URL 재작성 규칙이 .htaccess 파일에서 사라졌거나 손상된 것입니다.
게시됨
홈페이지는 잘 열리는데 개별 글과 페이지마다 404가 뜬다면, 워드프레스가 의존하는 URL 재작성 규칙이 .htaccess 파일에서 사라졌거나 손상된 것입니다. 가장 빠른 해결책은 wp-admin의 설정 → 고유주소로 가서 아무것도 바꾸지 않고 변경 사항 저장을 누르는 것입니다. 이렇게 하면 워드프레스가 재작성 블록을 다시 생성합니다. 그래도 404가 계속된다면 웹 서버가 .htaccess에 쓸 수 없거나, 아파치가 이 파일을 무시하도록 설정돼 있거나, 아니면 .htaccess를 아예 쓰지 않는 Nginx를 사용 중인 것입니다.
홈페이지는 되는데 글은 안 되는 이유
홈페이지가 되는 이유는 /에 대한 요청이 문서 루트의 index.php에서 곧바로 처리되기 때문입니다. 재작성이 필요 없습니다.
/2026/my-post/에 대한 요청은 다릅니다. 이 경로는 디스크에 실제로 존재하는 폴더가 아닙니다. 워드프레스가 이 요청에 응답하려면, 아파치가 요청을 내부적으로 index.php로 넘겨야 하고, 그러면 워드프레스가 REQUEST_URI를 읽어 데이터베이스의 rewrite_rules 옵션에 저장된 재작성 규칙과 대조하고, 쿼리 변수를 해석한 뒤(WP::parse_request()), 알맞은 글을 불러옵니다.
문제가 생기는 지점은 바로 아파치에서 index.php로 넘어가는 이 부분입니다. 이 과정은 전적으로 .htaccess 안의 다음 블록에 달려 있습니다.
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
두 줄의 RewriteCond는 이렇게 말합니다. 요청된 경로가 실제 파일이 아니고(!-f) 실제 디렉터리도 아니면(!-d) index.php로 보내라. 이 블록을 지우면 아파치는 /2026/my-post/라는 실제 디렉터리를 찾다가, 없으니까 워드프레스가 요청을 보기도 전에 자기 자신의 404를 반환합니다.
고유주소를 저장하면 워드프레스가 .htaccess를 덮어쓰는 이유
대부분의 가이드가 건너뛰는 부분입니다. 고유주소 화면에서 저장을 누르면 워드프레스는 flush_rewrite_rules()를 호출합니다. .htaccess에 쓸 수 있는 아파치 환경이라면 이 호출이 save_mod_rewrite_rules()를 실행하고, 이것이 WP_Rewrite::mod_rewrite_rules()를 통해 위 블록을 다시 생성한 뒤 insert_with_markers()로 파일에 다시 씁니다.
insert_with_markers()는 # BEGIN WordPress와 # END WordPress 사이의 줄만 건드립니다. 이 표시자 바깥에 있는 것은 그대로 두고, 안에 있는 것은 전부 버리고 다시 씁니다. 최신 워드프레스 버전은 아예 파일 안에 이런 경고를 넣어둡니다.
Any changes to the directives between these markers will be overwritten.
그러니 “워드프레스가 내 .htaccess를 덮어쓴다”는 말은 맞지만 범위가 좁습니다. 워드프레스는 자기가 표시해 둔 블록만, 그것도 파일에 쓸 수 있을 때만 다시 씁니다. 파일에 쓸 수 없으면 저장은 디스크에 아무 변화도 남기지 않고 조용히 끝납니다. 이때 워드프레스는 고유주소 화면에 규칙을 직접 붙여넣으라는 노란색 안내를 표시하는데, 이 안내를 놓치면 재작성 블록은 계속 없는 채로 남고 모든 글이 계속 404를 반환합니다.
순서대로 해결하는 법
1. 고유주소를 다시 저장하세요. 설정 → 고유주소 → 변경 사항 저장. 아무것도 수정할 필요 없습니다. 이렇게 하면 flush가 다시 실행되고 블록이 다시 작성됩니다. 이주 후 규칙이 오래된 경우를 포함해 대부분의 상황이 이걸로 해결됩니다.
2. .htaccess가 존재하며 쓰기 가능한지 확인하세요. 이 파일은 wp-config.php와 같은 폴더에 있습니다. 점(.)으로 시작하는 숨김 파일이므로 FTP 클라이언트에서 “숨김 파일 표시”를 켜거나 다음 명령을 사용하세요.
ls -la /path/to/wordpress/.htaccess
파일이 없으면 새로 만들어 올바른 블록을 붙여넣으세요. 설치본이 하위 디렉터리에 있다면 RewriteBase와 마지막 재작성 대상 경로가 그 경로와 일치해야 합니다. 이걸 잘못 맞추는 것이 손으로 붙여넣은 블록에서도 404가 계속되는 가장 흔한 원인입니다. .htaccess 생성기를 쓰면 설치 경로에 맞는 정확한 블록을 만들어 주므로 추측할 필요가 없습니다.
3. 아파치가 .htaccess를 읽도록 허용돼 있는지 확인하세요. 가상 호스트에 AllowOverride None이 설정돼 있으면 아파치는 파일 내용과 상관없이 이 파일을 완전히 무시합니다. 워드프레스 디렉터리에 AllowOverride All(적어도 FileInfo)이 필요합니다. 이 설정은 .htaccess가 아니라 서버 설정에 있으므로 서버 접근 권한이 있어야 합니다.
<Directory /var/www/html>
AllowOverride All
</Directory>
4. mod_rewrite가 활성화돼 있는지 확인하세요. Debian/Ubuntu에서는 다음과 같습니다.
sudo a2enmod rewrite
sudo systemctl restart apache2
5. Nginx에는 .htaccess가 없습니다. 재작성 규칙은 대신 server 블록에 들어갑니다.
location / {
try_files $uri $uri/ /index.php?$args;
}
빠른 진단: 누가 낸 404인가?
404 페이지를 보세요. 스타일이 입혀지지 않은 아파치나 호스팅 업체의 오류 페이지라면 아파치가 워드프레스까지 도달하지 못한 것입니다. 이건 .htaccess나 서버 설정 문제입니다(2~4단계). 반면 테마의 디자인이 입혀진 404라면 워드프레스가 요청은 받았지만 대조하지 못한 것입니다. 데이터베이스의 규칙이 오래된 것이니 고유주소를 다시 저장하는 것(1단계)이 해결책입니다. 이 하나의 구분만으로도 대부분은 한 시간의 추측을 아낍니다.
하지 말아야 할 것
먼저 모든 플러그인을 비활성화하지 마세요. “글은 404, 홈페이지는 정상”이라는 순수한 증상은 재작성 문제이지 플러그인 충돌이 아닙니다. 플러그인 끄기는 포럼에서 반사적으로 나오는 답변이지만, 이 문제에는 거의 아무 상관이 없습니다.
.htaccess에 chmod 777을 하지 마세요. 워드프레스에 필요한 건 웹 서버 사용자가 파일에 쓸 수 있는 권한이지, 전체 사용자에게 쓰기를 허용하는 것과는 다릅니다. 올바른 소유자와 함께 644면 충분하고 안전합니다. 777은 어차피 라우팅을 고쳐주지도 않으면서 보안 구멍만 냅니다.
# BEGIN WordPress / # END WordPress 표시자 안에 커스텀 재작성 규칙을 붙여넣지 마세요. 다음에 누군가 고유주소를 저장하는 순간 지워집니다. 커스텀 지시문은 # BEGIN WordPress 줄 위에 두세요.
워드프레스를 재설치하지 마세요. 코어 파일은 멀쩡합니다. 이건 텍스트 블록 하나가 빠진 것이지 설치가 손상된 게 아닙니다.
테마를 탓하지 마세요. 고유주소 라우팅은 워드프레스 코어입니다. 테마가 이걸 일으킬 수도 없고 고칠 수도 없습니다.
그래도 막혔다면?
고유주소를 다시 저장했고, 블록이 존재하며 쓰기 가능한지 확인했고, AllowOverride All도 설정했는데도 안 된다면, 남은 용의자는 보안 플러그인이나 호스팅 업체 수준의 규칙이 뒤에서 몰래 .htaccess를 다시 만들거나 잠그고 있는 경우입니다. 고유주소를 저장한 직후 파일의 수정 시각을 확인하세요. 시각이 바뀌지 않았다면 무언가가 쓰기를 막고 있는 것입니다. .htaccess 생성기로 깨끗한 블록을 다시 만들어 붙여넣고, 강력 새로 고침으로 다시 확인하세요.