Si la portada de WordPress carga pero las entradas, las páginas y las categorías devuelven 404, o bien la petición nunca llega a index.php (en Nginx falta el try_files correcto, en Apache no hay .htaccess, mod_rewrite está desactivado o AllowOverride está en None), o bien WordPress no tiene ninguna regla de reescritura para esa URL. Primero averigüe quién generó la página 404: si no tiene el diseño del tema, hay que corregir el servidor web; si es la página 404 de su tema, ejecute wp rewrite flush (o guarde Ajustes → Enlaces permanentes) y busque un plugin que rompa las reglas. Reprodujimos seis causas en un banco de pruebas; a continuación están los códigos de respuesta reales y las soluciones que verificamos.
Cómo lo probamos
- Entorno: banco de pruebas de RichHost, contenedor aislado con 2 vCPU / 8 GB, Ubuntu 24.04, nginx 1.24, PHP 8.3-FPM, MariaDB 10.11, WordPress 6.7.2, tema Twenty Twenty-Five, enlaces permanentes /%postname%/. Para las pruebas con Apache instalamos temporalmente Apache 2.4.58 en un puerto aparte. Fecha: octubre de 2026.
- Rompimos una copia aparte del sitio con su propia base de datos (nginx en el puerto 8090, Apache en el 8091), para que el banco principal quedara intacto. Después eliminamos la copia, su base de datos y Apache.
- Cada estado se comprobó con curl: código de respuesta, número de menciones de wp-content en el HTML (señal de que WordPress generó la página) y la cabecera X-Cache. La salida está abreviada.
- Son resultados del banco de pruebas, no mediciones de los servidores de producción de RichHost.
Primer paso: quién devolvió el 404, el servidor o WordPress
Tome cualquier URL de una entrada que devuelva 404 y ejecute tres comandos:
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/post-name/
curl -s https://example.com/post-name/ | grep -c wp-content
curl -sI "https://example.com/?p=123" | grep -iE "^HTTP|^location"
En el banco, la página 404 de nginx o de Apache contenía 0 menciones de wp-content, mientras que la página 404 de Twenty Twenty-Five contenía 4. Cero significa que la petición nunca llegó a PHP y hay que corregir la configuración del servidor web. Un número mayor que cero significa que WordPress recibió la petición, pero no encontró ninguna regla que coincidiera.
La prueba con la dirección simple /?p=ID es útil, pero no se comporta como dicen muchas guías: no devuelve 200, sino que redirige (301) a la URL amigable. Pasó en todos los escenarios que probamos: /?p=304 devolvía 301 hacia /post-300/, que a su vez devolvía 404. La propia redirección demuestra que PHP y la base de datos funcionan y que lo que falla es la reescritura de URL.

Causa 1. Nginx sin el try_files correcto
Nginx ignora el .htaccess, así que todo depende del bloque location /. Ejecutamos la copia del sitio con tres versiones de ese bloque:
| URL | try_files $uri $uri/ /index.php?$args; |
try_files $uri $uri/ =404; |
sin location / |
|---|---|---|---|
| / | 200 | 200 | 200 |
| /post-300/ | 200 | 404 | 404 |
| /sample-page/ | 200 | 404 | 404 |
| /category/uncategorized/ | 200 | 404 | 404 |
| /wp-json/ | 200 | 404 | 404 |
| /?p=304 | 301 | 301 | 301 |
La portada siempre funciona porque, para «/», nginx encuentra index.php mediante la directiva index. La línea try_files $uri $uri/ =404; viene del sitio por defecto de Ubuntu, por eso acaba a menudo en configuraciones de WordPress. Con ella, error.log se quedó vacío, así que el registro no le llevará a la causa. Sin ningún bloque location /, el registro sí da una pista:
[error] ... "/var/www/r10wp/sample-page/index.php" is not found (2: No such file or directory), request: "GET /sample-page/ HTTP/1.1"
/wp-json/ también dejó de funcionar, de modo que el editor de bloques no puede guardar entradas. En cambio, /?rest_route=/ devolvía 200: la API REST funciona, solo falla la reescritura. El bloque con el que todas las URL del banco devolvieron 200:
server {
listen 80;
server_name example.com;
root /var/www/example.com/public_html;
index index.php;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
}
sudo nginx -T | grep -n try_files
sudo nginx -t && sudo systemctl reload nginx
Sustituya la versión de PHP en la ruta del socket por la suya. Más bloques de Nginx, incluidas las redirecciones a https y al dominio sin www, están reunidos en nuestros ejemplos listos de redirecciones 301.
Causa 2. Apache: falta el .htaccess, mod_rewrite desactivado o AllowOverride None
Para esta parte instalamos Apache 2.4.58 con PHP-FPM en un puerto aparte para la misma copia del sitio. Cuatro fallos distintos dieron casi el mismo resultado:
| Estado | Portada | Entrada | Registro de errores de Apache |
|---|---|---|---|
| Sin .htaccess | 200 | 404 | vacío |
| .htaccess con el envoltorio IfModule, mod_rewrite desactivado | 200 | 404 | vacío |
| .htaccess sin IfModule, mod_rewrite desactivado | 500 | 500 | Invalid command ‘RewriteEngine’ |
| mod_rewrite activado, pero AllowOverride None | 200 | 404 | vacío |
| mod_rewrite activado, AllowOverride All | 200 | 200 | sin errores |
Así que una simple página «Not Found» de Apache con el registro vacío es lo normal en tres de las cuatro causas. Tres comprobaciones permiten distinguirlas:
ls -la .htaccess # is the file in the site root
apache2ctl -M | grep rewrite # expect rewrite_module (shared)
# AllowOverride test: add any random word as the first line of .htaccess
También verificamos la prueba de la palabra al azar. Con AllowOverride None el sitio siguió devolviendo 404, porque el archivo se ignora. Con AllowOverride All devolvió 500 al instante, y en el registro apareció «Invalid command» con esa palabra. Elimine la línea después de la prueba.
El bloque estándar para un solo sitio, generado por el propio WordPress en el banco:
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
En un VPS donde usted administra Apache, active el módulo y permita el .htaccess para el directorio del sitio (después de esto, todas las URL del banco devolvieron 200):
sudo a2enmod rewrite
sudo systemctl restart apache2
# in the virtual host config
<Directory /var/www/example.com/public_html>
AllowOverride All
Require all granted
</Directory>
Una trampa de WP-CLI: wp rewrite flush --hard no creó el .htaccess y en su lugar mostró un aviso:
$ wp rewrite flush --hard --path=/var/www/r10wp
Warning: Regenerating a .htaccess file requires special configuration. See usage docs.
Success: Rewrite rules flushed.
Un archivo wp-cli.yml con apache_modules lo soluciona, pero WP-CLI busca ese archivo en el directorio actual, no en el indicado con --path. Solo funcionó así:
cd /var/www/example.com/public_html
printf 'apache_modules:\n - mod_rewrite\n' > wp-cli.yml
wp rewrite flush --hard
El archivo se creó con root como propietario, porque el comando se ejecutó como root. En un hosting, ejecute WP-CLI con el usuario del sitio o cambie el propietario; los permisos del .htaccess deben ser 644. Otros ajustes del servidor web los tratamos en nuestra guía de configuración del servidor.
Causa 3. Se cambió la estructura de enlaces permanentes y no se regeneraron las reglas
WordPress guarda las reglas de reescritura en la base de datos (la opción rewrite_rules) y solo las regenera con un «flush»: al guardar la página Enlaces permanentes, cuando lo hacen algunos plugins o cuando ejecuta wp rewrite flush. Si la estructura se cambia directamente en la base de datos o con wp option update (ocurre tras importar bases de datos y con scripts de migración), los enlaces del sitio ya son nuevos, pero las reglas siguen siendo las antiguas:
$ wp option update permalink_structure '/%year%/%monthnum%/%postname%/'
Success: Updated 'permalink_structure' option.
/2026/10/post-300/ 404 (theme 404 page, wp-content: 4)
/post-300/ 301 → /2026/10/post-300/
/sample-page/ 200
$ wp rewrite list --match=/2026/10/post-300/ --fields=match,query
(.?.+?)(?:/([0-9]+))?/?$ index.php?pagename=$matches[1]&page=$matches[2]
Las URL antiguas redirigen a las nuevas y las nuevas devuelven 404, así que el visitante se encuentra un error en cada enlace del menú. wp rewrite list --match muestra qué regla coincidió primero: aquí la regla de las páginas atrapó la URL, porque la base de datos no tiene reglas para la nueva estructura. La solución:
$ wp rewrite flush
Success: Rewrite rules flushed.
$ wp rewrite list --format=count
96
/2026/10/post-300/ 200
Los tipos de contenido nuevos se comportan igual. Registramos un tipo de contenido book en un mu-plugin: /book/test-book/ devolvía la página 404 del tema, aunque /?book=test-book se abría con 200. Tras wp rewrite flush el número de reglas pasó de 94 a 111 y la entrada se abrió. Por eso un 404 solo en los productos de WooCommerce o en otro tipo de contenido casi siempre se debe a reglas sin regenerar. La misma causa es habitual al mover WordPress a otro hosting importando la base de datos a mano.
Causa 4. home y siteurl incorrectos, o instalación en un subdirectorio
El sitio pasó de /blog/ a la raíz y la base de datos conservó la URL antigua
Pusimos home y siteurl con la URL de /blog mientras los archivos estaban en la raíz y Nginx estaba bien configurado. Las entradas seguían abriéndose con 200 tanto en /post-300/ como en /blog/post-300/: WordPress interpreta ambas. Pero todos los enlaces del HTML apuntaban a /blog/, los estilos de /blog/wp-content/ devolvían 404 y /wp-admin/ redirigía a /blog/wp-login.php, que también devolvía 404. Así que aquí el síntoma es otro: un sitio sin estilos y sin acceso al panel de administración.
wp option get home
wp option get siteurl
wp option update home https://example.com
wp option update siteurl https://example.com
Tras los dos comandos de actualización, los estilos devolvieron 200 en el banco y /wp-admin/ llevó a la página de acceso correcta. Si no puede entrar en el panel y no tiene SSH, defina los mismos valores con las constantes WP_HOME y WP_SITEURL en wp-config.php. Las URL antiguas dentro del contenido de las entradas se sustituyen con wp search-replace (ejecútelo primero con --dry-run); en esta prueba no lo ejecutamos.
WordPress en un subdirectorio, Nginx recurre al index.php de la raíz
Los archivos de WordPress estaban en /blog/ y la configuración solo tenía location / con recurso a /index.php. Resultado: /blog/ devolvía 200, /blog/post-300/ y /blog/sample-page/ devolvían la página 404 de nginx, y los estilos y wp-login.php devolvían 200. Es el mismo «solo funciona la portada», pero en un subdirectorio: nginx busca /index.php en la raíz y allí no hay ninguno. La solución con la que todas las URL devolvieron 200:
location /blog/ {
try_files $uri $uri/ /blog/index.php?$args;
}
En Apache, una instalación en un subdirectorio cambia dos líneas del .htaccess: RewriteBase /blog/ y RewriteRule . /blog/index.php [L]. Esta variante no la probamos en el banco.
Causa 5. Un plugin o tema con una regla de reescritura en conflicto
Los plugins que «quitan la base de las URL» (para que las landing pages o los productos se abran como /nombre/) añaden su propia regla al principio de la lista. Lo reprodujimos con un mu-plugin que registra un tipo de contenido personalizado y una regla:
add_rewrite_rule('^([^/]+)/?$', 'index.php?landing=$matches[1]', 'top');
- Justo después de la instalación no cambió nada: las reglas de un plugin llegan a la base de datos solo tras un flush. Por eso la avería suele aparecer más tarde, cuando alguien guarda los enlaces permanentes o actualiza otro plugin.
- Tras
wp rewrite flush, la portada y /category/uncategorized/ devolvían 200, mientras que /post-300/ y /sample-page/ devolvían la página 404 del tema. - Al eliminar el plugin sin hacer flush fue peor: /post-300/ devolvía 200, pero con el contenido de la portada (título del sitio, body class «home blog»). Un buscador vería decenas de duplicados de la portada en lugar de las entradas.
- Otro
wp rewrite flushlo dejó todo como estaba: 200 y la entrada correcta.
El mismo comando encuentra al culpable:
$ wp rewrite list --match=/post-300/ --fields=match,query,source
^([^/]+)/?$ index.php?landing=$matches[1] other
(.?.+?)(?:/([0-9]+))?/?$ index.php?pagename=$matches[1]&page=$matches[2] page
La fuente «other» indica una regla añadida por el código de un plugin o de un tema, no por el núcleo. Después, desactive los plugins sospechosos uno a uno (mejor en una copia del sitio) y ejecute wp rewrite flush tras cada paso. Un detalle curioso: la misma regla demasiado amplia apuntando a páginas normales (pagename) no rompió nada. WordPress la salta cuando ninguna página tiene esa ruta y pasa a la siguiente regla. Lo que rompe el sitio son las reglas que apuntan a variables de consulta personalizadas.
Causa 6. La caché sigue sirviendo un 404 guardado después de la corrección
Activamos fastcgi_cache con fastcgi_cache_valid 404 10m;. Primera observación: WordPress envía su 404 con Cache-Control: no-cache y una fecha Expires de 1984, así que por defecto nginx no lo guarda en caché (X-Cache: MISS en cada petición). El problema apareció al añadir fastcgi_ignore_headers Cache-Control Expires Set-Cookie;, una línea que se recomienda a menudo para la caché de WordPress:
broken /post-300/ 404 X-Cache: MISS
/post-300/ 404 X-Cache: HIT
rules fixed /post-300/ 404 X-Cache: HIT
/post-300/?utm=1 200 X-Cache: MISS
cache entry deleted /post-300/ 200 X-Cache: MISS
/post-300/ 200 X-Cache: HIT
La señal: la URL con cualquier parámetro se abre, mientras que la URL sin parámetros devuelve 404 con X-Cache: HIT. Puede borrar una sola entrada sin el módulo de purga, porque el nombre del archivo es el md5 de la clave de caché. En el banco, ese archivo contenía «KEY: httpGET127.0.0.1/post-300/» y «Status: 404 Not Found»; una vez borrado, la entrada devolvió 200.
# key = $scheme$request_method$host$request_uri, levels=1:2
KEY="httpsGETexample.com/post-name/"
H=$(echo -n "$KEY" | md5sum | cut -d' ' -f1)
sudo rm -f /var/cache/nginx/wp/${H: -1}/${H: -3:2}/$H
# or the whole cache
sudo find /var/cache/nginx/wp -type f -delete
Tome la ruta de la caché de su directiva fastcgi_cache_path. Vacíe por separado la caché del plugin y la del CDN. Para que los errores no se queden pegados, guarde los 404 en caché poco tiempo, por ejemplo fastcgi_cache_valid 404 1m;. Cómo afecta la caché del servidor a la velocidad lo explicamos en el artículo sobre caché y RAM en el hosting.
Tabla de decisión: síntoma, causa, comprobación, solución
| Síntoma | Causa | Comprobación | Solución |
|---|---|---|---|
| 404 sin el diseño del tema (página de nginx), registro vacío | try_files ... =404 en location / |
nginx -T | grep try_files |
try_files $uri $uri/ /index.php?$args; |
| Página 404 de nginx, el registro dice «index.php is not found» | No hay bloque location / | Registro de errores de nginx | Añadir location / con try_files |
| Página «Not Found» de Apache, registro vacío | Sin .htaccess, mod_rewrite desactivado o AllowOverride None | ls -la .htaccess, apache2ctl -M, prueba de la palabra al azar |
Restaurar el .htaccess, a2enmod rewrite, AllowOverride All |
| 500 en todas las páginas, incluida la portada | .htaccess sin IfModule con mod_rewrite desactivado | «Invalid command ‘RewriteEngine’» en el registro | a2enmod rewrite |
| 404 del tema en todas las entradas, las URL antiguas devuelven 301 | Estructura de enlaces cambiada sin flush | wp rewrite list --match=/url/ |
wp rewrite flush |
| 404 del tema solo en un tipo de contenido | Tipo de contenido nuevo, reglas sin regenerar | /?type=slug se abre | wp rewrite flush |
| 404 del tema en URL de un solo segmento, las categorías funcionan | Plugin con una regla demasiado amplia | La primera regla en wp rewrite list --match tiene la fuente other |
Desactivar el plugin, wp rewrite flush |
| Cualquier URL de un solo segmento muestra la portada con 200 | Plugin eliminado, su regla quedó en la base de datos | Body class «home» en la URL de una entrada | wp rewrite flush |
| El 404 sigue tras la corrección, la URL con parámetro devuelve 200 | La caché guardó el 404 | curl -sI url | grep -i x-cache → HIT |
Borrar la entrada o toda la caché |
| Sitio sin estilos, el acceso al panel devuelve 404 | home y siteurl incorrectos | wp option get home |
wp option update home y siteurl |
| /blog/ funciona, /blog/post/ devuelve el 404 de nginx | Instalación en subdirectorio, recurso a /index.php de la raíz | Configuración de location | location /blog/ con /blog/index.php?$args |
Preguntas frecuentes
¿Por qué tras cambiar de hosting solo funciona la portada?
Lo más habitual es que se haya perdido el archivo oculto .htaccess (los clientes FTP suelen ocultar y saltarse los archivos que empiezan por punto) o que el nuevo servidor use Nginx sin el try_files necesario. En el banco, ambos casos dieron el mismo cuadro: 200 en la portada y 404 en las entradas.
¿Cómo sé si mi sitio lo sirve Nginx o Apache?
Ejecute curl -sI https://example.com/ | grep -i server. En el banco, la cabecera era «Server: nginx/1.24.0 (Ubuntu)». Algunos proveedores la ocultan; en ese caso, consulte el tipo de servidor web en el panel de control. Si nginx hace de proxy delante de Apache, las reglas de WordPress las ejecuta Apache mediante el .htaccess.
¿Sirve de algo reinstalar WordPress?
No. En ninguno de los seis escenarios la causa estaba en los archivos del núcleo: las reglas viven en la base de datos, y pasar las peticiones a index.php es tarea del servidor web.
¿Unos días de errores 404 perjudican el posicionamiento en Google?
Una caída breve suele pasar sin consecuencias, pero si el rastreador ve 404 durante varios días, las páginas empiezan a salir del índice. Tras la corrección, revise algunas URL con la herramienta de inspección de URLs de Search Console y vuelva a enviar el sitemap.
Si prefiere no configurar usted mismo Nginx, Apache y la caché, eche un vistazo a los planes de hosting de RichHost: discos NVMe, panel de control propio con asistente de IA y SSL gratuito de Let’s Encrypt. También explicamos cómo elegir hosting para WordPress en una guía aparte.



